1

Note: I have already read Function to set default value of associative array if the key is not present and Default values for Associative array in PHP.

Let's say:

$a = array('foo' => '123');
$b = $a['bar'];       // PHP Notice:  Undefined index: bar

I'd like to have an empty string by default "" if the key is not present, instead of a PHP Notice.

I know the solutions would be to do:

$b = isset($a['bar']) ? $a['bar'] : "";
// or, since PHP 5.3:
$b = $a['bar'] ?: "";      // but this still produces a "PHP Notice"!

Is there a way to avoid the ?: "" and directly have:

$b = $a['bar'];

get "" with no Notice?


Note: this exists in Python with:

import collections
d = collections.defaultdict(str)
print(d['bar'])   # empty string, no error
Basj
  • 41,386
  • 99
  • 383
  • 673
  • If you've already read the answers to those questions, which quite clearly state that no such function exists, why are you asking the same question again? – miken32 Nov 30 '21 at 21:48
  • @miken32 Because I want to go one little step further than just setting a default value (as detailed [here](https://stackoverflow.com/questions/5089176/function-to-set-default-value-of-associative-array-if-the-key-is-not-present)) by also removing the PHP Notice. This detail is important for me. This question is not discussed in this linked question. – Basj Nov 30 '21 at 21:59
  • There is no way to set a default value. Your question is "is there way to... have `$b = $a['bar'];` get `"" `with no notice?" Which is the same as the other question. The answer is still no. The closest you're going to get is `$b = $a['bar'] ?? "";` – miken32 Nov 30 '21 at 22:01
  • @miken32 Hmm I haven't thought about this one. Again the devil is in the detail, what is the difference between this and `?: ""`? Edit: I see, no more notice, great! Maybe you can post an answer with this! – Basj Nov 30 '21 at 22:03
  • I don't want to answer this question, I'm trying to determine why I shouldn't close it as a duplicate. – miken32 Nov 30 '21 at 22:06
  • @miken32 I've searched, and you find questions *close* to this one (I linked some of them), but not exactly with the requirements here, ie 1) keep the standard `$a['key']` syntax 2) no PHP Notice 3) without using a helper function like `myGetter($a, 'key')` – Basj Nov 30 '21 at 22:09
  • Required reading: [PHP Elvis operator vs null coalescing operator](https://stackoverflow.com/q/34571330/2943403) This is not just some strange/cryptic syntax -- this is professional grade syntax that is used in some of the best PHP code. This is not to be avoided; it is to be learned, embraced and enjoyed. – mickmackusa Dec 19 '22 at 06:14

5 Answers5

3

(TL;DR the answer to the last line is "Yes, it's possible - see Scuzzy's answer")

No. PHP arrays don't work that way.

If you need defaults for known keys, then you can assign them:

$defaults = [
    'foo'  => 'foo',
    'bar'  => 'bar',
];

$a = [ 'bar' => 'baz' ];

$a = array_merge($defaults, $a);

Now $a['bar] will be 'baz' (not the default 'bar'), but $a['foo'] will yield 'foo'.

I think the only way to achieve a similar lexical simplicity with not-yet-defined keys is use a class and have $a return a default value of your choice using a magic getter. Different defaults for different keys, even.

Then you can write,

class FakeArray {
    private array $values;
    private array $defaults;
    private $default = null;

    public function __get($key) {
        if (array_key_exists($key, $this->values)) {
            return $this->values[$key];
        }
        if (array_key_exists($key, $this->defaults)) {
            return $this->defaults[$key];
        }
        return $this->default;
    }
    public function __construct(array $values = [ ]) {
        $this->values = $values;
    }
    public function setDefault($default) {
        $this->default = $default;
    }
    public function setDefaults(array $default = []) {
        $this->defaults = $defaults;
    }
};


$a = new FakeArray([ 'foo' => '123' ]);
$a->setDefaults([ 'joe' => 'jim' ]);
$a->setDefault('baz');

$c = $a->foo; // $c is now '123'.
$b = $a->bar; // $b is now 'baz' (no error).
$d = $a->joe; // $d is now 'jim' (not 'baz').

I would have thought that extending ArrayObject would have come very close to what you wanted, allowing to replace $a['key'] for $a->key, but that is not the case:

class FakeArray extends ArrayObject {
    ...
    public function __get($key) {
         return $this->default;
    }
}

but while $a['foo'] = 'bar' does store the value, actually not just accessing a nonexistent index, but even accessing a nonexistent property (which would otherwise work in other classes) now triggers an Index notice:

$b = $a->baz;

PHP Notice:  Undefined index: baz in ./test.php ...

So, the ArrayObject actually behaves worse than a StdClass.

This might mean that there's some magic goodness in ArrayObject that could yet be accessed and exploited, but I don't know for sure how and if this is true, or possible.

LSerni
  • 55,617
  • 10
  • 65
  • 107
  • Yeah what about making a class and using [__get()](https://www.php.net/manual/en/language.oop5.overloading.php#object.get) to return empty string when the key isn't found, the biggest annoyance is is_array() will fail, and instead you'd have to start using is_iterable() everywhere – Scuzzy Nov 30 '21 at 21:19
  • Thanks @LSerni. Could we extend `ArrayObject` in a way we would still use the `$a['bar']` syntax? – Basj Nov 30 '21 at 21:22
  • I have been trying just that, but even supplying a magic getter, `$a['nonexistent']` still throws a Notice. – LSerni Nov 30 '21 at 21:27
  • I'm having a look at `offsetGet()` and `offsetexists()` to wrap it with custom behavior – Scuzzy Nov 30 '21 at 21:28
  • @LSerni Yes the new operator `$a['nonexistent'] ?: ""` still produces a PHP Notice :( We probably have to use `isset(...) ? ... : ...`. – Basj Nov 30 '21 at 21:28
  • 1
    @LSerni I've posted another answer that may help the usage of ArrayObject concept – Scuzzy Nov 30 '21 at 21:38
2

1 step - add this function:

function getIfIsset(array $array, string $key)
{
    return $array[$key] ?? '';
}

2 step - use this:

echo getIfIsset($mayArray, 'bar');
Rate IN UA
  • 59
  • 3
  • 1
    Yes, but I'd like to avoid using a strange syntax like `getIsset(...)`, I'd prefer to use the `$a['key']` standard syntax. – Basj Nov 30 '21 at 22:02
1

I'd like to submit another solution using array ArrayObject as stated above

This extends the class and slightly tweaks the offsetGet behavior to always return an empty string

the parent:: statement here invokes the function of the extended class

class MyArrayObject extends ArrayObject
{
   function offsetGet( $key )
   {
     return parent::offsetExists( $key ) ? parent::offsetGet( $key ) : '';
   }
}

$myArray = new MyArrayObject( array( 'foo' => '123') );

var_dump( $myArray['foo'] ); // '123' provided string
var_dump( $myArray['bar'] ); // '' our new default
var_dump( isset( $myArray['foo'] ) ); // true
var_dump( isset( $myArray['bar'] ) ); // false

foreach( $myArray as $key )
{
    var_dump( $key ); // only one iteration of '123'
}

var_dump( is_array( $myArray ) ); // false unfortunately
var_dump( is_iterable( $myArray ) ); // true however

var_dump( array_merge( array(), $myArray ) ); // will fail
var_dump( array_merge( array(), (array) $myArray ) ); // will work

and finally

$myArray['bar'] = '456'; // now if we set bar
var_dump( $myArray['bar'] ); // '456' new value
var_dump( isset( $myArray['bar'] ) ); // now true

as a suggestion from @miken32 to set a default value

class MyArrayObject extends ArrayObject
{
    protected $default;
    public function __construct($array = [], $default = null, $flags = 0, $iteratorClass = "ArrayIterator")
    {
        parent::__construct( $array, $flags, $iteratorClass );
        $this->default = $default;
    }
    function offsetGet( $key )
    {
        return parent::offsetExists( $key ) ? parent::offsetGet( $key ) : $this->default;
    }
}

you can set a default value via

$myArray = new MyArrayObject( array( 'foo' => '123'), 'mydefault' );
Scuzzy
  • 12,186
  • 1
  • 46
  • 46
  • 1
    Could be improved by setting a default value in the constructor and accessing that instead of hard-coding the space. – miken32 Nov 30 '21 at 22:20
  • I like it, I may have to create a `setDefault($value)` method as the constructor is allready allocated the following: `__construct($array = [], $flags = 0, $iteratorClass = "ArrayIterator")` – Scuzzy Nov 30 '21 at 22:23
  • Turns out it doesn't use an interface, so we can go right ahead with a custom constructor. – Scuzzy Nov 30 '21 at 22:31
  • 1
    Works beautifully. +1. It seems that offsetGet() was the magic I didn't know :-D – LSerni Dec 01 '21 at 09:14
0

You'd have to pre-populate your array with your defaults or create a object class as stated to handle the default response.

$array = array('foo' => '123') + array('foo' => '', 'bar' => '');

var_dump( $array );

array(2) {
  ["foo"]=>string(3) "123"
  ["bar"]=> string(0) ""
}
Scuzzy
  • 12,186
  • 1
  • 46
  • 46
  • In your first line @Scuzzy, why doesn't the `+ array(...)` overywrite the value of key `foo`? – Basj Nov 30 '21 at 21:29
  • 1
    It checks if 'foo' exists or not before performing the merge https://stackoverflow.com/a/2140094/61795 that's why its a great simple way to populate keys you expect to exist in an array with default values. – Scuzzy Nov 30 '21 at 21:31
-1

You can use this method @ before variable with key:

echo @$a['bar'];

Rate IN UA
  • 59
  • 3