Skip to content

fix: Emit resource constructor parameters required first - #473

Merged
razor-x merged 1 commit into
betafrom
claude/php-audit-uoa7nb-m8-resource-param-order
Aug 19, 2026
Merged

fix: Emit resource constructor parameters required first#473
razor-x merged 1 commit into
betafrom
claude/php-audit-uoa7nb-m8-resource-param-order

Conversation

@razor-x

@razor-x razor-x commented Aug 17, 2026

Copy link
Copy Markdown
Member

Fixes finding M8 from the cross-SDK audit.

Rebased onto beta at a2cd8ce (4.0.0-beta.13) and regenerated, so the conflict is gone.

The problem

codegen/lib/layouts/resource.ts sorted properties by name alone:

const sorted = [...schema.properties].sort((a, b) => a.name.localeCompare(b.name))

So a property carrying = null could land ahead of a required one — AcsUser declared $access_schedule = null before $acs_system_id. PHP deprecates that, treats the "optional" parameter as required anyway (making every default inert), and PHP 9 is expected to make it fatal.

Measured on beta:

$ php -d error_reporting=E_ALL -r 'foreach (glob("src/Resources/*.php") as $f) require $f;' 2>&1 | grep -c Deprecated:
409

409 notices across 26 of 32 files. Zero in src/Routes — the route generator was fixed for this in 1652d01, but that commit never touched the resource generator.

Nothing saw them. psalm.xml and phpunit.xml.dist both exclude src/Resources, and PHPUnit routes deprecations to a channel failOnWarning does not cover, so the suite stayed green through all 409.

This is not only cosmetic. composer lint:syntax shells out to php -l, so it fails on any host whose error_reporting includes E_DEPRECATED — PHP's own default when no php.ini is loaded. It passes in CI only because the container's php.ini sets E_ALL & ~E_DEPRECATED.

The fix

Emit required properties first, optional after, alphabetical within each group — the same shape sortPhpClientMethodParameters already produces for endpoint parameters.

Deliberately not fixed by giving every parameter a default. Which properties are required is the API definition's call, carried on isOptional; blanket-defaulting would erase that distinction to work around a code-generation ordering problem. from_json passes every value by name, so it is unaffected by the reorder.

Result:

$ php -d error_reporting=E_ALL -r 'foreach (glob("src/Resources/*.php") as $f) require $f;' 2>&1 | grep -c Deprecated:
0

Also in this PR:

  • failOnDeprecation="true" in phpunit.xml.dist, so this cannot come back unnoticed.
  • Corrected the generator's header comment, which claimed "the blueprint does not track which resource properties are required, so every property is optional." It does track it, isOptional is false for many properties, and that is precisely how required parameters ended up interleaved.
  • MIGRATION.md entry, since positional new Resource(...) calls change order.

Breaking change

Constructing a resource positionally binds different parameters now. Reading properties, from_json, and named-argument construction are all unaffected.

Verification

  • npm run generate produces exactly the committed diff — src/Resources only, no src/Routes drift.
  • Full suite: 228 tests green with failOnDeprecation on, zero deprecations.
  • php -l clean under -d error_reporting=E_ALL.
  • psalm clean, npm run lint clean, composer validate --strict clean.

Note on the diff size

The bulk of the 852 deletions / 878 insertions is the per-property docblocks moving with their parameters — roughly 50 added / 50 removed per resource file. The only substantive changes are codegen/lib/layouts/resource.ts, phpunit.xml.dist, and MIGRATION.md.

One thing worth knowing if you regenerate locally: node_modules must be refreshed (npm ci) after #478 landed. A stale @seamapi/types reverts GET→POST across 19 route files, which is unrelated to this change and should not be committed.


Generated by Claude Code

Resource constructor parameters were ordered by property name alone, so
an optional property, one carrying a null default, could be declared
ahead of a required one. PHP deprecates that ordering, which made every
optional default inert, and PHP 9 is expected to make it fatal.

Loading the resource classmap emitted 409 E_DEPRECATED notices across 26
of the 32 files. None of the tooling saw them: psalm and phpunit both
exclude src/Resources, and phpunit routes deprecations to a channel
failOnWarning does not cover, so the suite stayed green through all of
them. Nor is it only cosmetic. composer lint:syntax shells out to php
-l, so it fails on any host whose error_reporting includes E_DEPRECATED,
which is PHP's own default when no php.ini is loaded.

Emit required properties first and optional ones after, alphabetical
within each group, the same shape the route generator already produces
for endpoint parameters. Which properties are required stays the
blueprint's call, read from isOptional, rather than being flattened by
giving everything a default. from_json passes every value by name, so it
is unaffected.

Turn on failOnDeprecation so this cannot come back unnoticed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HH3wdHh4Y6Wjyc5uHwk5iG
@razor-x
razor-x force-pushed the claude/php-audit-uoa7nb-m8-resource-param-order branch from eef6fa4 to a319a2b Compare August 19, 2026 21:22
@razor-x
razor-x merged commit 9ebee0c into beta Aug 19, 2026
15 checks passed
@razor-x
razor-x deleted the claude/php-audit-uoa7nb-m8-resource-param-order branch August 19, 2026 21:30
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.

2 participants