42

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')
hakre
  • 193,403
  • 52
  • 435
  • 836
Alex
  • 6,441
  • 2
  • 25
  • 26
  • 4
    Why not create it yourself? It's a popular approach within the PHP community :) – J0HN Apr 24 '12 at 15:17
  • 1
    Admittedly, I'm not too familiar with it myself, but if you were to extend the ArrayObject class so that you had that ternary operation in the magic getter method, you could then just set it like normal. – mseancole Apr 24 '12 at 15:21
  • You could try using [`filter_var()`](http://us3.php.net/manual/en/function.filter-var.php) as well, though that could prove almost as bulky and cumbersome. Still think the best would be to set the magic get method and pass your array to the ArrayObject. – mseancole Apr 24 '12 at 15:52
  • 3
    @J0HN - absolutely agree with you, I just wanted to get input from other developers and to make sure I don't reinvent the wheel. @showerhead - thanks, I like this approach... simply overloading `ArrayObject::offsetGet($index)` does the trick. – Alex Apr 24 '12 at 17:28
  • @Alex: Again, I have to admit I didn't know about the `offsetGet()` method, I was merely talking about the magic `__get()` method. However, since it already has that functionality, go for it :) – mseancole Apr 24 '12 at 18:04
  • @showerhead `__get()` will work only if requesting property `$obj->prop` but not index `$obj['idx']`. Since `ArrayObject` implements `ArrayAccess` we can overload array access method `offsetGet` to check if idx exists before trying to get it. Thanks for your input. – Alex Apr 24 '12 at 19:23

4 Answers4

52

PHP7 introduced the null coalesce operator ??. Assuming you're lucky enough to be running it, you can just do

$x = $array['idx'] ?? null;
Yasammez
  • 1,383
  • 12
  • 15
  • 1
    1) The result of accessing an undefined key is already `null`, so `?? null` is not needed. 2) Be aware that *"an E_NOTICE-level error message will be issued"*: https://www.php.net/manual/en/language.types.array.php "Attempting to access an array key which has not been defined is the same as accessing any other undefined variable: an E_NOTICE-level error message will be issued, and the result will be NULL." – ToolmakerSteve May 08 '19 at 10:39
  • 9
    @ToolmakerSteve that is not correct. The Elvis operator suppresses the notice (just like `isset` does) and then returns the value on the right. Try it yourself. – Tamlyn Dec 18 '19 at 12:19
40

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

Federico Baù
  • 6,013
  • 5
  • 30
  • 38
hakre
  • 193,403
  • 52
  • 435
  • 836
  • thanks for the summarizing it. We came to pretty much same conclusion in the question comments but with heavier `ArrayObject` class and overloading 1 method... – Alex Apr 24 '12 at 19:55
  • I also thought about `ArrayObject` but with this version I had something with an array reference first which ain't possible with `ArrayObject` - however this version could extend from `ArrayObject` as well which normally is not heavy but very nice (it's PHP internal code so faster normally). – hakre Apr 24 '12 at 19:59
  • instead of NULL for the offsetGet you should use $this->default its more along the idea of what you are trying to do. also in the usage section it should be '$arrayData = ' instead of $array (first line). Very helpful post otherwise – Jeffrey Nicholson Carré Dec 18 '13 at 04:21
  • @bleuscyther: Thanks for the feedback! For the default that's true, most likely an oversight and fixed now incl. some other formatting problems. For the `$arrayData` suggestion I don't think so (data is so wishy-washy in my eyes, so it can stay the way it is as it's an array - I fixed the example). I also added a demo: https://eval.in/80896 – hakre Dec 19 '13 at 09:55
  • Ok, it must have been my mistake then cuz i was using new PigArray($arrayData ... – Jeffrey Nicholson Carré Dec 19 '13 at 21:07
  • I've seen that, too. I do honor your intentions, it was just my feedback after reviewing. So thanks for comment (I've also seen you tried to edit but it didn't pass community), so I thought you deserve extensive feedback as well. Hope this seems fine to you, too! – hakre Dec 20 '13 at 11:15
  • NOTE: Error suppression with `@` has some performance cost (not documented but easily measured, see e.g. in the comments of http://php.net/manual/en/language.operators.errorcontrol.php. In fact it's not just the cost of suppression itself, but that of the stir within PHP caused by the actual error (of the non-existing element in this case), which it still does handle in certain ways, only hides in the end. – Sz. Apr 03 '14 at 09:30
  • @lunakid: Just keep with the current version. PHP consistently improves the performance of the @ operator. That was merely a "true-myth" in earlier days. I think it's more important to educate why it should be used with caution and to explain it's expensiveness with design / handling reasons. – hakre Apr 03 '14 at 09:54
  • @hakre, I know what you mean, that's why I added that it *mostly comes from the actual error* itself, not from the `@` operator alone. So, no "myth" involved: certain amount of work to handle the actually occurring error (generating & recording it in `$php_errormsg` + messing with ini settings & various other kludges etc.) is there *by design, is documented, and can be measured* accordingly. The issue is indeed highly overrated, though. Anyhow, the point remains: *avoid* the error, if you can, *don't just ignore* it. Your nice array class also does just that. – Sz. Apr 03 '14 at 11:29
  • It would be cumbersome to use this for nested data, yes? For $foo['bar']['baz'], if $foo['bar'] had been assigned a plain array, you would no longer be able to take advantage of the simplified accessor. – BHSPitMonkey Dec 12 '15 at 01:40
  • @BHSPitMonkey: It's possible to further allow that by returning a new instance on NULL but it does not work that similar. – hakre Dec 14 '15 at 08:54
1

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

Tschallacka
  • 27,901
  • 14
  • 88
  • 133
0

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.

Weltschmerz
  • 2,166
  • 1
  • 15
  • 18
  • 6
    good try but this is even messier than ternary checks, especially when array has few dozen fields... This will remove dynamic features of PHP sparse arrays and result in lots of hardcoding. this solution will crate unwanted maintenance in future. – Alex Apr 24 '12 at 17:33
  • 3
    i'm not a fan of `extract` as it often does more pollution than help, although i know this code works. – Xuan Mar 05 '13 at 11:22