Bump minimum PHP requirement from 7.2 to 7.4#2469
Conversation
Updates the composer require constraint and the config.platform.php override so dependencies resolve against PHP 7.4 as the floor. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Aligns the PHPCompatibility testVersion with the new minimum PHP version, so syntax newer than 7.4 stays disallowed but 7.4-only constructs (typed properties, arrow functions) no longer flag. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Updates the WordPress plugin metadata across all bundled plugins so WordPress will refuse to activate them on hosts below the new floor. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The plugin no longer supports anything below PHP 7.4, so the two older matrix legs only cost CI time without protecting users. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The PHP 8 polyfill stubs are still required (those functions appeared in PHP 8.0), but the rationale comment incorrectly cited 7.2. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
PHP 7.4 changed array_merge() to accept zero arguments (returning an empty array), so the explicit early-return when links_by_rel is empty is no longer necessary. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…edoc The 'JS;' closer can now sit at the same indent as its opener and PHP will strip that prefix from each body line, dropping the obsolete PHP 7.2 indentation note in the process. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
WPCS (Squiz.Commenting.VariableComment.MissingVar) requires every class property docblock to carry an @var tag, while Slevomats PropertyTypeHint.UselessAnnotation flags any @var that just repeats the native type. Once we start adding native typed properties (PHP 7.4+) the two sniffs collide on every plain bool/int/string property, so excluding the Slevomat sub-sniff lets the WordPress convention win. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Now that PHP 7.4 is the minimum, declare native types on the classs nine private properties; the existing @var docblocks stay to carry PHPStan-level subtypes (non-empty-string[], non-negative-int, array shapes) that the native type cannot express. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Declares the seven private properties with native types now that PHP 7.4 is the minimum, retaining the @var docblocks so PHPStan still sees the bounded-int and array-shape subtypes. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Make $elements nullable so the existing lazy-init pattern continues to work with a typed property; null replaces the prior is_array check, which would always be true for a non-nullable array property. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
OD_Tag_Visitor_Registry, OD_Link_Collection, and OD_Visited_Tag_State each have a single property; bundling the conversions in one commit since the change in each file is mechanical and trivial. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Convert the three private/protected properties across the video and background-image tag visitors. The lazily-populated tuple list keeps its nullable type to preserve "not yet computed" sentinel semantics; copy it to a local before iterating so PHPStan can narrow off null. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Type all four straightforward properties across Perflab_Server_Timing and Perflab_Server_Timing_Metric. The remaining metric $value property keeps an int|float|null docblock (with a phpcs:ignore note) because PHP 7.4 has no native int|float union type; it converts when PHP 8.0 becomes the floor. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Type the five straightforward properties across the registry and the animation class. Four properties remain untyped because PHP 7.4 does not allow callable as a native property type or bool|string unions; each is annotated with phpcs:ignore plus a TODO for the PHP 8.0 follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Drop the enableNativeTypeHint=false suppression now that every class property in tracked plugin source carries a native type (or an inline phpcs:ignore for the few PHP 8.0-only union-type cases). Tests are excluded from the MissingNativeTypeHint sub-sniff so PHPUnit fixtures, mocks, and test data classes do not have to match the strict typing applied to production code -- this matches how other strict sniffs (DiscouragedFunctions, DocComment, etc.) are already excluded for tests in the same ruleset. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…-detective Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…tizer Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…ffloading The WooCommerce check is intentionally left as a closure because its body contains a documentation comment that does not survive the reduction to a single expression. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## trunk #2469 +/- ##
==========================================
- Coverage 69.33% 69.29% -0.05%
==========================================
Files 90 90
Lines 7749 7724 -25
==========================================
- Hits 5373 5352 -21
+ Misses 2376 2372 -4
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
There was a problem hiding this comment.
Pull request overview
Raises the monorepo’s minimum supported PHP version to 7.4 (per #2340), updates tooling/CI/docs to match, and modernizes plugin source to take advantage of PHP 7.4 features (typed properties and arrow functions) while removing now-unnecessary 7.2/7.3 compatibility workarounds.
Changes:
- Bump PHP requirements across Composer, PHPCS (PHPCompatibility testVersion), plugin headers, docs, and CI matrix (dropping PHP 7.2/7.3 legs).
- Re-enable/enforce native property type hints via PHPCS ruleset updates (with targeted exclusions for tests and redundant
@varannotations). - Modernize runtime code with typed properties, null-based lazy init, and arrow functions; retire PHP 7.2/7.3-specific workarounds.
Reviewed changes
Copilot reviewed 42 out of 43 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
composer.json |
Updates required PHP version and Composer platform PHP to 7.4. |
composer.lock |
Reflects updated platform PHP constraints/override and new content hash. |
tools/phpcs/phpcs.ruleset.xml |
Bumps PHPCompatibility to 7.4-, enforces native property types, and adjusts Slevomat exclusions (including tests). |
phpstan.neon.dist |
Updates comment to reflect 7.4 Composer platform setting. |
.github/workflows/php-test-plugins.yml |
Removes PHP 7.2 and 7.3 from the plugin test matrix. |
CONTRIBUTING.md |
Updates stated minimum PHP version to 7.4. |
AGENTS.md |
Updates repository minimum PHP version references to 7.4. |
plugins/auto-sizes/auto-sizes.php |
Updates plugin header “Requires PHP” to 7.4. |
plugins/dominant-color-images/load.php |
Updates plugin header “Requires PHP” to 7.4. |
plugins/embed-optimizer/load.php |
Updates plugin header “Requires PHP” to 7.4. |
plugins/embed-optimizer/class-embed-optimizer-tag-visitor.php |
Adds native typed property for internal state. |
plugins/image-prioritizer/load.php |
Updates plugin header “Requires PHP” to 7.4. |
plugins/image-prioritizer/helper.php |
Converts a single-expression closure to an arrow function. |
plugins/image-prioritizer/class-image-prioritizer-video-tag-visitor.php |
Adds native typed property for internal state. |
plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php |
Converts a single-expression closure to an arrow function. |
plugins/image-prioritizer/class-image-prioritizer-background-image-styled-tag-visitor.php |
Refactors lazy-init state to ?array and adjusts related logic accordingly. |
plugins/optimization-detective/load.php |
Updates plugin header “Requires PHP” to 7.4. |
plugins/optimization-detective/detection.php |
Converts a single-expression closure to an arrow function. |
plugins/optimization-detective/class-od-link-collection.php |
Retires PHP 7.2/7.3 array_merge empty-spread workaround; adds typed property and arrow function. |
plugins/optimization-detective/class-od-html-tag-processor.php |
Adds native typed properties for internal stacks/state. |
plugins/optimization-detective/class-od-element.php |
Adds native typed properties for data and owning URL metric. |
plugins/optimization-detective/class-od-url-metric.php |
Adds native typed properties and refactors lazy-init elements to null sentinel; converts closures to arrow functions. |
plugins/optimization-detective/class-od-url-metric-group.php |
Adds native typed properties; converts closures to arrow functions. |
plugins/optimization-detective/class-od-url-metric-group-collection.php |
Adds native typed properties for collection state/caches. |
plugins/optimization-detective/class-od-template-optimization-context.php |
Adds native typed properties for context fields. |
plugins/optimization-detective/class-od-tag-visitor-context.php |
Adds native typed properties for visitor context fields. |
plugins/optimization-detective/class-od-tag-visitor-registry.php |
Adds native typed property for registry state. |
plugins/optimization-detective/class-od-visited-tag-state.php |
Adds native typed property for internal boolean state. |
plugins/optimization-detective/storage/class-od-url-metric-store-request-context.php |
Adds native typed properties for request context fields. |
plugins/performance-lab/load.php |
Updates plugin header “Requires PHP” to 7.4. |
plugins/performance-lab/includes/server-timing/defaults.php |
Converts a single-expression closure to an arrow function. |
plugins/performance-lab/includes/server-timing/class-perflab-server-timing.php |
Adds typed properties and converts a single-expression closure to an arrow function. |
plugins/performance-lab/includes/server-timing/class-perflab-server-timing-metric.php |
Adds typed properties; leaves union-typed value untyped with PHPCS suppression. |
plugins/speculation-rules/load.php |
Updates plugin header “Requires PHP” to 7.4. |
plugins/speculation-rules/settings.php |
Uses flexible heredoc indentation now that PHP 7.3+ is guaranteed; removes related workaround comment. |
plugins/speculation-rules/class-plsr-url-pattern-prefixer.php |
Adds typed property and converts closure to arrow function. |
plugins/view-transitions/view-transitions.php |
Updates plugin header “Requires PHP” to 7.4. |
plugins/view-transitions/includes/theme.php |
Converts a single-expression closure to an arrow function. |
plugins/view-transitions/includes/class-plvt-view-transition-animation.php |
Adds typed properties where possible; documents/suppresses cases requiring PHP 8 union types or disallowed callable property types. |
plugins/view-transitions/includes/class-plvt-view-transition-animation-registry.php |
Adds typed properties for registry maps. |
plugins/web-worker-offloading/load.php |
Updates plugin header “Requires PHP” to 7.4. |
plugins/web-worker-offloading/third-party.php |
Converts single-expression closures to arrow functions for integration checks. |
plugins/webp-uploads/load.php |
Updates plugin header “Requires PHP” to 7.4. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| 'complete' => $group->is_complete(), | ||
| ); | ||
| }, | ||
| static fn ( OD_URL_Metric_Group $group ): array => array( |
There was a problem hiding this comment.
The array return type hint is overkill.
| static fn ( OD_URL_Metric_Group $group ): array => array( | |
| static fn ( OD_URL_Metric_Group $group ) => array( |
Summary
Fixes #2340
Raises the minimum PHP version across the monorepo from 7.2 to 7.4, drops the two now-stale CI legs, retires workarounds whose comments invited removal at this bump, and opportunistically modernizes plugin source to use PHP 7.4 typed properties and arrow functions.
Floor bump
composer.json:require.phpandconfig.platform.php→^7.4 || ^8.0/7.4.tools/phpcs/phpcs.ruleset.xml:testVersion→7.4-.Requires PHP: 7.4..github/workflows/php-test-plugins.yml: drops7.2and7.3from the unit-test matrix.CONTRIBUTING.md,AGENTS.md,phpstan.neon.distcomment updated to reference 7.4.Retired workarounds
OD_Link_Collection::get_prepared_links()— drops the empty-input guard aroundarray_merge( ...array_map( ... ) )that PHP 7.2/7.3 needed.plugins/speculation-rules/settings.php— heredoc closer is reindented to its enclosing block using PHP 7.3+ flexible heredoc syntax.Modernization
@vardocblocks retained where they carry PHPStan-level subtypes (non-empty-string,int<1, max>, array shapes, etc.) the native type can't express.bool|string,bool|callable,int|float|nullproperties stay un-typed with inlinephpcs:ignore+ a TODO note pointing at the eventual PHP 8.0 floor bump (PHP 7.4 has no native union types andcallableis not allowed as a property type).is_array($prop)sentinel) refactored to?T+null === $prop.tools/phpcs/phpcs.ruleset.xml: removed theenableNativeTypeHint=falsesuppression so the SlevomatCodingStandardPropertyTypeHintsniff now requires native types in source; also excludedUselessAnnotation(so it doesn't fight WPCS's required@var) andMissingNativeTypeHintfor tests (matching how other strict sniffs are already excluded forplugins/*/tests/**).The PR is structured as 30 small logical commits so review and bisection stay easy.
Test plan
php-test-pluginsmatrix shows three base legs (8.1, 8.0, 7.4) instead of fivephp-lint,plugin-check, and PHPStan greencomposer installresolves without re-lockingcomposer phpstan(level 8) greencomposer lint:allgreennpm run test-phppasses the 10 plugin suitesI fed this issue into Claude Code with the prompt:
In plan mode, it came up with the following:
Bump Performance plugin monorepo minimum PHP from 7.2 → 7.4
Context
The Performance Team's repo currently requires PHP 7.2, but tracking issue
#2340 calls for moving the floor to PHP 7.4. WordPress
core's own minimum has already moved past 7.2, so the older floor is buying
nothing in compatibility while costing two CI matrix jobs and blocking use of
typed properties, arrow functions, and the
??=operator.This plan does the floor bump end-to-end: configuration, plugin headers, CI
matrix, lint/static-analysis settings, and docs; removes two now-dead
workarounds whose comments explicitly invite their removal at this version
bump; and opportunistically modernizes the codebase by converting class
properties (well-documented via
@var) to native PHP 7.4 typed properties andcollapsing single-expression closures into arrow functions where the
conversion is unambiguous.
The user has approved an opportunistic modernization pass (not just a
config-only bump), so the plan covers both the version bump and the followup
typing work in a single PR.
Phase 1 — Bump the floor (config, headers, CI, docs)
composer.json"php": "^7.2 || ^8.0"→"php": "^7.4 || ^8.0"config.platform):"php": "7.2"→"php": "7.4"tools/phpcs/phpcs.ruleset.xml<config name="testVersion" value="7.2-"/>→value="7.4-"<property name="enableNativeTypeHint" value="false" /><!-- Only available in PHP 7.4+ -->line so the SlevomatCodingStandardPropertyTypeHintsniff defaults back to requiring native type hints. (See Phase 4 — every class property must gain a native type before this gate is allowed to be the default; the conversion lands in the same PR.)phpstan.neon.distphpstan/php-8-stubsscan entries below still apply (those are PHP 8.0 polyfills — the polyfill need is unrelated to the 7.4 floor).Plugin bootstrap headers —
Requires PHP: 7.2→Requires PHP: 7.4plugins/auto-sizes/auto-sizes.php:7plugins/dominant-color-images/load.php:7plugins/embed-optimizer/load.php:7plugins/image-prioritizer/load.php:7plugins/optimization-detective/load.php:7plugins/performance-lab/load.php:7plugins/speculation-rules/load.php:7plugins/view-transitions/view-transitions.php:7plugins/web-worker-offloading/load.php:7plugins/webp-uploads/load.php:7.github/workflows/php-test-plugins.ymlphp: ['8.1', '8.0', '7.4', '7.3', '7.2']→php: ['8.1', '8.0', '7.4'](drops the two unit-testing jobs the issue calls out). Leave theinclude:block alone — the existingphp: '7.4' + wp: '6.6'and 8.2/8.3/8.4/8.5 trunk entries stay.Documentation
CONTRIBUTING.md:10: "minimum required version right now is 7.2" → "7.4".AGENTS.md:83: "backward compatible with PHP 7.2" → "PHP 7.4".Out of scope for Phase 1
plugins/*/readme.txtfiles have noRequires PHPheader (it's pulled from the .php bootstrap by w.org), so no readme.txt changes needed.composer.jsonfiles exist; only the root one.package.xmlat the repo root is an untracked Xdebug PECL manifest unrelated to this work; ignore.Phase 2 — Remove dead 7.2/7.3 workarounds
Both call sites carry comments that explicitly say the workaround can be retired at the bump.
plugins/optimization-detective/class-od-link-collection.php(around line 130-135)PHP 7.4 changed
array_merge()to allow zero arguments, so the empty-input guard is no longer required. Remove theif ( count( $links_by_rel ) === 0 ) { return array(); }block and the explanatory comment; let the existingarray_merge( ...array_map( ... ) )call run unconditionally.plugins/speculation-rules/settings.php(around line 343-344)PHP 7.3 introduced flexible heredoc/nowdoc indentation. Re-indent the closing
JS;marker to match its enclosing block (two more levels in) and delete the// 👆 This 'JS;' line can only be indented two tabs when minimum PHP version is increased to 7.3+.comment. Also re-indent the heredoc body so it lines up with the new closer (PHP 7.3+ strips the closer's indentation from each line uniformly).Optimization-detective changelog note
The
plugins/optimization-detective/readme.txt:263line "Use PHP 7.2 features in Optimization Detective" is historical changelog content and should be left alone.Phase 3 — Modernization: typed properties
The Slevomat
PropertyTypeHintsniff is being un-suppressed in Phase 1, so every class property in non-test plugin code must gain a native type hint in this PR. The good news from the inventory: 63 of 64 properties already carry single-type@vardocblocks, so this is mechanical.Approach per property
@var Tdocblock.Tisstring,int,bool,float,array,self, a class name, orT|null(which becomes?T), declare it natively:private ?string $foo;orprivate array $bar = array();.Tis a non-null union beyondT|null(e.g.,array|false,string|int) — PHP 7.4 doesn't support these natively; refactor the property to use a single type plus a default that signals the absence (e.g., empty array,0), or leave the property untyped and add an inline phpcs:ignore for that one property with aTODO: PHP 8.0note. Inventory found ~11 such properties — handle them one-by-one in the diff, preferring refactor over suppression where the codepath cleanly allows it.@vardocblock — it stays valuable for PHPStan-level subtypes (e.g.,non-empty-string, array shapes) that the native type can't express.Files in scope (28 classes, 64 properties)
Heaviest:
optimization-detective(16 classes, 45 props). Top conversion targets:plugins/optimization-detective/class-od-html-tag-processor.php— 9 propertiesplugins/optimization-detective/class-od-url-metric-group.php— 7 propertiesplugins/optimization-detective/class-od-url-metric-group-collection.php— 6 propertiesplugins/optimization-detective/class-od-tag-visitor-context.php— 5 propertiesplugins/optimization-detective/class-od-template-optimization-context.php— 5 propertiesclass-od-element.php,class-od-data-validation-exception.php,class-od-link-collection.php,class-od-strict-url-metric.php,class-od-tag-visitor-registry.php,class-od-url-metric.php,class-od-visited-tag-state.php,storage/class-od-storage-lock.php, etc.Other plugins:
view-transitions— 2 classes, 9 propertiesperformance-lab— 2 classes, 5 propertiesimage-prioritizer— 4 classes, 3 propertiesembed-optimizer—class-embed-optimizer-tag-visitor.php— 1 propertyspeculation-rules— 1 class, 1 propertydominant-color-images— no class properties to convertThe 1 property lacking an
@var(uncovered by the inventory) gets typed by reading its assignments in context.Phase 4 — Modernization: arrow functions
Convert single-expression closures
function (...) { return <expr>; }tofn (...) => <expr>where:returnstatement (no other statements, no nested closures).use ($x)clause is needed (arrow functions auto-capture by value, which is a behavior change only when the closure currently captures a reference).static functionalready in a context where staticness matters —static fnis allowed in PHP 7.4 and should be kept.Highest-density files (from inventory):
plugins/performance-lab/— ~18 closure call sites across ~9 filesplugins/optimization-detective/— ~15 across ~8 files (notablystorage/data.php,helper.php,optimization.php)plugins/image-prioritizer/,plugins/speculation-rules/— a handful eachEstimate ~15–25 conversions across the repo. Inspect each individually rather than mechanically — leave multi-statement closures untouched.
The
??=operator: 0 candidates found in the codebase. No work.Commit strategy
Each discrete change lands in its own logical commit, in this order, on the
update/php-74branch:composer.jsonPHP requirement to ^7.4 || ^8.0 —require.phpandconfig.platform.php. Runcomposer update --lockonly if the lock file legitimately changes; otherwise omit.testVersionto 7.4- — single edit intools/phpcs/phpcs.ruleset.xmlline 6.Requires PHPto 7.4 — all 10 plugin bootstrap files in one commit (single mechanical change across the monorepo)..github/workflows/php-test-plugins.yml.plugins/optimization-detective/class-od-link-collection.php. (Phase 2.)plugins/speculation-rules/settings.php. (Phase 2.)PropertyTypeHintnative type requirement — remove theenableNativeTypeHint=falseline fromtools/phpcs/phpcs.ruleset.xml. This commit must come after all property typing is in place; ordering it last in the typing-related commits keepscomposer lint:allgreen at every commit.Commit messages follow the existing repo style (imperative present tense, no Anthropic Co-Authored-By trailer unless requested). Group order ensures bisection: every commit leaves the repo in a working, lint-green state.
Defer commit creation until the user explicitly asks to commit; per CLAUDE.md guidance, do not auto-commit.
Phase 5 — Verification
Run locally before opening the PR:
composer install— confirms platform/require change parses.composer phpstan— must stay green at level 8.composer lint:all— must stay green; verifies theenableNativeTypeHintremoval is satisfied (i.e., every property gained its native type).npm run test:php— full PHPUnit suite. Expect any test that exercises thearray_mergeempty-input branch in optimization-detective to still pass (the unconditionalarray_mergereturns the samearray()for empty input on PHP 7.4+).composer test:plugins— per-plugin suites.npm run env:cli -- plugin activate optimization-detective speculation-rules— sanity-check on the wp-env'd WordPress that the two files we edited still load (heredoc reformat in particular).http://localhost:8000/) and visit the front-end to confirm Optimization Detective's link collection and Speculation Rules' admin notice render correctly.CI verification once pushed:
php-test-plugins.ymlshould now show 3 base-matrix jobs (8.1, 8.0, 7.4) instead of 5 — confirms the matrix change took effect.php-lint.ymlshould pass on latest PHP.plugin-check.ymlshould pass on PHP 8.3.Critical files (for quick navigation during execution)
composer.jsontools/phpcs/phpcs.ruleset.xmlphpstan.neon.dist.github/workflows/php-test-plugins.ymlCONTRIBUTING.md,AGENTS.mdplugins/*/load.phpandplugins/auto-sizes/auto-sizes.php,plugins/view-transitions/view-transitions.phpplugins/optimization-detective/class-od-link-collection.php:130-135plugins/speculation-rules/settings.php:343-344plugins/optimization-detective/class-od-*.php