Skip to content

feat(php): make idempotency key generation IR-driven#17011

Open
devin-ai-integration[bot] wants to merge 4 commits into
mainfrom
devin/1783699524-php-idempotency-key
Open

feat(php): make idempotency key generation IR-driven#17011
devin-ai-integration[bot] wants to merge 4 commits into
mainfrom
devin/1783699524-php-idempotency-key

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Description

Makes the PHP SDK generator's auto-generated idempotency key IR-driven, mirroring the TypeScript reference (#17007). Instead of the previous per-generator config flag + hardcoded POST/PUT, the generator now reads ir.sdkConfig.idempotencyKeyGeneration (optional block with headerName and methods). Field present = enabled; absent = disabled.

The IR v67 upgrade this depends on (sdkConfig.idempotencyKeyGeneration only exists in IR v67) landed in #17026, now merged to main. This branch is rebased onto main, so the diff below is only the idempotency change.

Changes Made

  • HttpEndpointGenerator: attach the IR-configured header (default Idempotency-Key) with a freshly generated UUIDv4 on the IR-configured eligible methods (default POST/PUT). Caller-supplied value wins; method gating and header name come from the IR (no hardcoded POST/PUT).
  • New IdempotencyKey core helper (random_bytes(16) RFC 4122 v4 UUID). Shipped only when sdkConfig.idempotencyKeyGeneration != null (SdkGeneratorContext.getCoreAsIsFiles), so flag-OFF output is byte-identical to main.
  • Removed the per-generator config plumbing; no double-support of old + new.
  • packages/commons/api-workspace-commons/checkVersionExists.ts: added getGeneratorConfigObject() (falls back to generatorInvocation.config when raw.config is absent) used by getIdempotencyKeyGenerationFromGeneratorConfig() — the shared seed-harness fix, verbatim from the TS branch. +4 unit tests.
  • Seed fixture idempotency-headers split into no-custom-config (disabled) and auto-generate-idempotency-key (enabled) variants.

Testing

  • Unit tests added/updated — @fern-api/api-workspace-commons suite green (checkVersionExists.test.ts).
  • Manual testing completed — pnpm seed test --generator php-sdk --fixture idempotency-headers --local --skip-scripts: 2/2 pass.
    • auto-generate-idempotency-key variant, PaymentClient::create (POST): $headers['Idempotency-Key'] = $options['headers']['Idempotency-Key'] ?? IdempotencyKey::generate(); (caller-wins + UUID fallback).
    • PaymentClient::delete (DELETE): no injection.
    • no-custom-config variant: no IdempotencyKey helper and no header injection (byte-identical to disabled/main behavior).

Link to Devin session: https://app.devin.ai/sessions/ee9a7e8220314b9a832a30a1c0bbfaa6
Requested by: @cadesark


Open in Devin Review

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@nitpickybot nitpickybot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review Summary

The PR ports auto-generate idempotency key to the PHP SDK, gated behind a config flag. The core mechanism is sound, but the header-bag reference wiring has a bug that will drop caller-supplied header parameters when both a header parameter block and the idempotency block exist.

  • 🔴 1 critical issue(s)
  • 🟡 1 warning(s)

Comment on lines +252 to +254
if (!hasExistingHeaderBag) {
writer.writeTextStatement(`${HEADER_BAG_NAME} = []`);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 critical

When hasExistingHeaderBag is true, this block writes into $headers (HEADER_BAG_NAME), but the existing headerParameterCodeBlock populates its own variable (headerParameterBagReference). If those two references differ, the idempotency key lands in a different array than the one holding the caller's header params — and since headerBagReference below prefers the idempotency block's $headers, the header-parameter bag gets dropped from the request entirely.

Either reuse headerParameterCodeBlock.headerParameterBagReference as the target when it exists, or verify they're always the same variable. Otherwise declared header params silently vanish on POST/PUT when the flag is on.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 2a18937. The idempotency block now reuses `headerParameterCodeBlock.headerParameterBagReference` when it exists (both blocks were already writing to the same $headers var, but this makes it explicit and robust) and only declares a fresh bag when there is no existing one — so declared header params can never be dropped.

writer.write(`${HEADER_BAG_NAME}['${IDEMPOTENCY_KEY_HEADER}'] = `);
if (declaresIdempotencyKeyHeader) {
writer.write(
`$${this.context.getRequestOptionsName()}['headers']['${IDEMPOTENCY_KEY_HEADER}'] ?? `

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 warning

This reads $options['headers']['Idempotency-Key'] with an exact-case key, but the declaresIdempotencyKeyHeader check above does a case-insensitive match on the wire name. A header declared as e.g. idempotency-key would pass the check but the caller-wins lookup here uses the hardcoded Idempotency-Key casing, so it won't find a value under the actual declared name. Use the endpoint's declared wire value for the lookup key to stay consistent.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 2a18937. The lookup now uses the endpoint's declared wire value (getWireValue(declaredIdempotencyHeader.name)) for both the emitted header key and the $options['headers'][...] caller-wins lookup, so casing stays consistent with the case-insensitive match. The literal Idempotency-Key is only used for the standalone (non-declared) plain POST/PUT path.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 1 additional finding.

Open in Devin Review

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

SDK Generation Benchmark Results

Comparing PR branch against median of 5 nightly run(s) on main (latest: 2026-07-13T05:08:13Z).

Full benchmark table (click to expand)
Generator Spec main (generator) main (E2E) PR (generator) Delta
php-sdk square 61s (n=5) 85s (n=5) 48s -13s (-21.3%)

main (generator): generator-only time via --skip-scripts (includes Docker image build, container startup, IR parsing, and code generation — this is the same Docker-based flow customers use via fern generate). main (E2E): full customer-observable time including build/test scripts (nightly baseline, informational). Delta is computed against generator-only baseline.
⚠️ = generation exited with a non-zero exit code (timing may not reflect a successful run).
Baseline from nightly runs on main (latest: 2026-07-13T05:08:13Z). Trigger benchmark-baseline to refresh.
Last updated: 2026-07-13 21:36 UTC

@devin-ai-integration devin-ai-integration Bot force-pushed the devin/1783699524-php-idempotency-key branch from 5ef9ca7 to cfdd6b4 Compare July 10, 2026 23:14
@devin-ai-integration devin-ai-integration Bot changed the base branch from main to devin/1783724234-php-ir67-upgrade July 10, 2026 23:14
@devin-ai-integration devin-ai-integration Bot changed the title feat(php): auto-generate idempotency key for POST/PUT feat(php): make idempotency key generation IR-driven Jul 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Docs Generation Benchmark Results

Comparing PR branch against median of 5 nightly run(s) on main (latest: 2026-07-10T05:18:08Z).

Fixture main PR Delta
docs 241.5s (n=5) 251.1s (35 versions) +9.6s (+4.0%)

Docs generation runs fern generate --docs --preview end-to-end against the benchmark fixture with 35 API versions (each version: markdown processing + OpenAPI-to-IR + FDR upload).
Delta is computed against the nightly baseline on main.
Baseline from nightly run(s) on main (latest: 2026-07-10T05:18:08Z). Trigger benchmark-baseline to refresh.
Last updated: 2026-07-11 00:11 UTC

Base automatically changed from devin/1783724234-php-ir67-upgrade to main July 13, 2026 15:28
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration devin-ai-integration Bot force-pushed the devin/1783699524-php-idempotency-key branch from cfdd6b4 to a217cd6 Compare July 13, 2026 15:33
devin-ai-integration Bot and others added 3 commits July 13, 2026 17:26
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…idempotency resolver conflict in favor of main (global api.settings support)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Devin is archived and cannot be woken up. Please unarchive Devin if you want to continue using it.

3 similar comments
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Devin is archived and cannot be woken up. Please unarchive Devin if you want to continue using it.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Devin is archived and cannot be woken up. Please unarchive Devin if you want to continue using it.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Devin is archived and cannot be woken up. Please unarchive Devin if you want to continue using it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants