Skip to content

Editor, Themes, I18N: Use array_last() to read the last element of an array - #67

Draft
mukeshpanchal27 wants to merge 1 commit into
trunkfrom
perf/php85-array-last
Draft

Editor, Themes, I18N: Use array_last() to read the last element of an array#67
mukeshpanchal27 wants to merge 1 commit into
trunkfrom
perf/php85-array-last

Conversation

@mukeshpanchal27

@mukeshpanchal27 mukeshpanchal27 commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Summary

Core reaches for the last element of an array by computing its length and subtracting one:

$last = $array[ count( $array ) - 1 ];

PHP 8.5 added array_last() for
exactly this, and core has shipped a polyfill for it in wp-includes/compat.php since
6.9.0. On PHP 8.5+ the native call replaces a userland function call, an integer op and a
hash lookup with a single VM handler.

The existing form has two further problems beyond speed:

  1. It is only correct for lists. count() - 1 assumes keys are a gapless 0..n-1
    sequence. array_last() is correct for any array because it resolves the real last key.
    Every site changed here happens to hold a list today, but the assumption is invisible
    and easy to break — anything that unset()s an element upstream silently starts reading
    the wrong index, or emits an undefined-index warning.
  2. It states the wrong intent. count( $x ) - 1 describes an index calculation;
    array_last( $x ) describes the thing actually wanted.

array_first()/array_last() are already used elsewhere in core
(class-wp-walker.php:237, formatting.php:4580,
class-wp-rest-widget-types-controller.php:507), so this follows an established convention
rather than introducing one.

Sites changed

File Line Notes
src/wp-includes/class-wp-block-parser.php 345 Hottest. add_inner_block() — once per inner block, on every block parse.
src/wp-includes/class-wp-theme-json.php 3997 get_styles_for_block() element-name resolution.
src/wp-includes/class-wp-theme-json.php 4691 Same, in the pseudo-selector path.
src/wp-includes/pomo/plural-forms.php 131, 166 Both are inside while loopscount() was re-evaluated on every iteration of the shunting-yard operator stack.
src/wp-admin/includes/ajax-actions.php 131 wp_ajax_ajax_tag_search().
src/wp-trackback.php 53 Trackback post-ID extraction.

Hottest site

 public function add_inner_block( WP_Block_Parser_Block $block, $token_start, $token_length, $last_offset = null ) {
-	$parent                       = $this->stack[ count( $this->stack ) - 1 ];
+	$parent                       = array_last( $this->stack );

Loop sites

In plural-forms.php the call sits in the body of while ( ! empty( $stack ) ), so the
count() ran once per operator popped:

 while ( ! empty( $stack ) ) {
-	$o2 = $stack[ count( $stack ) - 1 ];
+	$o2 = array_last( $stack );

Related restructure — block-supports/states.php:386

This one is an assignment target, so array_last() cannot apply. It is instead
restructured to build the rule and append it once, which removes the count() and a
re-index write back into a by-reference array:

-	$css_rules[] = array(
+	$css_rule = array(
 		'state'        => $state,
 		'selector'     => $selector,
 		'declarations' => $declarations,
 	);
 	if ( ! empty( $rules_group ) ) {
-		$css_rules[ count( $css_rules ) - 1 ]['rules_group'] = $rules_group;
+		$css_rule['rules_group'] = $rules_group;
 	}
+
+	$css_rules[] = $css_rule;

Behaviour notes

  • Empty arrays. array_last( array() ) returns null, whereas
    $array[ count( $array ) - 1 ] on an empty array reads index -1 and emits an
    undefined-array-key warning while evaluating to null. The new form is strictly
    better-behaved. At every site changed here the array is already known non-empty
    (explode() always yields ≥1 element; the pomo sites are guarded by
    while ( ! empty( $stack ) ); the parser stack is non-empty by construction), so no
    observable behaviour changes.
  • pomo/ and polyfill availability. pomo/ is loaded outside the usual bootstrap in
    some tooling, so polyfill availability was checked before using it there: pomo/po.php
    already calls str_contains(), str_starts_with() and str_ends_with() (lines 121, 521,
    524), all polyfilled in the same compat.php. Using array_last() in pomo/ therefore
    carries no new dependency.

Testing instructions

  1. vendor/bin/phpunit --filter '(PluralFormsTest|Tests_Blocks_wpBlockParser)'
  2. vendor/bin/phpunit --filter 'ThemeJson'
  3. vendor/bin/phpunit tests/phpunit/tests/block-supports/
  4. Confirm plural translation forms still resolve for a locale with a complex rule
    (e.g. Russian, nplurals=3).
  5. Confirm nested blocks still parse: open a post containing a Group with inner blocks and
    check innerBlocks structure is unchanged.
  6. Confirm hover/focus state styles still emit inside a media query (states.php path).

Verification done

  • php -l clean on all 6 files.
  • vendor/bin/phpcs: 0 errors. One warning emitted, pre-existing and unrelated —
    plural-forms.php:1 "Class file names should be based on the class name".
  • PHPUnit in the project's Docker environment:
    • --filter '(PluralFormsTest|Tests_Blocks_wpBlockParser)'OK (82 tests, 90 assertions)
    • --filter 'ThemeJson'OK (312 tests, 637 assertions)

… array.

Replaces $array[ count( $array ) - 1 ] with the array_last() function added in
PHP 8.5, for which core has shipped a polyfill in compat.php since 6.9.0.

Besides avoiding the count() call, array_last() is correct for any array rather
than only for gapless lists, and it states the intent directly. Two of the sites
in pomo/plural-forms.php sit inside while loops, where count() was re-evaluated
on every iteration.

The site in block-supports/states.php is an assignment target, so it is instead
restructured to build the rule and append it once, removing both the count() and
a re-index write into a by-reference array.

Props mukesh.
@mukeshpanchal27 mukeshpanchal27 self-assigned this Jul 30, 2026
@mukeshpanchal27 mukeshpanchal27 changed the title Use array_last() to read the last element of an array Editor, Themes, I18N: Use array_last() to read the last element of an array Jul 30, 2026
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.

1 participant