Reputation: 451
PHP 8 has introduced an amazing code fallback tool, Null Safe Operator, eg:
$country = $session?->user?->getAddress()?->country;
It prevents you create a lot of comparisons of all whole object tree, Null Coalesce Operator not plays well here (PHP 7.x or Earlier) beacuse above code has an method which will throw an exception because their main class is null. Here, Null Safe Operator prevents an exception.
Well, there are some hack method to emulate this behaviour into earlier versions of PHP (<= 7.X)?
Fallback to some generic class with magic methods where ever returns null can be handful.
Upvotes: 1
Views: 677
Reputation: 76454
I don't know of such a tool in existence, but you can implement one yourself.
Like (untested)
function handleNullSafe($root, $chain, $default) {
if (!$root) {
return $default;
} else {
$element = $root;
for ($index = 0; $index < count($chain); $index++) {
$current = $chain[$index];
//Data member
if (!is_array($current)) {
if ((!$element) || (!isset($element->{$chain[$index]}))) {
return $default;
} else {
$element = $element->{$chain[$index]};
}
//Array or function
} else {
$current = $chain[$index][0]; //The name
$type = $chain[$index][1]; //The type
$params = $chain[$index][2]; //The params or indexes
if ($type === 'array') {
//Get the member
$element = $element->{$current};
foreach ($params as $param) {
//exists
if (is_array($element) && isset($element[$param])) {
$element = $element[$param];
//does not exist
} else {
return $default;
}
}
//function
} else {
//null or not an object
if ((!$element) || (!is_object($element))) {
return $default;
} else {
$element = call_user_func_array(array($element, $current), $params);
}
}
}
}
return $element;
}
}
The idea is that we have a root and a chain of items. In our chain we may have data members, arrays indexed or functions.
So handleNullSafe
is to be called by passing whatever the $root
may be, possibly passing something like $root ?? null
if we are unsure about $root
being a properly initialized value. $chain
is an array of elements. If it's a simple data-member, then we pass its name, otherwise we pass an array for it, specifying the 1. name, 2. type (array or function), 3. parameters/indexes. So your
$country = $session?->user?->getAddress()?->country;
would become
$country = handleNullSafe(
$session ?? null,
'user'
['getAddress', 'function', [/*here you could pass the parameters, in this case it's empty, as you did not pass anything to getAddress*/]],
'country'
);
I admit, I did not test this code, but I'm pretty sure that the idea is correct, please do let me know if there is anything wrong, typos, bugs, anything.
Upvotes: 0
Reputation: 11
You might be interested in Error Control Operator (@ at sign).
Your expression turns to:
$country = @$session->user->getAddress()->country;
If any property in path is null or is causing error, the result will remain null
.
Exceptions will not be triggered when expression marked with @
.
So be careful of using it because it might cover an important misbehavior.
Upvotes: 0
Reputation: 26699
To emulate null safe operator, you can take inspiration from the option type. The idea is simple - you wrap the value in an object, as you suggested, and have a magic method handling. Now, the magic method will either return $this
- e.g. the same Option instance, if this is already a null, or call the method and wrap the result in an Option, to allow further chaining.
The challenge you face with PHP will be where to terminate, e.g. where to return the original value, and not the wrapper. If you can afford an explicit method call at the end of the chain, it becomes straightforward.
It would look something like (not tested, written for illustrative purposes)
class Option {
protected $value;
public function __construct($value)
{
$this->value = $value;
}
public function __call($methodName, $args) {
if (is_null($this->value)) {
return $this;
}
return new Option($this->value->$methodName($args));
}
public function __get($propertyName) {
if (is_null($this->value)) {
return $this;
}
return new Option($this->value->$propertyName);
}
public function get() {
return $this->value;
}
}
So you will do:
$country = new Option($session)->user->getAddress()->country->get();
Upvotes: 1
Reputation: 451
We can create a black holed class to instead of throw an exception return Null if we call an undefined method of a generic class which will acts as our fallback.
<?php
// _7 - for brevity write and 7 is closer to question mark ;)
class _7 {
public function __call($method, $args) return null;
}
$myDate = (\DateTime::createFromFormat('d/m/Y','05/04/1989') ?: new _7)->format('Y-m-d') ?? 'Bad date';
//'1989-04-05' - because expected date format is provided to Datetime
$myDate = (\DateTime::createFromFormat('d/m/Y','1989') ?: new _7)->format('Y-m-d') ?? 'Bad date';
//'Bad date' - Very cool! No exceptions here, successfull fallback;
Important! This approach only works with PHP >= 7.0, i will collect info to work with 5.x soon as possible.
Upvotes: 0