Skip to content

fix: avoid PHP 8.5 null array offset deprecation in memoizers#23399

Open
enricobattocchi wants to merge 1 commit into
trunkfrom
23397-null-array-offset-memoizers
Open

fix: avoid PHP 8.5 null array offset deprecation in memoizers#23399
enricobattocchi wants to merge 1 commit into
trunkfrom
23397-null-array-offset-memoizers

Conversation

@enricobattocchi

Copy link
Copy Markdown
Member

Context

PHP 8.5 introduced a new deprecation, Using null as an array offset is deprecated, use an empty string instead. Yoast's meta memoizers cache their results in a plain array keyed by the indexable's id. When that id is null, the array access triggers the deprecation on every call. An indexable id is null for any indexable that was built but never saved: the fallback indexable that Indexable_Repository::for_current_page() returns (for example while the post editor is loading), and, on non-production sites, every indexable (because indexables are only persisted in production). The result is debug.log being flooded with deprecation notices when editing a post, as reported in #23397.

Summary

This PR can be summarized in the following changelog entry:

  • Fixes a bug where PHP deprecation notices were logged when editing a post on PHP 8.5 and later.

Relevant technical choices:

  • The deprecation is new in PHP 8.5 and only fires when the null key comes from a variable, not a literal (a literal $array[null] is folded to $array[''] at compile time). That is why a quick literal-key test looks clean and the issue was initially hard to reproduce.
  • The root cause is in Meta_Tags_Context_Memoizer::get() and Presentation_Memoizer::get(), which key their cache array by $indexable->id. $indexable->id is null for unsaved indexables.
  • The fix normalises the key once with (int) $indexable->id. A real id is already an integer and is unchanged; a null id becomes 0. Real indexable ids start at 1, so 0 never collides with a real one, and 0 is a valid array key that does not trigger the deprecation. Behaviour is otherwise identical to before.
  • This is a deliberately minimal, behaviour-preserving change. It does not change the pre-existing fact that all unsaved (null-id) indexables share one cache slot; that only matters on non-production sites and is out of scope for this bugfix.
  • No constructor signatures changed and no services were added, so the DI container does not need recompiling.
  • Coverage is held: a unit test was added for each memoizer covering the null-id path.

Test instructions

Test instructions for the acceptance test before the PR gets merged

This PR can be acceptance tested by following these steps:

  • Use a test site running PHP 8.5 (the deprecation does not appear on older PHP versions).
  • In wp-config.php, turn on debug logging by setting both WP_DEBUG and WP_DEBUG_LOG to true.
  • Without this change, open any post in the editor (Posts, then edit a post, or add a new one) and then open wp-content/debug.log. You will see new lines reading PHP Deprecated: Using null as an array offset is deprecated that point at meta-tags-context-memoizer.php and presentation-memoizer.php.
  • With this change applied, empty the log, repeat the same edit-a-post step, and confirm that those deprecation lines no longer appear.
  • Confirm the editor and the post's SEO metabox still load and work normally.
  • Developers can also run the automated tests: composer test -- --filter=Memoizer.

Relevant test scenarios

  • Changes should be tested with the browser console open
  • Changes should be tested on different posts/pages/taxonomies/custom post types/custom taxonomies
  • Changes should be tested on different editors (Default Block/Gutenberg/Classic/Elementor/other)
  • Changes should be tested on different browsers
  • Changes should be tested on multisite

The memoizers serve every indexable type, so it is worth confirming the log stays clean for a post, a page, and a term while viewing them and while editing a post.

Test instructions for QA when the code is in the RC

  • QA should use the same steps as above.

QA can test this PR by following these steps:

  • Same as the acceptance test steps above, using a PHP 8.5 environment with debug logging enabled.

Impact check

This PR affects the following parts of the plugin, which may require extra testing:

  • The meta tags context memoizer and presentation memoizer, which feed front-end meta output (titles, descriptions, canonical, robots, social and schema) and the editor metabox initialisation. Worth a regression check that front-end meta and schema still render correctly for posts, pages, term archives and the home page.

Other environments

  • This PR also affects Shopify. I have added a changelog entry starting with [shopify-seo], added test instructions for Shopify and attached the Shopify label to this PR.
  • This PR also affects Yoast SEO for Google Docs. I have added a changelog entry starting with [yoast-doc-extension], added test instructions for Yoast SEO for Google Docs and attached the Google Docs Add-on label to this PR.

Documentation

  • I have written documentation for this change. For example, comments in the Relevant technical choices, comments in the code, documentation on Confluence / shared Google Drive / Yoast developer portal, or other.

Quality assurance

  • I have tested this code to the best of my abilities.
  • During testing, I had activated all plugins that Yoast SEO provides integrations for.
  • I have added unit tests to verify the code works as intended.
  • If any part of the code is behind a feature flag, my test instructions also cover cases where the feature flag is switched off.
  • I have written this PR in accordance with my team's definition of done.
  • I have checked that the base branch is correctly set.
  • I have run grunt build:images and committed the results, if my PR introduces or edits images or SVGs.

Innovation

  • No innovation project is applicable for this PR.
  • This PR falls under an innovation project. I have attached the innovation label.
  • I have added my hours to the WBSO document.

Fixes #23397

Cast the cache key to int in Meta_Tags_Context_Memoizer::get() and
Presentation_Memoizer::get() so an unsaved indexable (null id) resolves
to a stable integer key instead of triggering the "Using null as an
array offset is deprecated" notice on PHP 8.5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@enricobattocchi enricobattocchi added the changelog: bugfix Needs to be included in the 'Bugfixes' category in the changelog label Jun 24, 2026
@github-actions

Copy link
Copy Markdown

Coverage Report for CI Build 0

Coverage decreased (-0.5%) to 53.325%

Details

  • Coverage decreased (-0.5%) from the base build.
  • Patch coverage: 8 of 8 lines across 2 files are fully covered (100%).
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 67848
Covered Lines: 35945
Line Coverage: 52.98%
Relevant Branches: 16664
Covered Branches: 9121
Branch Coverage: 54.73%
Branches in Coverage %: Yes
Coverage Strength: 44354.73 hits per line

💛 - Coveralls

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Addresses a PHP 8.5 deprecation (“Using null as an array offset is deprecated…”) triggered when memoizer cache arrays are accessed with an unsaved Indexable’s null id, primarily affecting editor loads and other fallback-indexable scenarios.

Changes:

  • Normalize memoizer cache keys by casting $indexable->id to an integer before accessing the cache.
  • Add unit tests covering the null-id memoization path for both memoizers.
  • Expose memoizer internal cache in the presentation memoizer test double to enable assertions.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/memoizers/presentation-memoizer.php Normalizes the cache key in get() to avoid null array offset deprecations.
src/memoizers/meta-tags-context-memoizer.php Normalizes the cache key in get() to avoid null array offset deprecations.
tests/Unit/Memoizers/Presentation_Memoizer_Test.php Adds unit coverage for caching behavior when the Indexable id is null.
tests/Unit/Memoizers/Meta_Tags_Context_Memoizer_Test.php Adds unit coverage for caching behavior when the Indexable id is null.
tests/Unit/Doubles/Memoizers/Presentation_Memoizer_Double.php Adds get_cache() helper for inspecting memoizer cache during tests.
Comments suppressed due to low confidence (2)

src/memoizers/presentation-memoizer.php:73

  • get() now normalizes the cache key with (int) $indexable->id, but clear() still unsets using the raw $indexable->id. If an Indexable has a null id, this can still trigger the same "null array offset" deprecation, and it also won’t clear the cache entry that was stored under key 0 by get(). Consider normalizing the key in clear() as well (for the Indexable branch).
		return $this->cache[ $cache_key ];
	}

	/**
	 * Clears the memoization of either a specific indexable or all indexables.

src/memoizers/meta-tags-context-memoizer.php:164

  • get() now memoizes under (int) $indexable->id, but clear() still unsets using the raw $indexable->id and passes it to Presentation_Memoizer::clear(). If the Indexable id is null, this can still trigger the PHP 8.5 deprecation and (more importantly) Presentation_Memoizer::clear( null ) currently clears the entire presentation cache, which is likely unintended. Normalizing the id to the same integer key in clear() avoids both issues.
		return $this->cache[ $cache_key ];
	}

	/**
	 * Clears the memoization of either a specific indexable or all indexables.

Comment on lines +24 to +28
/**
* Used to retrieve the internal cache for testing purposes.
*
* @return array<int|string, mixed> The cache.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog: bugfix Needs to be included in the 'Bugfixes' category in the changelog

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PHP Deprecated: Using null as an array offset is deprecated warnings for PHP 8.5

2 participants