Operators: Null Safe

The null-safe operator allows you to avoid the tedious checks for object null-ness before calling a method or property on that object.

The operator is:

?->

It has the same semantics as the normal -> operator, except provides a null-case backout for the object in question.

Let's say you have a call like this:

$obj?->foo($x, $y);

If $obj is null, then instead of throwing a fatal error, the above expression will evaluate to null; otherwise, things work the same as if you used the normal -> operator.

Calling Methods

You can use the null-safe operator to call methods on a class.

<?hh

namespace Hack\UserDocumentation\Operators\NullSafe\Examples\CallingMethods;

class Bar {
  public function baz(): int {
    return 5;
  }
}

function get_Bar(): ?Bar {
  if (rand(0, 10) === 5) {
    return null;
  }
  return new Bar();
}

function foo(): ?int {
  $b = get_Bar();
  // Use the null-safe operator to access a method of class.
  return $b?->baz();
}


var_dump(foo());
Output
int(5)

Accessing Properties

You can use the null-safe operator to access properties.

IMPORTANT NOTE: The null-safe operator can only be used for read access to properties. Write access is forbidden.

<?hh

namespace Hack\UserDocumentation\Operators\NullSafe\Examples\AccessingProps;

class Bar {
  // This uses constructor parameter promotion
  public function __construct(public string $str = "") {}
}

function get_Bar(): ?Bar {
  if (rand(0, 10) < 5) {
    return null;
  }
  return new Bar("Hello");
}

function foo(): ?string {
  $b = get_Bar();
  // Use the null-safe operator to access a proprety of a class.
  return $b?->str;
}


var_dump(foo());
Output
string(5) "Hello"

Gotchas

The null-safe operator can really save time and code space, and allow for the typechecker to be able to understand possible null scenarios on method calls and property access, but there are some things to keep in mind.

Function Return Types

You do have to make sure that any function in which you use this operator on a return-type call that your function is typed as returning a nullable.

<?hh

namespace Hack\UserDocumentation\Operators\NullSafe\Examples\FuncReturnTypes;

class Bar {
  public function baz(): int {
    return 5;
  }
}

function get_Bar(): ?Bar {
  if (rand(0, 10) < 5) {
    return null;
  }
  return new Bar();
}

function foo(): ?int {
  $b = get_Bar();
  return $b?->baz();
}

var_dump(foo());
Output
NULL

Undefined Methods

If the object used with the null-safe operator tries to call a method that doesn't exist, and that object is not null, you will get a fatal error at runtime. The typechecker will raise an error whether the object is null or not.

<?hh

namespace Hack\UserDocumentation\Operators\NullSafe\Examples\UndefinedMethods;

class Bar {
  public function baz(int $x): int {
    return $x + 5;
  }
}

function get_Bar(): ?Bar {
  if (rand(0, 10) < 5) {
    return null;
  }
  return new Bar();
}

function foo(): ?int {
  $b = get_Bar();
  // foobar does not exist; this could raise a fatal if $b is not null
  // This will raise a typechecker error regardless.
  return $b?->foobar(6);
}

var_dump(foo());
Output
Fatal error: Call to undefined method Hack\UserDocumentation\Operators\NullSafe\Examples\UndefinedMethods\Bar::foobar() in /data/users/joelm/user-documentation/guides/hack/45-operators/03-null-safe-examples/undefined-methods.php.type-errors on line 22

No Short Circuit

Even if the object is null, any arguments passed to the function call will still be evaluated. In other words, there is no short-circuit process that causes all evaluation to stop if the object is null.

<?hh

namespace Hack\UserDocumentation\Operators\NullSafe\Examples\NoShortCircuit;

class Bar {
  public function baz(int $x): int {
    return $x + 5;
  }
}

function get_Bar(): ?Bar {
  if (rand(0, 10) < 5) {
    return null;
  }
  return new Bar();
}

function foo(): ?int {
  $x = 4;
  $b = get_Bar();
  // Even if $b ends up being null, $x will be incremented to 5
  $y = $b?->baz($x++);
  var_dump($x);
  return $y;
}

var_dump(foo());
Output
int(5)
int(9)

Not null or Object

Of course, if the variable on which the null-safe operator is being used is not null and not an object, then that is a runtime and typechecker error.

Not Nullable

The object on which the null-safe operator is being used itself should be nullable. This makes it so people are careful using this operator and not just throwing it around without any thought about it.

<?hh

namespace Hack\UserDocumentation\Operators\NullSafe\Examples\NotNullable;

class Bar {
  public function baz(): int {
    return 5;
  }
}

function get_Bar(): Bar {
  return new Bar();
}

function foo(): ?int {
  $b = get_Bar();
  // $b cannot be null here. so the typechecker will complain about using the
  // operator
  return $b?->baz();
}

var_dump(foo());
Output
int(5)

No Property Writes

You cannot use the null-safe operator to try to write to properties. It is read-only.