9

I love the Hash implementation of Ruby where you can initialize the Hash object with a default value. At the moment I'm struggling with implementing a similar object in PHP. This is my first (non-working) shot at this.

class DefaultArray extends ArrayObject {

  protected $_defaultValue;

  public function setDefault($defaultValue) {
    $this->_defaultValue  = $defaultValue;
  }

  public function offsetExists($index) {
    return true;
  }

  public function offsetGet($index) {
    if(!parent::offsetExists($index)) {
      if(is_object($this->_defaultValue))
        $default = clone $this->_defaultValue;
      else 
        $default = $this->_defaultValue;

      parent::offsetSet($index, $default);
    }
    return parent::offsetGet($index);
  }
}

$da = new DefaultArray();
assert($da["dummy"] == null);
$da->setDefault = 1;
assert($da["dummy2"] == 1);

The second assertion will fail. Stepping through the code shows that offsetGet is called and the if clause is executed. Nevertheless any array value is null. Any ideas for alternative implementations?

I'm tired of writing

if(!isset($myarr['value']))
    $myarr['value'] = new MyObj();
$myarr['value']->myVal=5;

instead of just writing

$myarr['value']->myVal=5;
2
  • When accessing a key that does not exist, offsetGet will set that key with the default value (see offsetSet). That means the next time it will exist. If you don’t want that behavior and always get the current default value for non-existing keys, remove the offsetSet call. Commented Sep 23, 2009 at 9:10
  • Thanks, Gumbo for the important clarification, this behavior has baffled me in my first test, but is the intended behavior. The question ins now answered and I'll leave the faulty test code as it is. Commented Sep 23, 2009 at 9:36

4 Answers 4

6
$da->setDefault(1);

You can also use the __construct magic function:

class DefaultArray extends ArrayObject
{
    public function __construct($value = null){
        if(is_null($value))
        {
            $this->value = 'default';
        } else {
            $this->value = $value;
        }
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, you spotted my error! It's $da->setDefault(1) instead of $da->setDefault = 1! Oh the embarassment! With this modification my example works as expected. I also thought about passing the default value in the constructor but wanted to preserve the original constructor arguments of ArrayObject.
You could use my tiny library ValueResolver to make this more compact.
3

Try the magic methods __get.

class DefaultArray extends ArrayObject {
    protected $_defaultValue;

    public function setDefault($defaultValue) {
        $this->_defaultValue  = $defaultValue;
    }

    public function __get($index) {
        return $this->offsetGet($index);
    }

    public function offsetGet($index) {
        if(!parent::offsetExists($index)) {
            if (is_object($this->_defaultValue)) {
                $default = clone $this->_defaultValue;
            } else {
                $default = $this->_defaultValue;
            }
            parent::offsetSet($index, $default);
        }
        return parent::offsetGet($index);
    }
}

Now you just need to use different keys as the read access will initialize that array items:

$da = new DefaultArray();
assert($da['foo'] == null);
$da->setDefault(1);
assert($da['bar'] == 1);

Comments

0

You could use my tiny library ValueResolver in this case, for example:

class DefaultArray extends ArrayObject
{
    public function __construct($value = null){
        $this->value = ValueResolver::resolve($value, 'default'); // returns 'default' if $value is null
    }
}

and don't forget to use namespace use LapaLabs\ValueResolver\Resolver\ValueResolver;

There are also ability to typecasting, for example if your variable's value should be integer, so use this:

$id = ValueResolver::toInteger('6 apples', 1); // returns 6
$id = ValueResolver::toInteger('There are no apples', 1); // returns 1 (used default value)

Check the docs for more examples

Comments

-3

Why so complicated?

function initVal($value) {
    global $myarr;
    if(!isset($myarr['value']))
        $myarr['value'] = new MyObj();
}

Now you just have to call:

initVal('bla');
$myarr['bla']->bla = 'bla';

But I see, yours is much more neat.

1 Comment

Using the ArrayObject class keeps all of the variables and supporting methods in one place. Where as using the global function has a much higher probability to lead to maintenance problems in the future.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.