Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

I have a bunch of strings like "memory.caching" and "server.base.url". Within my configuration object each part of the key "." is equal to a key within an array, the value could be another array and the last would be the value so "memory.caching" would equate to.

$config = array(
  "memory" => array(
    "caching" => true
  )
);

I want to create a setter method. I have ended up with the below code, but that won't work for three or four levels of depth. How can I do it without adding multiple else/if clauses.

public function set($k, $v) {
  $parts = explode(".", $k);

  // Start sucky code.
  if (count($parts) == 2)
  {
    $this->config[$parts[0]][$parts[1]] = $val;
  }
}

I was thinking some form of loop with assigning by reference like below, but I couldn't get it to work.

public function set($k, $v) {
  $parts = explode(".", $k);

  $tmp = $this->config;
  foreach ($parts as $p) {
    $tmp &= $tmp[$p];
  }

  $tmp = $v;
}

Any ideas on how I could achieve this?

share|improve this question

3 Answers 3

up vote 2 down vote accepted

To set a value...

public function set($path, $value, $delimiter = '.') {

    $tokens = explode($delimiter, $path);

    $currentPiece = &$this->config;

    foreach($tokens as $token) {
       $currentPiece = &$currentPiece[$token];
    }

    $currentPiece = $value;  

}

CodePad.

To get a value...

public function get($path, $delimiter = '.') {

    $tokens = explode($delimiter, $path);

    $currentPiece = $this->config;

    while ($token = array_shift($tokens)) {
        $currentPiece = $currentPiece[$token];
    }

    return $currentPiece;

}

CodePad.

share|improve this answer
    
Wow awesome. I was just typing up a recursive function to do the job, but yours is brilliant. :D –  Turbotoast Dec 13 '11 at 11:21
    
Yeah that will give the the correct $piece, but if I add $piece = $newValue; and dump the $config, then the value within the $config hasn't changed, that's what I'm after, sorry if that wasn't clear :) –  Gcoop Dec 13 '11 at 11:23
    
Agreed the problem is $config is a nested associative array and I am using keys like "memory.caching" to get to a value, which is fine, except setting the value in a nice way is tricky. The ideal solution would be to just rewrite how the values get put into $config in the first place so it's not a nested associative array! But I am trying to cut an corner and not do that just yet! :) –  Gcoop Dec 13 '11 at 11:29
    
@Gcoop See my edit :) –  alex Dec 13 '11 at 11:35
    
That's the puppy, just had my & in the wrong place. Thanks! –  Gcoop Dec 13 '11 at 11:42

One slightly horrible, but definitely works approach to this is to use eval() (evil):

public function set($k, $v) {
  eval('$this->config["'.implode('"]["', explode(".", $k)).'"] = $v;');
}
share|improve this answer

While this question has since been answered (and accepted) I'll just throw this version out there for future visitors:

(Might be missing some checks, I have a few versions of this kicking around)

class Map
{

    protected $_array = array();

    protected $_separator;

    public static function get(Array $array, $key, $separator)
    {
        $parts = explode($separator, $key);
        $key = array_shift($parts);
        while (!empty($parts))
        {
            if (!isset($array[$key]) || !is_array($array[$key]))
            {
                return null;
            }
            $array = &$array[$key];
            $key = array_shift($parts);
        }
        return isset($array[$key]) ? $array[$key] : null;
    }

    public static function has(Array $array, $key, $separator)
    {
        $parts = explode($separator, $key);
        $key = array_shift($parts);
        while (!empty($parts))
        {
            if (!isset($array[$key]) || !is_array($array[$key]))
            {
                return false;
            }
            $array = &$array[$key];
            $key = array_shift($parts);
        }
        return isset($array[$key]);
    }

    public static function set(&$array, $key, $value, $separator)
    {
        $parts = explode($separator, $key);
        $key = array_shift($parts);
        while (!empty($parts))
        {
            if (!isset($array[$key]) || !is_array($array[$key]))
            {
                $array[$key] = array();
            }
            $array = &$array[$key];
            $key = array_shift($parts);
        }
        $array[$key] = $value;
    }

    public static function bind(&$array, $key, &$variable, $separator)
    {
        $parts = explode($separator, $key);
        $key = array_shift($parts);
        while (!empty($parts))
        {
            if (!isset($array[$key]) || !is_array($array[$key]))
            {
                $array[$key] = array();
            }
            $array = &$array[$key];
            $key = array_shift($parts);
        }
        if (isset($array[$key]))
        {
            $variable = $array[$key];
        }
        $array[$key] = &$variable;
    }

    public static function remove(&$array, $key, $separator)
    {
        $parts = explode($separator, $key);
        $key = array_shift($parts);
        while (!empty($parts))
        {
            if (!isset($array[$key]) || !is_array($array[$key]))
            {
                return;
            }
            $array = &$array[$key];
            $key = array_shift($parts);
        }
        unset($array[$key]);
    }

    public function __construct(&$array, $separator)
    {
        if (!is_array($array))
        {
            $array = array();
        }
        $this->_array = $array;
        $this->_separator = (string) $separator;
    }

    public function __get($key)
    {
        return static::get($this->_array, $key, $this->_separator);
    }

    public function __isset($key)
    {
        return static::has($this->_array, $key, $this->_separator);
    }

    public function __set($key, $value)
    {
        static::set($this->_array, $key, $value, $this->_separator);
    }

    public function __unset($key)
    {
        static::remove($this->_array, $key, $this->_separator);
    }

    public function get_array()
    {
        return $this->_array;
    }

}

And use it like:

$array = array(
    'foo' => array(
        'bar' => array(
            'hello' => 'world',
        ),
    ),
);
$map = new Map($array, '.');

var_dump($map->{'foo.bar.hello'}); 
//string(5) "world"

$map->{'foo.bar.goodbye'} = 'universe';
unset($map->{'foo.bar.hello'});
var_dump($map->get_array());
// array(1) {
//   ["foo"]=>
//   array(1) {
//     ["bar"]=>
//     array(1) {
//       ["goodbye"]=>
//       string(8) "universe"
//     }
//   }
// }

var_dump(isset($map->{'foo.bar.goodbye'}));
// true

Bind is very useful, but doesn't have a magic method to alias it with semantically.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.