Skip to content

[PHP 8.6] Test scaffolding and deprecation-clean audit - #226

Merged
lisachenko merged 2 commits into
masterfrom
claude/php86-220-test-scaffolding
Aug 25, 2026
Merged

[PHP 8.6] Test scaffolding and deprecation-clean audit#226
lisachenko merged 2 commits into
masterfrom
claude/php86-220-test-scaffolding

Conversation

@lisachenko

Copy link
Copy Markdown
Member

Refs #220 (part of epic #219).

Establishes the PHP 8.6 test scaffolding, audits the code base against the "Deprecations for PHP 8.6" RFC batch, and makes the experimental 8.6 CI row readable. No library behaviour changes — src/ is untouched.

1. Stub / guard scaffolding

  • tests/Stub/FileWithClasses86.php — baseline 8.6 stub (interface, trait, abstract class, final class with a promoted property and a variadic union-typed method, backed enum, namespaced function and constant). It deliberately uses only syntax that is already valid and parseable, so per-feature 8.6 stubs (PFA, readonly defaults, #[\Override] on constants, …) can be added later as separate FileWith<Feature>86.php files by their own tickets.
  • AbstractTestCase::getFilesToAnalyze() — yields the stub behind PHP_VERSION_ID >= 80600. The guard is load-bearing: the parity data providers include_once every stub they yield, so a *86.php stub must never reach an older runtime.
  • tests/Php86ParsingTest.php — proves the wiring in both directions. The static assertions (file parsing, class/enum discovery, methods, parameters, types, constants, traits) run on every runtime, because the engine analyzes sources without loading them; the native-reflection parity test is guarded with PHP_VERSION_ID >= 80600, and the "reflect by name without loading" test is guarded the other way, since the stub is loaded by the parity providers on 8.6.
  • CLAUDE.md — documents both conventions for future contributors.

On 8.6 the stub also flows through the whole existing parity matrix, which is where the extra ~1090 tests come from.

2. Deprecation audit

Checked src/ and tests/ against the accepted items of the Deprecations for PHP 8.6 RFC: is_double()/is_integer()/is_long()/doubleval(), return from finally, list(), let/is/readonly/_ as identifiers, namespace as a class constant, strcoll(), SORT_LOCALE_STRING, metaphone(), spl_classes(), spl_object_hash(), SplFileObject CSV methods, mysqli_stmt_init()/mysqli_get_charset(), case-insensitive define(), is_a()/is_subclass_of() with a string subject and allow_string = false, objects passed to array_walk()/array_walk_recursive()/mb_convert_variables()/http_build_query()/deflate_init()/inflate_init(), session_set_save_handler() without create_sid()/validateId(), ArrayIterator inheriting ArrayObject implementations, and the Reflection items (ReflectionProperty::setValue()/setRawValue() with wrong types, ReflectionMethod::invoke()/invokeArgs() with an object for a static method).

Result: no hits. Details worth recording:

  • The three finally blocks in the code base (src/Resolver/TypeExpressionResolver.php:96, src/Resolver/NodeExpressionResolver.php:190, tests/ConstantExpressionClosuresTest.php:217) only perform cleanup and do not return.
  • The single is_subclass_of() call (src/Traits/ReflectionClassLikeTrait.php:853) passes an object, not a string, so it is unaffected.
  • ReflectionProperty::setValue() (src/ReflectionProperty.php:763) already normalises its arguments to null/object before delegating to the parent, so it cannot trigger the new wrong-type deprecation.
  • All define() usages in the stubs are two-argument calls.

Verified empirically as well: the full suite on 8.6 with --fail-on-deprecation --fail-on-notice --fail-on-warning --display-deprecations reports nothing (phpunit.xml.dist already sets error_reporting=E_ALL).

3. CI

The experimental 8.6 job was verified against the last master run (run 32818096739): both 8.6 rows install via setup-php and really execute the suite on the beta (--ignore-platform-req=php+ correctly relaxes only the upper platform bound). Nothing was broken, so the changes are cosmetic but do address the "surface the result clearly" part of the ticket:

  • The job name now reads PHPUnit tests (PHP 8.6, highest deps) [experimental] instead of PHPUnit tests (8.6, highest, ubuntu-latest, true).
  • Because continue-on-error reports a failing experimental row as successful in the checks list, the outcome of the test step is now written to the run summary for experimental rows.
  • The 8.6 rows keep continue-on-error: true until GA, as required.

Local results

Check Result
vendor/bin/phpunit (PHP 8.5.9) OK — 13749 tests, 15424 assertions, 134 skipped, 2 incomplete (baseline: 13743 / 133 / 2)
php8.6 vendor/bin/phpunit (PHP 8.6.0beta1) OK — 14842 tests, 16608 assertions, 145 skipped, 3 incomplete; also green with --fail-on-deprecation --fail-on-notice --fail-on-warning
vendor/bin/phpstan analyse src --no-progress (level 10) [OK] No errors

Finding for a follow-up (not fixed here — out of scope)

While drafting the stub, an unrelated pre-existing parity gap surfaced in ReflectionMethod::getPrototype() (src/ReflectionMethod.php:281). It searches the parent class first and does not resolve transitively, so for

interface I { public function describe(): string; }
abstract class A implements I { abstract public function describe(): string; }
final class C extends A { public function describe(): string { return ''; } }

it returns A while native reflection returns I. This is not 8.6-specific and would be a behaviour change in src/, so the stub was written to avoid the construct instead; it deserves its own ticket.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Sy8BcM8ivUEpu8uVm7wADn


Generated by Claude Code

claude added 2 commits August 25, 2026 09:53
Establishes the "FileWith*86.php" stub naming convention together with the
"PHP_VERSION_ID >= 80600" guard used for runtime-only assertions, mirroring
how the 8.5 stubs are wired into the test suite.

- tests/Stub/FileWithClasses86.php: baseline 8.6 stub using only syntax that
  is already valid and parseable, so per-feature 8.6 stubs can follow later
- AbstractTestCase::getFilesToAnalyze(): yields the stub only on PHP 8.6+,
  because the data providers include_once every stub they yield
- tests/Php86ParsingTest.php: proves the wiring - static analysis assertions
  run on every runtime, native reflection parity is guarded by the version
  check, so the suite stays green on PHP 8.5
- CLAUDE.md: documents the stub and guard conventions

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sy8BcM8ivUEpu8uVm7wADn
The matrix job name now spells out the PHP version, the dependency set and
the experimental flag instead of rendering as an unlabelled tuple, and the
outcome of the test step is written to the run summary for experimental
rows, whose failures are otherwise hidden by continue-on-error.

The 8.6 rows keep continue-on-error until PHP 8.6 reaches GA.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sy8BcM8ivUEpu8uVm7wADn
@lisachenko
lisachenko marked this pull request as ready for review August 25, 2026 10:24
@lisachenko
lisachenko merged commit 8bb445c into master Aug 25, 2026
7 checks passed
@lisachenko
lisachenko deleted the claude/php86-220-test-scaffolding branch August 25, 2026 13:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants