THRIFT-5959: Adopt PSR-12 in PHP library and align generator emission#3449
Closed
sveneld wants to merge 15 commits into
Closed
THRIFT-5959: Adopt PSR-12 in PHP library and align generator emission#3449sveneld wants to merge 15 commits into
sveneld wants to merge 15 commits into
Conversation
Client: php
Switch the PHP coding standard from PSR-2 (frozen since 2019) to PSR-12,
remove the legacy underscore exclusions in phpcs.xml.dist, and clean up
PHP-only underscore quirks across both the runtime and the generator.
Renames (public-API BC break for downstream PHP consumers):
* `$_TSPEC` -> `$tspec` on every generated struct, on TApplicationException,
and in the C++ generator emission. Mirrors Python's `thrift_spec`.
* `_read` / `_write` on TBase and TException -> `readStruct` / `writeStruct`
(the new names avoid clashing with the public abstract `read` / `write`).
* Private helpers `_readMap` / `_readList` / `_writeMap` / `_writeList`
-> `readMap` / `readList` / `writeMap` / `writeList`.
* Trailing-underscore properties across the runtime (TServer, TSocket,
TBufferedTransport, TBinaryProtocol, THttpClient, TCurlClient,
TJSONProtocol, etc., ~85 declarations / ~509 references) -> drop the `_`.
* Generator-emitted `$input_` / `$output_` / `$seqid_` on service clients
-> `$input` / `$output` / `$seqid`.
* Generator-emitted `$__names` on enums -> `$names`.
* Private validators `_validateForRead` / `_validateForWrite` ->
`validateForRead` / `validateForWrite`.
* REST helper `$impl_` -> `$impl`.
Other PSR-12 alignments in the C++ generator:
* `static public` / `static protected` -> `public static` / `protected
static`.
* Class constants emitted with explicit `public` visibility.
* Method declarations now place `{` on the next line (jsonSerialize,
validators, REST handlers, REST constructor).
Tooling and docs:
* phpcs.xml.dist: ruleset PSR2 -> PSR12, both underscore exclusions
removed.
* lib/php/coding_standards.md: updated to PSR-12 with a note that
Apache Thrift PHP code does not use leading or trailing underscores.
Test suite updated to use the new property and method names; hand-written
fixtures (TestSerializerStruct, TestRichException, NestedStruct,
ComplexStruct, TBaseTest) updated alongside the runtime.
Verified locally with vendor/bin/phpcs (clean), vendor/bin/phpstan
(clean) and vendor/bin/phpunit -c lib/php/phpunit.xml (629 tests pass,
0 errors, 0 failures; one unrelated pre-existing warning at
TSocketPoolTest:210).
Generated fixtures under lib/php/test/Resources/packages/ are
regenerated by `make stubs`; they are excluded from phpcs and remain
untracked per existing project convention.
Generated-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Client: php `$p` was a one-letter private property holding the parent protocol reference in the JSON / SimpleJSON context classes (LookaheadReader, ListContext, PairContext, StructContext, MapContext). Renames it to the descriptive `$protocol` (matching the constructor parameter name) so calls like `$this->protocol->getTransport()->...` read clearly. Affected files: * lib/php/lib/Protocol/JSON/LookaheadReader.php * lib/php/lib/Protocol/JSON/ListContext.php * lib/php/lib/Protocol/JSON/PairContext.php * lib/php/lib/Protocol/SimpleJSON/ListContext.php * lib/php/lib/Protocol/SimpleJSON/StructContext.php * lib/php/lib/Protocol/SimpleJSON/MapContext.php These are private properties, so this rename is internal-only with no public BC impact. Generated-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Client: php Replace legacy `array(...)` with the short `[...]` syntax (PHP 5.4+) across the runtime, hand-written test fixtures, and the C++ generator emission. Add `Generic.Arrays.DisallowLongArraySyntax` to phpcs.xml.dist to enforce going forward. Generator updates (compiler/cpp/src/thrift/generate/t_php_generator.cc): * Enum `$names` table emits `[ ... ]` (was `array( ... )`). * Struct `$tspec` and the nested map / list / set / element type-spec arrays emit short syntax. * `render_const_value` emits short syntax for struct, map, list, and set constants — including the `new Foo([...])` call shape. * Local accumulators (`$prefix = [];`) and default parameter values (`= []`) emit short syntax. Hand-written runtime: `vendor/bin/phpcbf` auto-converted the remaining sites in `lib/php/lib/` and `lib/php/test/Unit/`. `TApplicationException::$tspec` is reformatted to one element per line with a trailing comma, matching the generator's emission style; the previous form was a hard-to-scan multi-line nested call without aligned indentation. Verified `vendor/bin/phpcs` (clean), `vendor/bin/phpunit -c lib/php/phpunit.xml` (629 tests pass, 0 errors, 0 failures), and `make stubs` (regenerated fixtures show only the expected array-syntax diff). Generated-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ties Client: php Add the `Thrift\Base\TrailingUnderscorePropertyCompat` trait, a transitional aid for downstream code still reading or writing the old trailing-underscore property names (`$obj->host_`, `$obj->context_`, etc.) renamed in the THRIFT-5959 PSR-12 cleanup. The trait implements `__get` / `__set` / `__isset` / `__unset`; when an inaccessible name ending in `_` matches an existing property without the underscore, the access is proxied and an `E_USER_DEPRECATED` notice is emitted pointing at the new name. Names that do not match an existing property fall through to PHP's default undefined-property behaviour. The trait is added on the runtime base classes so subclasses inherit it automatically: * `Thrift\Transport\TTransport` (covers TSocket, TSSLSocket, TBufferedTransport, TFramedTransport, TMemoryBuffer, TPhpStream, TSocketPool, TCurlClient, THttpClient, TNullTransport). * `Thrift\Server\TServer` (covers TSimpleServer, TForkingServer). * `Thrift\Server\TServerTransport` (covers TServerSocket, TSSLServerSocket). * `Thrift\Protocol\TProtocol` (covers TBinaryProtocol, TBinaryProtocolAccelerated, TCompactProtocol, TJSONProtocol, TSimpleJSONProtocol, TProtocolDecorator, TMultiplexedProtocol, StoredMessageProtocol). * `Thrift\Protocol\JSON\BaseContext` (covers ListContext, PairContext, LookaheadReader uses no parent so trait used directly there is not needed — JSON Context classes inherit). * `Thrift\Protocol\SimpleJSON\Context` (covers ListContext, StructContext, MapContext). Standalone classes use the trait directly: * `Thrift\TMultiplexedProcessor` * `Thrift\Factory\TBinaryProtocolFactory` Note: PHP has no `__getStatic` magic method, so the `Class::$_TSPEC -> Class::$tspec` rename remains a hard break and cannot be shimmed; see the PR description for migration guidance. Six unit tests cover read, write, isset, undefined-name fall-through, and the no-shim-without-real-property guard. Generated-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Client: php The previous approach added a `TrailingUnderscorePropertyCompat` trait on every base class, which was overkill: the only public instance properties renamed by THRIFT-5959 are three on `TJSONProtocol` (`$contextStack_`, `$context_`, `$reader_`); every other rename was on `protected` or `private` members that are not part of the public contract. Replace the trait with a focused `__get` / `__set` / `__isset` / `__unset` block on `TJSONProtocol` that only handles those three aliases. The map of deprecated names lives in a class constant so the shim is easy to grep and easy to remove once the deprecation period ends. Static properties (`$_TSPEC`, `$__names`) cannot be shimmed via magic methods because PHP has no `__getStatic`. Those renames remain a hard break, documented in the PR description. Files removed: * lib/php/lib/Base/TrailingUnderscorePropertyCompat.php * lib/php/test/Unit/Lib/Base/TrailingUnderscorePropertyCompatTest.php Files reverted (trait usage removed): * TTransport, TServer, TServerTransport, TProtocol, BaseContext, Context, TMultiplexedProcessor, TBinaryProtocolFactory. New test `TJSONProtocolDeprecatedAliasesTest` (10 cases) pins the read / write / isset / fall-through behaviour of the inline shim. Generated-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Client: php The PSR-12 cleanup renamed `public static $_TSPEC` to `$tspec` on every generated struct, but the `thrift_protocol` C extension reads that static property by name via `zend_read_static_property(..., "_TSPEC", ...)`. Without this fix the accelerated binary protocol crashes with `TProtocolException: Attempt deserialize to non-Thrift object` whenever it tries to look up a struct's spec, which propagates to every cross- language test that involves a PHP server using the accelerated protocol (php-php accel*, php-rs accel-binary, php-nodets accel-binary, and the TBinarySerializer path in php-php json). Replace all seven `_TSPEC` lookups in `lib/php/src/ext/thrift_protocol/php_thrift_protocol.cpp` with the new `tspec` name. Also fix a pre-existing typo on line 1175 that wrote `_TPSEC` in the `sizeof()` argument; the read happened to work because both literals were five characters long. Found by re-running CI on the THRIFT-5959 branch — see https://github.com/apache/thrift/actions/runs/25402704647 for the failing cross-tests this commit unblocks. Generated-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Client: php Audit found no callers outside `TJSONProtocol` for the three properties that the previous commit had kept public behind a magic-method shim (`$contextStack`, `$context`, `$reader`): every reference is internal `$this->...` access. Tighten visibility from `public` to `private` and delete the deprecation shim entirely (the previous `__get` / `__set` / `__isset` / `__unset` block plus `DEPRECATED_PROPERTY_ALIASES`). Any downstream code that was reading these properties was already crossing an undocumented internal boundary, so making them private is the correct cleanup rather than carrying a permanent compatibility layer for them. Also rename two leading-underscore local variables emitted by the generator (`$_uuid_bin` / `$_uuid_hex`) to `$uuidBin` / `$uuidHex` for consistency with the rest of the PSR-12 cleanup. PSR-12 sniffs do not flag local variables, but the prior names were the last residue of the old underscore convention in generator output; aligning them lets the "no leading underscore in PHP identifiers" rule be applied uniformly. Files removed: * lib/php/test/Unit/Lib/Protocol/TJSONProtocolDeprecatedAliasesTest.php Verified locally: vendor/bin/phpcs clean; vendor/bin/phpunit -c lib/php/phpunit.xml -> 629 tests pass, 0 errors, 0 failures; make stubs regenerates with the new $uuidBin / $uuidHex local names. Generated-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Client: php
The generator's `generate_php_type_spec` outdented the closing `]`
*after* emitting it for the `'val'` (map) and `'elem'` (list / set)
sub-specs, while the matching `'key'` branch did it in the right order.
Pre-existing bug — was already producing a 4-space over-indent under
the old `array(...)` syntax, but became visible once short array syntax
made the closing bracket sit on its own line.
Before:
'val' => [
'type' => TType::I64,
], <- 16 spaces, should be 12
],
After:
'val' => [
'type' => TType::I64,
],
],
Move `indent_down()` ahead of the closing-bracket emission for both
branches so the close lines up with the opening `'val' =>` / `'elem' =>`.
Generated-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Client: php Replace the bare `<exclude-pattern>` with a comment that explains the cross-Thrift code-generation conventions (snake_case `process_*`, `send_*`, `recv_*` method names; `<service>_<method>_args` / `<service>_<method>_result` class names; multiple classes per file in the classmap variant) which would otherwise trip PSR-1 / Squiz strict sniffs. Auditing the regenerated fixtures with the exclude removed showed 1141 violations after `phpcbf`, all four sources rooted in those conventions and impossible to fix without breaking ABI parity with the other Thrift language generators. No behavioural change to the lint configuration — the exclude is identical, only the rationale is now in the file. Generated-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Client: php
Make the generated PHP header PSR-12-compliant and embed `phpcs:disable`
directives in the autogen comment so the regenerated test fixtures lint
cleanly without any per-project phpcs configuration. The blanket
`<exclude-pattern>lib/php/test/Resources/packages/*</exclude-pattern>`
in `phpcs.xml.dist` is therefore removed; the lint scope now covers
those fixtures end-to-end.
Generator changes:
* `generate_program_header` and `generate_service_header` reordered so
the file-level docblock sits between `<?php` and the namespace
declaration, with the required surrounding blank lines (PSR-12 §3).
* New `t_php_generator::autogen_comment()` override that adds inline
`phpcs:disable` lines to the docblock. The disabled sniffs split
into two groups:
1. Cross-Thrift naming conventions that must remain consistent
with every other Thrift language generator
(`PSR1.Classes.ClassDeclaration.MultipleClasses`,
`PSR1.Methods.CamelCapsMethodName`,
`Squiz.Classes.ValidClassName`).
2. Cosmetic emission style still inherited from the legacy
generator and out of scope here
(`Squiz.WhiteSpace.SuperfluousWhitespace`,
`Generic.WhiteSpace.ScopeIndent`,
`Generic.ControlStructures.InlineControlStructure`,
`PSR12.Operators.OperatorSpacing`,
`PSR12.Classes.ClassInstantiation`,
`PSR2.Classes.ClassDeclaration`,
`PSR2.Files.EndFileNewline`).
After regenerating with `make stubs`, `vendor/bin/phpcs` covers the
full `lib/php/lib`, `lib/php/test`, and
`lib/php/test/Resources/packages` tree and exits 0 (435 fixture files
inclusive). `vendor/bin/phpunit -c lib/php/phpunit.xml` continues to
pass (629 tests, 0 errors, 0 failures).
Future work can incrementally fix the emission style sniffs in the
generator and remove them from the disable list one at a time.
Generated-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…le list)
Client: php
Fix the cosmetic emission issues that previously forced the
auto-generator to ship a long `phpcs:disable` list. The disable list in
the autogen comment is now down to just the three cross-Thrift naming
conventions that genuinely have to stay
(`PSR1.Classes.ClassDeclaration.MultipleClasses`,
`PSR1.Methods.CamelCapsMethodName`, `Squiz.Classes.ValidClassName`).
Generator changes (`t_php_generator.cc`):
* Inline-mode branches now emit four-space body indents inside `if` /
loops instead of the two-space leftovers from the original PSR-2
port (covers `break;`, `return;`, `$input->...`, `$output->...`,
`$x = new ...`, `$x->write(...)`, `$len = ...`, `$val = ...`,
`$arr[1] = ...`, the field-name assignments, etc.).
* The OOP read/write branches no longer emit a trailing `out << '\n'`
after `scope_down`, which was producing a stray blank line before
the closing class brace.
* `$json = new stdClass;` now emits the parentheses required by
`PSR12.Classes.ClassInstantiation`.
* Enum class close (`f_enum << "}"`) emits a single trailing newline
instead of two.
* Service-rest method and class closes likewise emit a single newline.
* The classmap-mode service file is no longer padded with an extra
`'\n'` before close.
* `$arr[1] * 4294967296` and `$elem[] = $value;` now use proper
PSR-12 operator spacing (was `*` without spaces, was `[]= ` glued
together).
* The "Bad version identifier" check is emitted as a multi-line
`if (...) { throw ...; }` block instead of an inline control
structure, with `' . $ver` instead of `'.$ver`.
* `process_<method>` dispatcher emits `'process_' . $fname` and the
unknown-method exception path uses `' . $fname . '` and proper
`indent()` instead of hard-coded four-space prefixes layered on top
of `indent_up()` (the prior emission was double-indented).
* Removed the orphan `out << '\n'` after `getName()`'s closing brace
that produced a triple newline.
Generator base (`t_generator.cc`):
* `generate_docstring_comment` now strips trailing whitespace from
the line prefix when emitting an empty docstring line. This stops
every Thrift language generator from leaking ` * ` (asterisk +
trailing space) on otherwise-empty doc lines, which Squiz's
`SuperfluousWhitespace` sniff rightly flags. Behaviour change is
universally an improvement; no language relies on the trailing
space.
Result: `vendor/bin/phpcs` over `lib/php/lib`, `lib/php/test`, and
`lib/php/test/Resources/packages/` (435 fixture files) exits 0 with
zero output. `vendor/bin/phpunit -c lib/php/phpunit.xml` continues to
pass (629 tests, 0 errors, 0 failures).
Generated-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Client: php
Previously the generated PHP autogen comment carried all three
cross-Thrift `phpcs:disable` lines on every file, even on plain
structs / enums / interfaces / REST stubs that do not contain any
identifier the disabled sniffs would fire on. Make the disable list
context-aware: each file shape now receives only the rules it
actually needs.
Per-file-type disable lists:
| File shape | Disables |
|--------------------------------------|------------------------------------------------|
| Plain struct / enum / interface / REST | (none) |
| Service args / result helper struct | `Squiz.Classes.ValidClassName` |
| Service client | `PSR1.Methods.CamelCapsMethodName` |
| Service processor | `PSR1.Methods.CamelCapsMethodName` |
| Top-level `Constant.php` | `PSR1.Methods.CamelCapsMethodName` |
| Classmap-mode service / Types file | all three (multi-class file containing client, processor, args/result, ...) |
Implementation:
* Replace the parameterless `autogen_comment()` override with a new
`php_autogen_comment(disables)` helper that builds the docblock and
inlines `phpcs:disable <rule>` lines for whatever rules are passed.
`autogen_comment()` itself now returns `php_autogen_comment({})` so
base-class callers see a clean comment.
* Add a `phpcs_disables` parameter (default empty) to
`generate_program_header` and `generate_service_header`.
* Update every call site to pass the appropriate rule list — most
pass `{}` and produce a clean header.
`vendor/bin/phpcs` over `lib/php/lib`, `lib/php/test`, and
`lib/php/test/Resources/packages/` continues to exit 0.
`vendor/bin/phpunit -c lib/php/phpunit.xml` continues to pass
(629 tests, 0 errors, 0 failures).
Generated-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Client: php
The inline-mode read/write paths and the OOP send-message helper
emitted the strict-binary-protocol version marker as the magic numbers
`0x80010000` and `0xffff0000` directly. The runtime
`TBinaryProtocol` class already exposes these as named constants
(`VERSION_1` and `VERSION_MASK`) and uses them in its non-inlined
read path. Switch the generator to emit those names so the code is
self-documenting and a future bump of the binary protocol version is
a one-line edit in the runtime.
Changes in `t_php_generator.cc`:
* `php_includes()` now also emits `use Thrift\Protocol\TBinaryProtocol;`
alongside the existing `TBinaryProtocolAccelerated` import.
* The inline-mode message-header check
(`recv_<method>` / `process_<method>` paths) emits
```
$ver = $ver & TBinaryProtocol::VERSION_MASK;
if ($ver != TBinaryProtocol::VERSION_1) {
throw new TProtocolException('Bad version identifier: ' . $ver,
TProtocolException::BAD_VERSION);
}
```
* The two write-path version emissions
(`$buff = pack('N', (0x80010000 | $msgType));`) now use
`TBinaryProtocol::VERSION_1`.
The other magic numbers in the same emission paths (`0x7fffffff`,
`0xffffffff`, `0xffff`) are PHP-specific sign-extension masks for
varint decoding, not protocol versions, so they stay as numeric
literals.
Verified `vendor/bin/phpcs` clean and
`vendor/bin/phpunit -c lib/php/phpunit.xml` passes (629 tests, 0
errors, 0 failures).
Generated-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Client: php A previous commit on this branch fixed `* ` (asterisk + trailing space) on otherwise-empty docstring lines by editing the base `t_generator::generate_docstring_comment`. That helper is shared by every Thrift language generator, so a PHP-only ticket should not be changing it. Revert the base file and keep the fix scoped to the PHP generator. Replace `t_php_generator::generate_php_docstring_comment` so it emits the docblock body itself instead of forwarding to the base helper. The PHP-local body is the same loop, except an empty docstring line produces ` *` (no trailing space) rather than ` * `, matching what the runtime classes already do and keeping `Squiz.WhiteSpace.SuperfluousWhitespace` happy. The remaining direct call to `generate_docstring_comment` inside `generate_php_doc` for functions is updated to go through the same helper for consistency. `compiler/cpp/src/thrift/generate/t_generator.cc` is restored to its pre-PR state. No other language generator's output changes. Verified: `vendor/bin/phpcs` clean, `vendor/bin/phpunit -c lib/php/phpunit.xml` passes (629 tests, 0 errors, 0 failures), and `grep ' \* $' lib/php/test/Resources/packages/.../*.php` returns nothing (no surviving trailing-whitespace docblock lines). Generated-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…END_STRL)
Client: php
Three small reuse / quality cleanups discovered during a /simplify
review of the branch:
* Hoist the cross-Thrift `phpcs:disable` rule lists out of the call
sites and into named member vectors:
`PHPCS_DISABLES_CLASSMAP`, `PHPCS_DISABLES_SNAKE_CASE_METHODS`,
`PHPCS_DISABLES_SERVICE_HELPERS`. Each call to
`generate_program_header` / `generate_service_header` now passes the
named vector instead of an inline string-literal list, so a future
rename or addition is a one-line edit instead of touching seven sites.
* Collapse `generate_program_header` and `generate_service_header`
bodies (they were byte-for-byte identical except for the
`t_program*` source) into a shared `emit_file_header` helper. Both
public entry points are now thin forwarders.
* Make `php_autogen_comment` delegate to the inherited
`t_oop_generator::autogen_comment()` and splice the `phpcs:disable`
block in just before the closing `*/`, instead of duplicating the
base docblock structure (`/**`, summary, "DO NOT EDIT...",
`@generated`, `*/`). Future tweaks to the base autogen comment now
flow through automatically.
C extension (`lib/php/src/ext/thrift_protocol/php_thrift_protocol.cpp`):
* All seven `zend_read_static_property(..., "tspec", sizeof("tspec")-1, ...)`
calls now use the idiomatic `ZEND_STRL("tspec")` macro that Zend
ships exactly for this `(const char*, size_t)` pair pattern.
No behavioural change: `vendor/bin/phpcs` and
`vendor/bin/phpunit -c lib/php/phpunit.xml` (629 tests, 0 errors)
remain clean. Generated fixtures are byte-identical for the same
inputs (verified by regenerating all six variants).
Generated-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Member
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements THRIFT-5959: adopts PSR-12 across the PHP library and aligns the C++ generator emission with the same standard.
PSR-2 has been frozen since 2019. PSR-12 is its modern successor and what
squizlabs/php_codesniffer ^3.10(already a project dependency) supports out of the box. While reformatting, this PR also cleans up the PHP-only leading/trailing-underscore identifier conventions that the runtime and generator inherited from the original PHP port — every other Thrift language target (Pythonthrift_spec, JavametaDataMap, RubyFIELDS, Go/C#Read/Write) uses neither leading nor trailing underscores.The lint scope now covers the full
lib/php/lib/,lib/php/test/, and the regeneratedlib/php/test/Resources/packages/tree (435 fixture files);vendor/bin/phpcsexits 0. Runtime behaviour is unchanged — the diff is renames, formatting, generator output, tooling, and docs.What changed
Tooling
phpcs.xml.dist:PSR2→PSR12.Generic.Arrays.DisallowLongArraySyntax.<exclude-pattern>lib/php/test/Resources/packages/*</exclude-pattern>; the regenerated fixtures now lint cleanly via inlinephpcs:disabledirectives in the autogen comment (see below).lib/php/coding_standards.md: PSR-2 link replaced with PSR-12; explicit note that Apache Thrift PHP code does not use leading or trailing underscores on identifiers.Runtime renames (
lib/php/lib/)public static $_TSPEC→public static $tspecon every generated struct and onTApplicationException. Mirrors Python'sthrift_spec._read/_writehelpers onTBaseandTException→readStruct/writeStruct(the new names avoid clashing with the public abstractread/write, which take a different signature)._readMap/_readList/_writeMap/_writeList→ drop the leading_.TServer,TSocket,TBufferedTransport,TBinaryProtocol,THttpClient,TCurlClient,TServerSocket,TSSLSocket,TFramedTransport,TPhpStream,TJSONProtocol, etc.) → drop the trailing_(~509 internal reference updates).TJSONProtocol::$contextStack/$context/$reader: rename and tighten visibility frompublictoprivate. An audit found no callers outside the class — every reference was internal$this->...access — so a deprecation shim was not needed and was deliberately not introduced.$pprivate property in JSON / SimpleJSON context classes → descriptive$protocol.Hand-written test fixtures
TestSerializerStruct.php,TestRichException.php,NestedStruct.php,ComplexStruct.php, andTBaseTest.phpupdated alongside the runtime;AbstractValidatorTestCasenow inspectsvalidateForRead/validateForWrite;ReflectionHelper-based property-name string arguments updated.C++ generator (
compiler/cpp/src/thrift/generate/t_php_generator.cc)Naming alignment with the new runtime:
$tspec,readStruct(...),writeStruct(...),validateForRead,validateForWrite.$input/$output/$seqid(was$input_/$output_/$seqid_).$names(was$__names).$impl(was$impl_).$uuidBin/$uuidHex(was$_uuid_bin/$_uuid_hex).TBinaryProtocol::VERSION_1/VERSION_MASKnamed constants instead of the0x80010000/0xffff0000magic numbers (matches what the runtimeTBinaryProtocolalready does for non-inlined paths).PSR-12 emission alignment:
<?php⇢ blank ⇢ file-level docblock ⇢ blank ⇢namespace⇢ blank ⇢useblock (PSR-12 §3).[...]for$names,$tspec, type-spec sub-arrays ('key'/'val'/'elem'), const values (struct / map / list / set), and local accumulators / default parameter values.publicvisibility.public static/protected staticordering instead ofstatic public/static protected.jsonSerialize, validators, REST handlers, REST constructor.'val'(map) and'elem'(list / set) sub-specs fixed:indent_down()now precedes the closing-bracket emission so the close lines up with the opening'val' =>/'elem' =>.$json = new stdClass();instead ofnew stdClass.if (...) { throw ...; }for the strict-binary version check instead of an inline control structure.'process_' . $fnameand' . $fname . 'with proper PSR-12 concat spacing; unknown-method exception path usesindent()rather than hard-coded four-space prefixes layered on top ofindent_up().$arr[1] * 4294967296and$name[] = $valueuse proper PSR-12 operator spacing.generate_php_docstring_comment; the cross-language baset_generator::generate_docstring_commentis untouched).phpcs:disabledirectives in the autogen docblock are emitted per file shape, not blanket:Squiz.Classes.ValidClassNamePSR1.Methods.CamelCapsMethodNameConstant.phpPSR1.Methods.CamelCapsMethodNameThe three cross-Thrift sniffs we ever disable cover naming conventions that must remain consistent with the other Thrift language generators (snake_case
process_*/send_*/recv_*method names,<service>_<method>_argsclass names, multi-class classmap files). Rule lists are hoisted to named member vectors so a future rename or addition is a one-line edit.generate_program_headerandgenerate_service_headercollapse into a sharedemit_file_headerhelper;php_autogen_commentdelegates to the inheritedt_oop_generator::autogen_comment()and splices the optionalphpcs:disableblock before the closing*/, avoiding duplication of the base docblock structure.C extension (
lib/php/src/ext/thrift_protocol/php_thrift_protocol.cpp)Critical for cross-language tests with PHP server (without this, the accelerated binary protocol crashes with
TProtocolException: Attempt deserialize to non-Thrift object):zend_read_static_property(..., "_TSPEC", sizeof("_TSPEC")-1, ...)calls now read"tspec"via the idiomaticZEND_STRL("tspec")Zend macro.sizeof("_TPSEC")-1, only working by accident because both literals had the same length) is fixed in the same pass.Breaking change
This is a deliberate, documented public-API BC break for downstream PHP consumers. Affected surfaces:
Class::$_TSPECreads on every generated struct → must useClass::$tspec. PHP has no__getStatic, so this rename cannot be shimmed; callers must migrate at the same time as the upgrade.Class::$__namesreads on enums → must useClass::$names. Same constraint.TJSONProtocol::$contextStack/$context/$readerare nowprivate. Audit found no external callers in the runtime or test suite, so the public surface is removed rather than renamed.TServer/TSocket/TBufferedTransport/TBinaryProtocol/THttpClient/TCurlClient/TServerSocket/TSSLSocket/TFramedTransport/TPhpStream/TSimpleJSONProtocolreferencing protected trailing-underscore properties → drop the trailing_on the new name._read/_writeonTBaseorTException→ usereadStruct/writeStruct.The JIRA ticket needs the Breaking-Change label per the contributing guide (CLI authorization not granted; will be set manually).
Out of scope (deferred to follow-up tickets)
declare(strict_types=1)— semantic change; warrants its own ticket.readonly, native enums) — separate refactor.init_FOO,process_<name>,send_<name>,recv_<name>) — deliberate cross-Thrift snake_case generator-name conventions; PSR-12 does not forbid snake_case method names.lib/php/src/Thrift.phplegacy non-namespaced bundle (not autoloaded since the PSR-4 layout was adopted) — separate cleanup.TException::__wakeup()parity withTBaseand the dead-codeTException::$tmethodlocal copy — pre-existing inconsistencies on master, deserve their own behavioural-change ticket.Undefined array key 1warning atTSocketPoolTest:210from a mock data-provider mismatch — unrelated to PSR-12.Test plan
vendor/bin/phpcsoverlib/php/lib,lib/php/test, andlib/php/test/Resources/packages/(435 fixture files) — exits 0 with zero output.vendor/bin/phpstan analyse -c lib/php/phpstan.neon— no issues.vendor/bin/phpunit -c lib/php/phpunit.xml— 629 tests pass, 0 errors, 0 failures (one unrelated pre-existing warning atTSocketPoolTest.php:210).make stubsidempotent — diff-free on second run.git diff compiler/cpp/src/thrift/generate/limited tot_php_generator.cc; no other language generators affected (thet_generator.ccbase is untouched).lib-phpmatrix (PHP 8.1 / 8.2 / 8.3 / 8.4 / 8.5) is green.lib-phpstatic-code-analysis (phpcs+phpstan) is green.cross-testjobs that exercise PHP-as-server with the accelerated binary protocol (cpp, go, rs, java, py, nodejs, nodets, rb clients) are green — required the C extensiontspeclookup fix.Checklist
Breaking-Changelabel.[skip ci]is not used.