87

I've just seen a video about upcoming PHP 7.4 features and saw new ??= operator. I already know the ?? operator. How's this different?

0

7 Answers 7

77

From the docs:

Coalesce equal or ??=operator is an assignment operator. If the left parameter is null, assigns the value of the right parameter to the left one. If the value is not null, nothing is done.

Example:

// The following lines are doing the same
$this->request->data['comments']['user_id'] = $this->request->data['comments']['user_id'] ?? 'value';
// Instead of repeating variables with long names, the equal coalesce operator is used
$this->request->data['comments']['user_id'] ??= 'value';

So it's basically just a shorthand to assign a value if it hasn't been assigned before.

Sign up to request clarification or add additional context in comments.

2 Comments

it's not 100% exact that the 2 lines are doing "the same", in the second case the left-hand side is only evaluated once, so it's more efficient
'If the left parameter is null, assigns (...)'. Don't you think it should be corrected to: 'If the left parameter is null OR is not set, assigns (...)' ?
12

Null coalescing assignment operator chaining:

$a = null;
$b = null;
$c = 'c';

$a ??= $b ??= $c;

print $b; // c
print $a; // c

Example at 3v4l.org

3 Comments

That example can be handled easily by the null coalescing operator ?? because all variables are defined. The point of the null coalescing assignment operator is that it will work with undefined variables. So you can remove the first two lines which assign nulls to $a and $b and the code will still work.
This comes right out of hell but you helped me to understand messy code I can now clean up :D
Note: If $a is set, this will not set $b, which might be a problem. Do not chain null coalescing assignment operators!
10

The null coalescing assignment operator is a shorthand way of assigning the result of the null coalescing operator.

An example from the official release notes:

$array['key'] ??= computeDefault();
// is roughly equivalent to
if (!isset($array['key'])) {
    $array['key'] = computeDefault();
}

Comments

4

Example Docs:

$array['key'] ??= computeDefault();
// is roughly equivalent to
if (!isset($array['key'])) {
    $array['key'] = computeDefault();
}

Comments

2

It can roughly be translated as "$a defaults to $b", like this:

$page ??= 1;            //  If page is not specified, start at the beginning
$menu ??= "main";       //  Default menu is the main menu
$name ??= "John Doe";   //  Name not given --> use John Doe

A long-awaited tool in the world of PHP.
Before PHP 7.4, we did this with a function:

function defaultOf(&$var, $value) {
    if(is_null($var)) $var=$value;
}

// Now the 3 statements above would look like:

defaultOf( $page, 1 );
defaultOf( $menu, "main" );
defaultOf( $name, "John Doe" );

(I still use it because it's more readable.)

Comments

0

You can use this to initialize variables during a loop's first iteration. But beware!

$reverse_values = array();
$array = ['a','b','c']; // with [NULL, 'b', 'c'], $first_value === 'b'
foreach($array as $key => $value) {
  $first_value ??= $value; // won't be overwritten on next iteration (unless 1st value is NULL!)
  $counter ??= 0; // initialize counter
  $counter++;
  array_unshift($reverse_values,$value);
}
// $first_value === 'a', or 'b' if first value is NULL
// $counter === 3
// $reverse_values = array('c','b','a'), or array('c','b',NULL) if first value is null

If the first value is NULL, then $first_value will be initialized to NULL and then overwritten by the next non-NULL value. If the array has a lot of NULL values, $first_value will end up either as NULL or the first non-NULL after the last NULL. So this seems like a terrible idea.

I would still prefer doing something like this mainly because it's more clear, but also because it works with NULL as an array value:

$reverse_values = array();
$array = ['a','b','c']; // with [NULL, 'b', 'c'], $first_value === NULL
$counter = 0;
foreach($array as $key => $value) {
  $counter++;
  if($counter === 1) $first_value = $value; // does work with NULL first value
  array_unshift($reverse_values,$value);
}

Comments

-1

Null Coalescing Assignment ??= means return the value, but if it's null then assign it a value first.

Without ??=

if ($a === null) {
    $a = 1;
}
return $a;

With ??=

return $a ??= 1;

In both of the examples above:

  • if $a was null, change it to 1
  • null is never returned

The main use of this operator is to provide a default value in case of null.


Example use case for this Null Coalescing Assignment operator in 2024:

<?php

namespace App\Pages;

use App\Entities\User;

class WelcomePage extends AbstractPage
{
    protected User $user;
    
    protected function setUser(User $user): static
    {
        // assign the value (always) and then return it
        return $this->user = $user;
    }

    protected function getUser(): User
    {
        // Null Coalescing Assignment:
        // assign the value (only if it's null) and then return it
        // else just return it
        return $this->user ??= new User("Anonymous");
    }

    public function display(): string
    {
        return $this->getUser()->loggedIn() ?
            "<h1>Welcome {$this->getUser()->name}! :D</h1>" :
            '<h1>Welcome! :)</h1><a href="/login">log in</a>';
    }
}

In this example, App\Pages\WelcomePage is an extension of the App\Pages\AbstractPage class which contains all of the common functions of pages in our app.

This particular page needs to get the current User as an object and display the name, but only if logged in. This page is not responsible for managing the login state of the user - that's handled elsewhere. The getUser() function is going to try to read the $user property of our class, but if the property is not initialised, because we haven't called setUser() yet, it will first initialise it with new User("Anonymous") and then return the value of the property. Subsequent calls to getUser() will not need to initialise the property.

We can use this WelcomePage class later in some other file like this for anonymous users:

$page = new WelcomePage();
echo $page->display();

Or like this for logged-in users:

$page = new WelcomePage();
echo $page->setUser($loggedInUser)->display();

To extend this further, we could move $user and its getter and setter into the AbstractPage so that all of our pages can extend it and have $user initialised automatically, but only when we want to use it and not more than once.

If we didn't have this operator, we would have to rewrite this function:

    protected function getUser(): User
    {
        if (!isset($this->user)) {
            $this->user = new User("Anonymous");
        }
        return $this->user;
    }

Or use a simple getter:

    protected function getUser(): User
    {
        return $this->user; // can cause an error if not initialised
    }

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.