Make WordPress Core

Opened 4 weeks ago

Last modified 3 hours ago

#65823 new enhancement

Code Quality: Use the null coalescing assignment operator (??=)

Reported by: Soean Owned by:
Priority: normal Milestone: 7.2
Component: General Version:
Severity: normal Keywords: has-patch has-unit-tests
Cc: Focuses: coding-standards

Description

Core still contains a number of places using the verbose "assign a default only if not already set" pattern, either as a self-referencing null coalescing assignment or as an isset() guard:

<?php
$args['title'] = $args['title'] ?? '';
if ( ! isset( $metadata['sizes'] ) ) {
        $metadata['sizes'] = array();
}

These can be written with the null coalescing assignment operator (??=):

<?php
$args['title'] ??= '';
$metadata['sizes'] ??= array();

Both forms are functionally identical, but ??= is shorter and states the intent directly.
Notes:

  • ??= requires PHP 7.4, which is already the minimum supported version for WordPress.
  • The operator is already used in core, e.g. in wp-includes/l10n.php and wp-includes/view-config.php, so this only makes existing usage consistent.
  • No behavior change; this is a purely internal simplification.

Change History (11)

This ticket was mentioned in PR #12603 on WordPress/wordpress-develop by @Soean.


4 weeks ago
#1

  • Keywords has-unit-tests added

## Description
Replaces verbose "assign a default only if not already set" patterns with the null coalescing assignment operator (??=):

// Before
$args['title'] = $args['title'] ?? '';

if ( ! isset( $metadata['sizes'] ) ) {
    $metadata['sizes'] = array();
}


// After
$args['title'] ??= '';

$metadata['sizes'] ??= array();

Both forms are functionally identical; ??= is shorter and clearer.

## Why this is safe

  • ??= requires PHP 7.4, which is already the minimum.
  • The operator is already used elsewhere in core (e.g. wp-includes/l10n.php, wp-includes/view-config.php), so this only makes existing usage consistent.
  • No behavior change

Trac ticket: https://core.trac.wordpress.org/ticket/65823

This ticket was mentioned in PR #12889 on WordPress/wordpress-develop by @Soean.


4 weeks ago
#2

## Description

Replaces the null-guard singleton pattern in get_instance() accessors with the null coalescing assignment operator (??=):

// Before
public static function get_instance() {
        if ( null === self::$instance ) {
                self::$instance = new self();
        }

        return self::$instance;
}


// After
public static function get_instance() {
        self::$instance ??= new self();

        return self::$instance;
}

Both forms are functionally identical; ??= is shorter and states the intent directly.

## Why this is safe

  • ??= requires PHP 7.4, which is already the minimum supported version.
  • The operator short-circuits: the right-hand side is only evaluated when the left-hand side is null or not set, so the object is still instantiated exactly once.
  • The operator is already used elsewhere in core, e.g. in wp-includes/l10n.php and wp-includes/view-config.php, so this only makes existing usage consistent.
  • No behavior change.

## Intentionally not changed

WP_Abilities_Registry::get_instance() and WP_Ability_Categories_Registry::get_instance() use the same guard, but their blocks contain additional side effects beyond the assignment:

if ( null === self::$instance ) {
        self::$instance = new self();

        WP_Ability_Categories_Registry::get_instance();
        do_action( 'wp_abilities_api_init', self::$instance );
}

Converting these to ??= would cause the action and the nested registry initialization to run on every call instead of only on the first, so both are left as-is.

Trac ticket: https://core.trac.wordpress.org/ticket/65823

This ticket was mentioned in PR #12911 on WordPress/wordpress-develop by @Soean.


4 weeks ago
#3

## Description

Replacing further isset()/null guards that assign a default value with the null coalescing assignment operator (??=):

// Before
if ( ! isset( $size_data['width'] ) ) {
        $size_data['width'] = null;
}

if ( ! isset( $size_data['height'] ) ) {
        $size_data['height'] = null;
}

if ( ! isset( $size_data['crop'] ) ) {
        $size_data['crop'] = false;
}


// After
$size_data['width']  ??= null;
$size_data['height'] ??= null;
$size_data['crop']   ??= false;

Both forms are functionally identical; ??= is shorter and turns a nine-line block into three lines that read as a single list of defaults.

## Scope

This is deliberately limited to blocks where *every* guard in a contiguous run can be converted, so no mixed styles are left behind within one function:

  • WP_Image_Editor_GD::resize() and WP_Image_Editor_Imagick::resize() — the $size_data defaults (identical blocks in both classes).
  • WP_Date_Query::build_mysql_datetime() — the $datetime defaults.
  • WP_Translation_Controller — the $locale fallback in load_file(), is_textdomain_loaded(), get_files() and has_translation().
  • register_post_status() — the $args defaults.

## Why this is safe

  • ??= requires PHP 7.4, which is already the minimum supported version.
  • ??= and ! isset() test the same condition, so the default is applied in exactly the same cases.
  • Assignment order is preserved everywhere it matters. In register_post_status() the compound guard above the block is left untouched and still runs first, $args->internal ??= false; still follows it, and publicly_queryable is still derived after public has been resolved.
  • In WP_Date_Query::build_mysql_datetime() the preceding array_map( 'absint', $datetime ) turns any existing null into 0, which both isset() and ??= treat as set.
  • No behavior change.

## Not converted

Similar runs in WP_Query::parse_query(), WP_Post_Type::set_props() and WP_Taxonomy::set_props() contain guards that cannot be expressed as ??= — an elseif branch in the first case, compound conditions such as if ( null === $args['show_in_menu'] || ! $args['show_ui'] ) in the other two. Converting only the neighbouring guards would leave two styles interleaved in the same block, so those are left alone.

Trac ticket: https://core.trac.wordpress.org/ticket/65823

This ticket was mentioned in PR #12912 on WordPress/wordpress-develop by @Soean.


4 weeks ago
#4

## Description

Covering one recognizable idiom: initializing a container to an empty array before writing into it.

-if ( ! isset( $wp_meta_boxes[ $page ] ) ) {
-       $wp_meta_boxes[ $page ] = array();
-}
+$wp_meta_boxes[ $page ] ??= array();

Every one of the 58 changed sites has exactly this shape, an isset() guard whose entire body is a single = array(); assignment to the tested expression. There is no per-site judgement involved, which makes the change easy to verify: confirm the rule once, then scan.

## Why this is safe

  • ??= requires PHP 7.4, which is already the minimum supported version.
  • ??= and ! isset() test the same condition, so the array is created in exactly the same cases.
  • Only blocks whose body is a single assignment were converted. Any guard containing further statements was skipped, since ??= would let those statements run unconditionally.
  • Nested initializations keep their order, so parent arrays are still created before their children:
    $wp_meta_boxes                      ??= array();
      $wp_meta_boxes[ $page ]             ??= array();
      $wp_meta_boxes[ $page ][ $context ] ??= array();
    
  • No behavior change.

Trac ticket: https://core.trac.wordpress.org/ticket/65823

#5 @westonruter
4 weeks ago

  • Milestone Awaiting Review7.2

#6 @SergeyBiryukov
4 days ago

In 63443:

Code Modernization: Use the null coalescing assignment operator in singleton accessors.

Replaces the null-guard singleton pattern in get_instance() accessors with the null coalescing assignment operator (??=):

// Before
public static function get_instance() {
	if ( null === self::$instance ) {
		self::$instance = new self();
	}

	return self::$instance;
}


// After
public static function get_instance() {
	self::$instance ??= new self();

	return self::$instance;
}

Both forms are functionally identical; ??= is shorter and states the intent directly.

Why this is safe

  • ??= requires PHP 7.4, which is already the minimum supported version.
  • The operator short-circuits: the right-hand side is only evaluated when the left-hand side is null or not set, so the object is still instantiated exactly once.
  • The operator is already used elsewhere in core, e.g. in wp-includes/l10n.php and wp-includes/view-config.php, so this only makes existing usage consistent.
  • No behavior change.

Intentionally not changed

WP_Abilities_Registry::get_instance() and WP_Ability_Categories_Registry::get_instance() use the same guard, but their blocks contain additional side effects beyond the assignment:

if ( null === self::$instance ) {
	self::$instance = new self();

	WP_Ability_Categories_Registry::get_instance();
	do_action( 'wp_abilities_api_init', self::$instance );
}

Converting these to ??= would cause the action and the nested registry initialization to run on every call instead of only on the first, so both are left as-is.

Developed in https://github.com/WordPress/wordpress-develop/pull/12889.

Props Soean, mukesh27.
See #65823.

@SergeyBiryukov commented on PR #12889:


4 days ago
#7

Thanks for the PR! Merged in r63443.

#8 @SergeyBiryukov
33 hours ago

In 63501:

Code Modernization: Use the null coalescing assignment operator for default value assignments.

Replacing further isset()/null guards that assign a default value with the null coalescing assignment operator (??=):

// Before
if ( ! isset( $size_data['width'] ) ) {
	$size_data['width'] = null;
}

if ( ! isset( $size_data['height'] ) ) {
	$size_data['height'] = null;
}

if ( ! isset( $size_data['crop'] ) ) {
	$size_data['crop'] = false;
}


// After
$size_data['width']  ??= null;
$size_data['height'] ??= null;
$size_data['crop']   ??= false;

Both forms are functionally identical; ??= is shorter and turns a nine-line block into three lines that read as a single list of defaults.

Scope

This is deliberately limited to blocks where every guard in a contiguous run can be converted, so no mixed styles are left behind within one function:

  • WP_Image_Editor_GD::resize() and WP_Image_Editor_Imagick::resize() — the $size_data defaults (identical blocks in both classes).
  • WP_Date_Query::build_mysql_datetime() — the $datetime defaults.
  • WP_Translation_Controller — the $locale fallback in load_file(), is_textdomain_loaded(), get_files() and has_translation().
  • register_post_status() — the $args defaults.

Why this is safe

  • ??= requires PHP 7.4, which is already the minimum supported version.
  • ??= and ! isset() test the same condition, so the default is applied in exactly the same cases.
  • Assignment order is preserved everywhere it matters. In register_post_status() the compound guard above the block is left untouched and still runs first, $args->internal ??= false; still follows it, and publicly_queryable is still derived after public has been resolved.
  • In WP_Date_Query::build_mysql_datetime() the preceding array_map( 'absint', $datetime ) turns any existing null into 0, which both isset() and ??= treat as set.
  • No behavior change.

Not converted

Similar runs in WP_Query::parse_query(), WP_Post_Type::set_props() and WP_Taxonomy::set_props() contain guards that cannot be expressed as ??= — an elseif branch in the first case, compound conditions such as if ( null === $args['show_in_menu'] || ! $args['show_ui'] ) in the other two. Converting only the neighbouring guards would leave two styles interleaved in the same block, so those are left alone.

Developed in https://github.com/WordPress/wordpress-develop/pull/12911.

Follow-up to r63443.

Props Soean, mukesh27.
See #65823.

@SergeyBiryukov commented on PR #12911:


33 hours ago
#9

Thanks for the PR! Merged in r63501.

#10 @SergeyBiryukov
3 hours ago

In 63509:

Code Modernization: Use the null coalescing assignment operator for default value assignments.

Replaces verbose "assign a default only if not already set" patterns with the null coalescing assignment operator (??=):

// Before
$args['title'] = $args['title'] ?? '';

// After
$args['title'] ??= '';

Both forms are functionally identical; ??= is shorter and clearer.

Why this is safe

  • ??= requires PHP 7.4, which is already the minimum.
  • The operator is already used elsewhere in core (e.g. wp-includes/l10n.php, wp-includes/view-config.php), so this only makes existing usage consistent.
  • No behavior change.

Follow-up to r63443, r63501.

Props Soean, mukesh27.
See #65823.

@SergeyBiryukov commented on PR #12603:


3 hours ago
#11

Thanks for the PR! Merged in r63509.

Note: See TracTickets for help on using tickets.