rfc:ifsetor

Request for Comments: ifsetor()

This RFC proposes an operator that efficiently implements (isset($foo) ? $foo : $bar) as ifsetor($foo, $bar). However this proposal has not been accepted, mainly due to the existance of the userland implementation #2. Of course once a true COALESCE() with any number of parameters could be supported, the situation would be different, since obviously using the “hack” of a pass by reference variable would require hardcoding the number of optional parameters.

Introduction

Frequently PHP developers need to initialize some variable that is passed in via the outside to some value. This leads to repeated code, that is needlessly prone to typos as well as some performance overhead.

Why do we need ifsetor()?

Frequently developers need to use the following code constructs:

$var = isset($var) ? $var : "admin";
$var2 = isset($var) ? $var : "admin";

The proposal is that this could be written in a much more concise manner:

$var = ifsetor($var, "admin");
$var2 = ifsetor($var, "admin");

One key point is that ifsetor(), will behave like isset() or empty() in that it will not throw a notice if the variable is not yet defined. This will become very handy in case one is trying to read some value out of a deepy nested array. Both examples below would not throw a notice for an undefined variable or array index.

$var = array();
$var = isset($var['foo']['bar']) ? $var['foo']['bar'] : "admin";

Would become

$var = array();
$var = ifsetor($var['foo']['bar'], "admin");

Furthermore ifsetor() would support expressions in the second parameter, that would only be evaluated if all of the previous parameter were either null or evaluated to null. In the following example the really_expensive_function() should not get called in most cases.

function foo() { return (rand(0, 100) < 100 ? true : null); }
$foo = foo();
$var = ifsetor($foo, really_expensive_function());

Common Misconceptions

"?:"

One of the main misconceptions is that the newly introduced short ternary syntax will solve this issue already. In case $var is not yet defined, this will obviously throw a notice.

$var = $var ?: "admin";

Using isset() to prevent the notice will obviously also not leas to the desired result, since isset($var) will always be set since it returns a boolean

$var = isset($var) ?: "admin";

So the above code will essentially be equivalent to

$var = isset($var) ? isset($var) : "admin";

Also “?:” does a boolean evaluation and not an isset() check.

"@"

Another approach that is frequently pointed out is to simply use the “@” sign to suppress the notice, which could solve some cases:

$var2 = (int)@$var;

This would be equivalent to the following longer version with the serious caveat that using “@” has significant overhead and could lead to issues with custom error handlers that do not properly handle the “@” technique to suppress notices.

$var2 = isset($var) ? (int)$var : null;

The suggested approach would make the above code not throw notices while being a lot more concise:

$var2 = (int)(ifsetor($var));

This however is a deviation of the implementation of COALESCE() for example in SQLite, where at least 2 parameters are required.

userland #1

Another misconception is that the functionality could be implemented in userland. This would prevent several key advantages of ifsetor().

The obvious issue is that in the userland case a notice would be thrown for an undefined variable. This issue could be solved by reducing the solution to the special case of a potentially undefined array key in the first dimension, which would cover a fraction of the cases

    function ifsetor($variable, $key, $default = null){
        if(isset($variable[$key])){
          return $variable[$key];
        }
        return $default;
    }

userland #2

A more flexible approach would make it possible to handle non arrays as well. The below is a simple implementation proposed on the list. The use of pass by reference would prevent the notice from being thrown for the first parameter only. It also has some minor implications on memory management, since the below example would create a variable $foo with the value null in the current namespace (unless of course $x would be replaced with $foo in the below example).

    function ifsetor(&$variable, $default = null) {
        if (isset($variable)) {
            $tmp = $variable;
        } else {
            $tmp = $default;
        }
        return $tmp;
    }
$x = ifsetor($foo, 'bar');

The obvious problem is that all expressions would always be evaluated, even if their return value will not be used. This brings with it a significant performance overhead and can even lead to bugs if the expression creates side effects that should only occur if the expression was actually used to generate the value for the variable.

Proposal and Patch

Synopsis: “ifsetor” “(” value “,” default “)”

Returns the value if it exists or a given default value.

Syntax: “ifsetor” “(” variable [ “,” expression ] “)”

Semantic:

  1. The value in question must be a variable.
  2. The default value can be any expression.
  3. The default value can be omitted in which case NULL will be used.

http://php.net/~helly/ze2-ifsetor-20040901.diff.txt

Rejected Features

Actually ifsetor() is supposed to allow any number of parameters and it will return the first non null parameter (or null if there is none). This is analogues to the SQL function COALESCE(). In some situations code like the following will even be necessary to take into account multiple sources for a default.

$var = isset($var) ? $var : (isset($var2) ? $var2 : "admin");

The proposal is that this could be written in a much more concise manner:

$var = ifsetor($var, $var2, "admin");

However this is currently not possible to be implemented without major slowdowns to the engine.

Changelog

References

rfc/ifsetor.txt · Last modified: 2017/09/22 13:28 by 127.0.0.1