Coalesce function for PHP? - php

Many programming languages have a coalesce function (returns the first non-NULL value, example). PHP, sadly in 2009, does not.
What would be a good way to implement one in PHP until PHP itself gets a coalesce function?

There is a new operator in php 5.3 which does this: ?:
// A
echo 'A' ?: 'B';
// B
echo '' ?: 'B';
// B
echo false ?: 'B';
// B
echo null ?: 'B';
Source: http://www.php.net/ChangeLog-5.php#5.3.0

PHP 7 introduced a real coalesce operator:
echo $_GET['doesNotExist'] ?? 'fallback'; // prints 'fallback'
If the value before the ?? does not exists or is null the value after the ?? is taken.
The improvement over the mentioned ?: operator is, that the ?? also handles undefined variables without throwing an E_NOTICE.

First hit for "php coalesce" on google.
function coalesce() {
$args = func_get_args();
foreach ($args as $arg) {
if (!empty($arg)) {
return $arg;
}
}
return NULL;
}
http://drupial.com/content/php-coalesce

I really like the ?: operator. Unfortunately, it is not yet implemented on my production environment. So I use the equivalent of this:
function coalesce() {
return array_shift(array_filter(func_get_args()));
}

It is worth noting that due to PHP's treatment of uninitalised variables and array indices, any kind of coalesce function is of limited use. I would love to be able to do this:
$id = coalesce($_GET['id'], $_SESSION['id'], null);
But this will, in most cases, cause PHP to error with an E_NOTICE. The only safe way to test the existence of a variable before using it is to use it directly in empty() or isset(). The ternary operator suggested by Kevin is the best option if you know that all the options in your coalesce are known to be initialised.

Make sure you identify exactly how you want this function to work with certain types. PHP has a wide variety of type-checking or similar functions, so make sure you know how they work. This is an example comparison of is_null() and empty()
$testData = array(
'FALSE' => FALSE
,'0' => 0
,'"0"' => "0"
,'NULL' => NULL
,'array()'=> array()
,'new stdClass()' => new stdClass()
,'$undef' => $undef
);
foreach ( $testData as $key => $var )
{
echo "$key " . (( empty( $var ) ) ? 'is' : 'is not') . " empty<br>";
echo "$key " . (( is_null( $var ) ) ? 'is' : 'is not') . " null<br>";
echo '<hr>';
}
As you can see, empty() returns true for all of these, but is_null() only does so for 2 of them.

I'm expanding on the answer posted by Ethan Kent. That answer will discard non-null arguments that evaluate to false due to the inner workings of array_filter, which isn't what a coalesce function typically does. For example:
echo 42 === coalesce(null, 0, 42) ? 'Oops' : 'Hooray';
Oops
To overcome this, a second argument and function definition are required. The callable function is responsible for telling array_filter whether or not to add the current array value to the result array:
// "callable"
function not_null($i){
return !is_null($i); // strictly non-null, 'isset' possibly not as much
}
function coalesce(){
// pass callable to array_filter
return array_shift(array_filter(func_get_args(), 'not_null'));
}
It would be nice if you could simply pass isset or 'isset' as the 2nd argument to array_filter, but no such luck.

I'm currently using this, but I wonder if it couldn't be improved with some of the new features in PHP 5.
function coalesce() {
$args = func_get_args();
foreach ($args as $arg) {
if (!empty($arg)) {
return $arg;
}
}
return $args[0];
}

PHP 5.3+, with closures:
function coalesce()
{
return array_shift(array_filter(func_get_args(), function ($value) {
return !is_null($value);
}));
}
Demo: https://eval.in/187365

A function to return the first non-NULL value:
function coalesce(&...$args) { // ... as of PHP 5.6
foreach ($args as $arg) {
if (isset($arg)) return $arg;
}
}
Equivalent to $var1 ?? $var2 ?? null in PHP 7+.
A function to return the first non-empty value:
function coalesce(&...$args) {
foreach ($args as $arg) {
if (!empty($arg)) return $arg;
}
}
Equivalent to (isset($var1) ? $var1 : null) ?: (isset($var2) ? $var2 : null) ?: null in PHP 5.3+.
The above will consider a numerical string of "0.00" as non-empty. Typically coming from a HTTP GET, HTTP POST, browser cookie or the MySQL driver passing a float or decimal as numerical string.
A function to return the first variable that is not undefined, null, (bool)false, (int)0, (float)0.00, (string)"", (string)"0", (string)"0.00", (array)[]:
function coalesce(&...$args) {
foreach ($args as $arg) {
if (!empty($arg) && (!is_numeric($arg) || (float)$arg!= 0)) return $arg;
}
}
To be used like:
$myvar = coalesce($var1, $var2, $var3, ...);

Related

Shorthand for checking if associative array not null and if key exists

Is there any way of checking if a variable is not null and then to check if variable has nested associative array keys using a shorthand? Something like optional chaining for associative arrays?
Example of what I would like to make concise:
public $arr;
//$arr gets set as an associative array somewhere else in the code.
function someFunc() {
if ($this->arr && $this->arr['key1'] && $this->arr['key1']['key2'] == 'Some Value') { // shorten this line?
// Do Something Cool!
}
}
I am looking for something similar to Optional Chaining in Javascript e.g. :
if (obj.key1?.key2 == 'Some Value') {
// Do something kool
}
There is a high probability that this is a duplicate and I apologize in advance if that is the case. I tried searching for this for associative arrays and could not find anything specific.
You could keep the if statement intact, but replace $this->arr && $this->arr['key1'] with an Null Coalescing Operator (??), so if those aren't defined, it will use the fallback, that isn't equal to the test string:
if (($this->arr['key1']['key2'] ?? false) == 'Some Value') {
// Do Something Cool!
}
So if $this->arr['key1']['key2'] is defined, you'll compare that to Some Value, otherwise, if it's not defined, you'll compare (eg) false to Some Value witch will remain false.
Use the php function isset: https://www.php.net/manual/en/function.isset.php
It will check for multiple keys: if( isset($this->arr['key1']['key2'] ) {...} and also includes a null check.
If you for some reason need to shorten it even more, simply decompose it to another function:
public $arr;
//$arr gets set as an associative array somewhere else in the code.
function someFunc()
{
if ($this->checkForSomeValue($this->arr, 'Some Value', 'key1', 'key2')) { // shorten this line?
// Do Something Cool!
}
}
function checkForSomeValue(array $arr, string $valueToCheck, ...$keys)
{
$valueCompare = $arr;
foreach($keys as $key)
{
if(!isset($valueCompare[$key]))
{
$valueCompare = null;
break;
}
else
{
$valueCompare = $valueCompare[$key];
}
}
return $valueCompare && $valueCompare === $valueToCheck;
}
I've updated the answer to allow supplying keys

PHP - Is there a way to get a variable value if it is set?

I always find myself writing something like:
if(isset($var))
{
DoSomethingWith($var);
}
else
{
DoSomethingWith(null);
}
or
if(isset($arr["key"]))
{
DoSomethingWith($arr["key"];
}
else
{
DoSomethingWith(null);
}
My question is exacly this:
Is there a way to write a get_var_if_set() function so that you can simply write...
DoSomethingWith(get_var_if_set($var));
/// and
DoSomethingWith(get_var_if_set($arr["key"]));
....WITHOUT notifying if $var doesn't exists or that $arr doesn't have a set value for "key"?
I guess it should be something like this:
function get_var_if_set($a)
{
return (isset($a) ? $a : null);
}
But that doesn't work because calling get_var_if_set() with unset variables will always generate a Notice, so it might need a little magic.
Thanks all folks.
Edit
A user who deleted his answer suggested to pass variables by reference, as PHP will pass null if the $variable doesn't exist.
So that would be perfect, take a look at these solutions (which might probably be equivalent):
function get_var_if_set(&$a) { return (isset($a) ? $a : null); }
function get_var_if_set(&$a) { return $a ?? null; } // Only in PHP 7.0+ (Coalescing operator)
Note: Coalescing operator suggested by Koray Küpe
The problem as you can see is that they initialize the passed variables somehow in the return statement.
We don't want that.
If you use PHP 7.0+ you can use null coalescing operator.
return $a ?? null;
Note: The operator checkes whether the variable exist and not empty. So, if the variable is already set but empty, it will return the null variable already.
The problem is not the var itself, but the key of the array key in this:
DoSomethingWith(get_var_if_set($arr["key"]))
So the only solution is to check for the array and the key you're looking for.
function get_var_if_set($a, $key = null)
{
if(is_array($a) && array_key_exists($key, $array)) {
return $a[$key];
} else {
return (isset($a) ? $a : null);
}
}

What is the PHP7 way to check if a value is set and is equal to a specific value?

What is the PHP7 method of the following function that checks if a value is set and equal to a specific value?
Let me know if you see any other room for improvement in general:
public function getResponseFormat($request)
{
$responseFormat = 'php';
if(isset($request['controller']['name']) && $request['controller']['name'] == 'email') {
if(isset($request['controller']['options']['responseFormat'])) {
$responseFormat = $request['controller']['options']['responseFormat'];
}
}
return $responseFormat;
}
If you want to make use of the new NULL COALESCE operator, you can write the method like this:
public function getResponseFormat($request)
{
if ($request['controller']['name'] ?? null == 'email') {
return $request['controller']['options']['responseFormat'] ?? 'php';
}
return 'php';
}
$x ?? null evaluates to null if $x is not set, and $x ?? 'php' evaluates to 'php' if $x is not set.
You could also put everything in one line with an additional ternary operator ?: to have a single return but that would be at the cost of readability.

Ternary operator shorthand to use subject of expression in true/false clause rather than repeating

Say I have a long(ish) variable, $row['data']['manybullets']['bullets']['bullet'][0], and want to test whether it's set using the ternary operator:
$bulletx =
isset($row['data']['property']['bullets']['bullet'][0]) // condition
? $row['data']['property']['bullets']['bullet'][0] // true
: 'empty'; // false
Is there anyway for me to reference the subject of the expression rather than repeating it. E.g.
$bulletx =
isset($row['data']['property']['bullets']['bullet'][0]) // condition
? SUBJECT // true
: 'empty'; // false
Curious.
PHP supports foo ?: bar but unfortunately this won't work because of the isset() in your condition.
So unfortunately there is no really good way to do this in a shorter way. Besides using another language of course (e.g. foo.get(..., 'empty') in python)
However, if the default value being evaluated in any case is not a problem (e.g. because it's just a static value anyway) you can use a function:
function ifsetor(&$value, $default) {
return isset($value) ? $value : $default;
}
Because of the reference argument this will not throw an E_NOTICE in case of an undefined value.
You can do it like this:
$bulletx = ($r=$row['data']['property']['bullets']['bullet'][0]) ? $r : 'empty';
See working demo
Not really, without triggering an E_NOTICE warning, but if you decide to ignore those you could achieve it like this.
$bulletx =
$row['data']['property']['bullets']['bullet'][0] // true
?: 'empty'; // false
No built-in way, but you can write a wrapper for isset that checks the array keys.
function array_isset(&$array /* ... */) {
$a = $array;
if (! is_array($a)) {
return false;
}
for ($i = 1; $i < func_num_args(); $i++) {
$k = func_get_arg($i);
if (isset($a[$k])) {
$a = $a[$k];
} else {
return false;
}
}
return $a;
}
$bulletx = array_isset($row, 'data', 'property', 'bullets', 'bullet', 0) ?: 'empty';
I like this way, as it keeps the same API as isset() and can make use of the ?: short cut.

safely get array element value for defined and undefined indexes

I'm getting tired of writing ternary expressions to sanitize the data, things like:
$x = isset($array['idx']) ? $array['idx'] : null;
// and
$x = !empty($array['idx']) ? $array['idx'] : null;
Is there a native way or ZF accessor/filter to get array element value for some given array without:
disabling error_reporting
ternary isset/empty check
error control operator #
creating my own global function or application accessor/filter
Something like:
$x = get_if_set($array['idx']);
// or
$x = Zend_XXX($array, 'idx')
PHP7 introduced the null coalesce operator ??. Assuming you're lucky enough to be running it, you can just do
$x = $array['idx'] ?? null;
Since PHP 7.0
Things are much more easy - thanks to Andrea Faulds and Nikita Popov for - the Null Coalescing Operator ??Docs, Migration, RFC:
$x = $array['idx'] ?? NULL;
or with a $default:
$x = $array['idx'] ?? $default ?? NULL;
Like isset or empty it gives no warning and the expression falls through to the right (if unset). if set and not NULL the value is taken. This is also how $default in the previous example works, always, even if undefined.
Since PHP 7.4
Thanks to Midori Kocak - is the Null Coalescing Assignment Operator ??=Docs, RFC (which was one of the things I missed after ??) allows to assign default values directly:
$array['idx'] ??= null;
$x = $array['idx'];
I don't use it by far as often as ??, but it is good to know about it, especially if while break up data-handling logic and want to have defaults early.
Original old answer
As far as you only need NULL as "default" value, you can make use of the error suppression operator:
$x = #$array['idx'];
Criticism: Using the error suppression operator has some downsides. First it uses the error suppression operator, so you can not easily recover issues if that part of the code has some. Additionally the standard error situation if undefined does pollute looking for screams. Your code is not as expressing itself as precise as it could be. Another potential issue is to make use of an invalid index value, e.g. injecting objects for indexes etc.. This would get unnoticed.
It will prevent warnings. However if you like to allow other default values as well, you can encapsulate the access to an array offset via the ArrayAccess interface:
class PigArray implements ArrayAccess
{
private $array;
private $default;
public function __construct(array $array, $default = NULL)
{
$this->array = $array;
$this->default = $default;
}
public function offsetExists($offset)
{
return isset($this->array[$offset]);
}
public function offsetGet($offset)
{
return isset($this->array[$offset])
? $this->array[$offset]
: $this->default
;
}
public function offsetSet($offset, $value)
{
$this->array[$offset] = $value;
}
public function offsetUnset($offset)
{
unset($this->array[$offset]);
}
}
Usage:
$array = array_fill_keys(range('A', 'C'), 'value');
$array = new PigArray($array, 'default');
$a = $array['A']; # string(13) "value"
$idx = $array['IDX']; # NULL "default"
var_dump($a, $idx);
Demo: https://eval.in/80896
I would say, use a helper function to interface with your array.
function getValue($key, $arr, $default=null) {
$pieces = explode('.', $key);
$array = $arr;
foreach($pieces as $array_key) {
if(!is_null($array) && is_array($array) && array_key_exists($array_key, $array)) {
$array = $array[$array_key];
}
else {
$array = null;
break;
}
}
return is_null($array) ? $default : $array;
}
$testarr = [
['foobar' => 'baz'],
['active' => false]
];
$output = getValue('0.foobar',$testarr,'NOT FOUND');
var_dump($output);
$output = getValue('0',$testarr,'NOT FOUND');
var_dump($output);
$output = getValue('1.active',$testarr,'NOT FOUND');
var_dump($output);
$output = getValue('i.do.not.exist',$testarr,'NOT FOUND');
var_dump($output);
This way you can provide a default value instead of null to your liking, you don't need to reinstate the array as another object and you can request any value as deeply nested as you wish without having to check the "parent" arrays.
https://ideone.com/11jtzj
Declare your variables and give them some initial value.
$x = NULL;
$y = 'something other than NULL';
now if you have an array $myArray that has x and y keys the extract function will overwrite the initial values (you can also configure it not to)
$myArray['x'] = 'newX';
extract($myArray);
//$x is now newX
if no keys are present the initial values for variables remain. It will also put other array keys in respective variables.

Categories