52

Is there a ternary operator or the like in PHP that acts like ?? of C#?

?? in C# is clean and shorter, but in PHP you have to do something like:

// This is absolutely okay except that $_REQUEST['test'] is kind of redundant.
echo isset($_REQUEST['test'])? $_REQUEST['test'] : 'hi';

// This is perfect! Shorter and cleaner, but only in this situation.
echo null? : 'replacement if empty';

// This line gives error when $_REQUEST['test'] is NOT set.
echo $_REQUEST['test']?: 'hi';
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
kazinix
  • 28,987
  • 33
  • 107
  • 157
  • `?:` is very close to `??`. In fact, `?:` actually catches more null-like cases than `??`; `??` is specifically for `null` and `!Nullabe.HasValue`. You sound like you're looking for something more like JavaScript's `||` operator. It's like `?:`, but JavaScript doesn't complain about referencing undefined keys/members--though it does throw an error if you try to reference a key/member of undefined/null, so you can only go one level. – Zenexer Jul 14 '13 at 13:20
  • @dpp, Why did you say `someres` then changed it to `test`? – Pacerier Mar 30 '15 at 12:09
  • 5
    Check out version 7. We finally have that. – Marcelo Camargo May 28 '15 at 13:36
  • 2
    Php 7 has this feature. Please check https://wiki.php.net/rfc/isset_ternary – Mukesh Jul 16 '15 at 09:02
  • 2
    As noted, this will be in PHP 7. In earlier versions, I think this is one of the few valid use cases for the error suppression operator, e.g. `echo @$_REQUEST['someres'] ?: 'hi';` which suppresses the error. – El Yobo Sep 23 '15 at 13:23

6 Answers6

70

PHP 7 adds the null coalescing operator:

// Fetches the value of $_GET['user'] and returns 'nobody'
// if it does not exist.
$username = $_GET['user'] ?? 'nobody';
// This is equivalent to:
$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';

You could also look at short way of writing PHP's ternary operator ?: (PHP >=5.3 only)

// Example usage for: Short Ternary Operator
$action = $_POST['action'] ?: 'default';

// The above is identical to
$action = $_POST['action'] ? $_POST['action'] : 'default';

And your comparison to C# is not fair. "in PHP you have to do something like" - In C# you will also have a runtime error if you try to access a non-existent array/dictionary item.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
zerkms
  • 249,484
  • 69
  • 436
  • 539
  • 7
    Really? So if I'm accessing a non-existent array element? I will get error too, yes, that makes sense. – kazinix Sep 02 '11 at 03:04
  • @NullUserException: thanks, I always get in stuck with such sort of phrases :-( – zerkms Sep 02 '11 at 03:06
  • 1
    It's a perfectly fair statement. The `System.Linq` namespace provides extension method `TSource Enumerable.ElementAtOrDefault(this IEnumerable source, int index)` for arrays and such. `IDictionary` itself provides [a method for this](http://msdn.microsoft.com/en-us/library/bb299639.aspx): `bool IDictionary.TryGetValue(TKey key, out TValue value)`. This method returns two values, as C# differentiates more explicitly between `null` and unset. If you'd rather treat `null` and unset equally, you could easily write an extension method. – Zenexer Jul 13 '13 at 22:53
  • @Zenexer: the question was not about methods, but about operators. Thanks for downvoting without any reason. – zerkms Jul 13 '13 at 23:19
  • @zerkms Look at it from the point of view of the person asking the question. They want a solution, not strictly an operator. An answer is not the place for you to get defensive. – Zenexer Jul 14 '13 at 00:32
  • @Zenexer: please read the question once again - OP asked about how operator specifically works. The question is about the general operator behaviour, not about a particular programming issue. – zerkms Jul 14 '13 at 00:34
  • @zerkms You are retaliating inappropriately. If you must retaliate at all, go downvote my post instead of insisting that I interpret the question literally. – Zenexer Jul 14 '13 at 00:43
  • @Zenexer: well, you think my answer is incorrect. I think that yours is wrong. Questions? – zerkms Jul 14 '13 at 00:54
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/33427/discussion-between-zenexer-and-zerkms) – Zenexer Jul 14 '13 at 04:46
  • @zerkms, Wow you have run-time errors and you are complaining? Here I've got to spend hours finding out where the silent errors are hiding. Give me all your errors. – Pacerier Mar 30 '15 at 12:23
  • @Pacerier "Wow you have run-time errors and you are complaining?" --- not sure what you mean, sorry. – zerkms Mar 30 '15 at 18:43
  • @zerkms, Ah the luxury of strongly-typed languages, as opposed to [loosely typed](https://www.google.com/search?q=fails+silently) ones. – Pacerier Apr 06 '15 at 01:38
  • @Pacerier okay. Still not sure where I was complaining about anything here. – zerkms Apr 06 '15 at 01:39
  • What in the world does "PHP you have to do something like ---" mean? Very unclear. – Kzqai May 06 '16 at 01:33
  • @Kzqai check the history of edits: someone else removed quotes and something else, so it's lost the original meaning. I'm going to fix it now. – zerkms May 06 '16 at 01:41
  • While `??` certainly makes some cases easier to write and read, I would prefer it to emulate `empty($var) ? 'default' : $var`. Empty would not raise error if there is no `$var` set (just like `isset`), but it would also check for emptyness (like `(bool)$var === false`, I think), which is likely to be more useful in web development, full of user inputs ;-) – pilat Mar 16 '17 at 07:38
  • @pilat "Empty would not raise error if there is no $var set" --- that's actually a not valid justification: you should **NEVER** address variables that might be not declared. It simply is already a mistake itself, so it should be fixed, not mitigated. – zerkms Mar 16 '17 at 08:04
  • 1
    @zerkms in web development, we deal with user input a lot. Even APIs may produce structures with "variable number of fields" and we still need to integrate with them. There is a reason behind `isset` function, after all. My example: `empty($var)` was not very good, however. I should have written something like `empty($var['key'])` (assuming that $var is indeed defined, but I have doubts about the 'key' existence), but it was omitted for the sake of brevity. – pilat Mar 16 '17 at 18:04
  • @pilat indeed, `empty($var['key'])` makes much more sense (and it should have been used as an example instead) :-) – zerkms Mar 16 '17 at 19:12
53

The Null Coalesce Operator, (??) has been accepted and implemented in PHP 7. It differs from the short ternary operator (?:) in that ?? will suppress the E_NOTICE that would otherwise occur when attempting to access an array where it doesn't have a key. The first example in the RFC gives:

$username = $_GET['user'] ?? 'nobody';
// equivalent to: $username = isset($_GET['user']) ? $_GET['user'] : 'nobody';

Notice that the ?? operator does not require the manual application of isset to prevent the E_NOTICE.

kojiro
  • 74,557
  • 19
  • 143
  • 201
  • 2
    It works anywhere `isset()` does, actually, so it works for variables and properties, too. – Andrea Jan 06 '15 at 03:35
  • 2
    Also, a nice feature is that you can chain it, thus: `$_GET['user'] ?? $_SESSION['user'] ?? 'bob';` - and as it short-circuits, you can even do function calls. Plus, unlike `isset($foo) ? $foo : $bar`, if it's a function call (e.g. `foo()[0]`), it won't evaluate it twice. :) – Andrea Jan 06 '15 at 04:11
  • 1
    @AndreaFaulds You should say that unlike the plain ternary you can chain it without [traumatic brain injury](http://stackoverflow.com/questions/13142538/strange-behaviour-ternary-operator). – kojiro Jan 06 '15 at 04:14
  • Well, you can do that with the short ternary operator `?:` too, it's a different use case. – Andrea Jan 07 '15 at 04:26
10

I use function. Obviously it is not operator, but seems cleaner than your approach:

function isset_or(&$check, $alternate = NULL)
{
    return (isset($check)) ? $check : $alternate;
}

Usage:

isset_or($_REQUEST['test'],'hi');
LukLed
  • 31,452
  • 17
  • 82
  • 107
  • 1
    Why pass the `$check` var as reference? – Axel A. García Dec 27 '13 at 19:27
  • 5
    @AxelA.Grazx: I am passing as reference, because if it is not sent as reference, `$_REQUEST['test']` value will be calculated before function call and then passed to function. It will throw `Undefined index: test` error if `$REQUEST` array doesn't have this element. I wanted to avoid that. – LukLed Jan 07 '14 at 22:36
  • Ohhh... Now I see... Nice approach. – Axel A. García Jan 08 '14 at 00:13
  • This answer should be higher - I've been using the same approach and it's very neat. – Chris Middleton Mar 06 '15 at 20:30
  • 4
    This has the negative side effect of setting the checked key to null: http://3v4l.org/9vmFR Here's a case where that can be particularly problematic: http://3v4l.org/sMsKD – Zenexer Jun 06 '15 at 06:02
6

Prior to PHP 7, there isn't. If you need to involve isset, the pattern to use is isset($var) ? $var : null. There's no ?: operator that includes the characteristics of isset.

Kyle Challis
  • 981
  • 2
  • 12
  • 28
deceze
  • 510,633
  • 85
  • 743
  • 889
4

?? is binary in C#, not ternary. And it has no equivalence in PHP prior to PHP 7.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Daniel
  • 30,896
  • 18
  • 85
  • 139
  • 1
    The equivalent is `?:` in PHP 5.3. E.g. `test ?: ifNull` === `test ? test : ifNull`. In other words, `?:` can be either binary or ternary in PHP as the middle operand is optional. – devios1 Jun 04 '13 at 19:21
  • Actually, `??` in C# is ternary. It's syntactic sugar for a ternary operation. It's just written in binary form. – Zenexer Jul 13 '13 at 22:55
  • 3
    @Zenexer: It's a binary operator, and it's not just syntactic sugar - `func() ?? otherfunc()` only calls `func` once, whereas `func()!=null ? func() : otherfunc()` calls it twice, so if `func` has side effects the results are completely different. – leviathanbadger Jul 29 '13 at 18:42
  • @aboveyou00 Interesting; I didn't know that. – Zenexer Jul 30 '13 at 19:56
1

An identical operator doesn't exist as of PHP 5.6, but you can make a function that behaves similarly.

/**
 * Returns the first entry that passes an isset() test.
 *
 * Each entry can either be a single value: $value, or an array-key pair:
 * $array, $key.  If all entries fail isset(), or no entries are passed,
 * then first() will return null.
 *
 * $array must be an array that passes isset() on its own, or it will be
 * treated as a standalone $value.  $key must be a valid array key, or
 * both $array and $key will be treated as standalone $value entries. To
 * be considered a valid key, $key must pass:
 *
 *     is_null($key) || is_string($key) || is_int($key) || is_float($key)
 *         || is_bool($key)
 *
 * If $value is an array, it must be the last entry, the following entry
 * must be a valid array-key pair, or the following entry's $value must
 * not be a valid $key.  Otherwise, $value and the immediately following
 * $value will be treated as an array-key pair's $array and $key,
 * respectfully.  See above for $key validity tests.
 */
function first(/* [(array $array, $key) | $value]... */)
{
    $count = func_num_args();

    for ($i = 0; $i < $count - 1; $i++)
    {
        $arg = func_get_arg($i);

        if (!isset($arg))
        {
            continue;
        }

        if (is_array($arg))
        {
            $key = func_get_arg($i + 1);

            if (is_null($key) || is_string($key) || is_int($key) || is_float($key) || is_bool($key))
            {
                if (isset($arg[$key]))
                {
                    return $arg[$key];
                }

                $i++;
                continue;
            }
        }

        return $arg;
    }

    if ($i < $count)
    {
        return func_get_arg($i);
    }

    return null;
}

Usage:

$option = first($option_override, $_REQUEST, 'option', $_SESSION, 'option', false);

This would try each variable until it finds one that satisfies isset():

  1. $option_override
  2. $_REQUEST['option']
  3. $_SESSION['option']
  4. false

If 4 weren't there, it would default to null.

Note: There's a simpler implementation that uses references, but it has the side effect of setting the tested item to null if it doesn't already exist. This can be problematic when the size or truthiness of an array matters.

Zenexer
  • 18,788
  • 9
  • 71
  • 77
  • doesn't answer the OPs question which was "does such an operator exist". Also, @LukLed's answer (posted months after this) demonstrates something similar without the need to deconstruct associative array indexing. – benrifkah Jun 03 '15 at 21:18
  • @benrifkah LukLed's answer has the negative side effect of setting the specified key to null: http://3v4l.org/9vmFR If you take the reference of a key that doesn't exist, it will be created. It's particularly problematic if you're working with arrays that don't exist: http://3v4l.org/sMsKD – Zenexer Jun 06 '15 at 06:03
  • I'm seeing strange results from this "first" function. Care to chat about it? https://chat.stackoverflow.com/rooms/79985/chat-about-php-null-coalesce-function – benrifkah Jun 08 '15 at 18:56
  • @benrifkah Sorry for the late reply; heading there now. – Zenexer Jun 10 '15 at 19:15