Basj
Basj

Reputation: 46433

Default value for an associative array in PHP, if key not present

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

Upvotes: 1

Views: 2145

Answers (5)

LSerni
LSerni

Reputation: 57388

(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.

Upvotes: 3

Scuzzy
Scuzzy

Reputation: 12332

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' );

Upvotes: 1

Rate IN UA
Rate IN UA

Reputation: 59

1 step - add this function:

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

2 step - use this:

echo getIfIsset($mayArray, 'bar');

Upvotes: 2

Scuzzy
Scuzzy

Reputation: 12332

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) ""
}

Upvotes: 0

Rate IN UA
Rate IN UA

Reputation: 59

You can use this method @ before variable with key:

echo @$a['bar'];

Upvotes: -1

Related Questions