====== Request for Comments: ifsetor() ====== * Version: 0.9 * Date: 2008-06-21 * Author: Lukas Smith * Status: Declined 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 [[http://marc.info/?l=php-internals&m=120138389219437&w=2|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 [[http://www.sqlite.org/lang_corefunc.html|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 [[http://marc.info/?l=php-internals&m=108940007627089&w=2|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 [[http://marc.info/?l=php-internals&m=111875921829669&w=2|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: - The value in question must be a variable. - The default value can be any expression. - 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 [[http://en.wikipedia.org/wiki/Null_%28SQL%29#COALESCE|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 [[http://marc.info/?l=php-internals&m=108931281901389&w=2|currently not possible]] to be implemented without major slowdowns to the engine. ===== Changelog ===== ===== References ===== * [[http://marc.info/?l=php-internals&m=108204233208758&w=2|Original thread that spawned the proposal]] * [[http://marc.info/?l=php-internals&m=108214435225556&w=2|Proposal to make the solution a new operator]] * [[http://marc.info/?l=php-internals&m=108214693612014&w=2|Proposal to extend isset() to cover this functionality]], which is not possible since [[http://marc.info/?l=php-internals&m=108214715226622&w=2|isset() already accepts multiple parameters]] * [[http://marc.info/?l=php-internals&m=108214772409770&w=2|Suggestion to go with the Oracle inspired nvl() as the name]] but this caused some concerned about the fact that [[http://marc.info/?l=php-internals&m=108215247922774&w=2|Oracle's approach to null values is different than in PHP]] * Suggestion to go with [[http://marc.info/?l=php-internals&m=108215154614820&w=2|ifexists()]] and [[http://marc.info/?l=php-internals&m=108215367007264&w=2|ifelse()]], which was pointed out would make it hard to differentiate with the cases [[http://marc.info/?l=php-internals&m=108215394516297&w=2|where these two words are separated by a space, which would lead to a different behavior]] * [[http://marc.info/?l=php-internals&m=108216270212991&w=2|Comment pointing out that using @ is too slow and ugly]] * [[http://marc.info/?l=php-internals&m=108215497929024&w=2|Suggestion to go with coalesce()]] * [[http://marc.info/?l=php-internals&m=108219392925263&w=2|Reminder that DB access usually brings the most overhead and not function calls vs. op-code based alternatives]] * [[http://marc.info/?l=php-internals&m=108942238308924&w=2|A list]]of other possible name suggestions and [[http://marc.info/?l=php-internals&m=108956959224805&w=2|another one with commentary]] * [[http://marc.info/?l=php-internals&m=108969685232444&w=2|Suggestion and discussion of default()]] which is already a reserved word * [[http://marc.info/?l=php-internals&m=111816821723769&w=2|Strong opposition by Zeev against ifsetor]] since he feels that its redundant and that not all useful things can be implemented as a native feature * [[http://marc.info/?l=php-internals&m=108951420008859&w=2|Summary of the bulk of the discussion]] * [[http://marc.info/?l=php-internals&m=111834043422491&w=2|Suggestion to leave an empty() variant out of the picture]] since this feature can be implemented in userland, though this of course not provide the full functionality of empty() which does not trigger notices for missing variables * [[http://marc.info/?l=php-internals&m=111872386430805&w=2|Request to also add a custom callback to ifsetor()]] to increase the flexibility * [[http://www.php.net/~derick/meeting-notes.html#ifsetor-as-replacement-for-foo-isset-foo-foo-something-else|Decision at the PHP6 planning meeting to only implement ?: and not ifsetor]] * Suggestion to add [[http://marc.info/?l=php-internals&m=113210592810849&w=2|macro]] [[http://marc.info/?l=php-internals&m=116361059402342&w=2|support]] to make it easier to reuse complex userland code pieces to keep the code more readable when handling some of the above aspects in userland * [[http://marc.info/?l=php-internals&m=118946242013246&w=2|Request to add a native function to handle the limited case of a single dimensional array]] * [[http://marc.info/?l=php-internals&m=108955534724882&w=2|Suggestion to fo with the ifsetor() as is]] and later try to find a way to implement a true coalesce() with the name "coalesce()"