6

I've got a Config class in my application, which loads static config settings and parses them into arrays.
As I need to override some elements during runtime, I would need to access the public variable inside the Config-class by doing this; $config->values['onelevel']['twolevel'] = 'changed';

I would like to make a method that is called override that does this for me, but I cant get my head around what would be the best way to do it as my config files may get unknown amount of nested levels in the future.

It would be lovely to do something like $config->onelevel->twolevel = 'changed' and let the __set magic method take care of the nesting, but from what I can tell, it isn't possible.

What would be the best way to do this?

hakre
  • 193,403
  • 52
  • 435
  • 836
Industrial
  • 41,400
  • 69
  • 194
  • 289
  • Perhaps that kind of data can't be represented by the word "config"; store it in the DB – Dor Aug 26 '11 at 09:39
  • Why did you choose ErikPerik's answer as the best answer when my answer meets the requirements of your question more closely? Not that I don't want to grant ErikPerik rep points. I'm just curious why you felt his answer was the better answer? – Decent Dabbler Aug 26 '11 at 21:43
  • I originally thought that his solution were quite clean until I saw the updated version of yours. Utilizing the `ArrayObject` is something I've never done before, definitely need to look that up! – Industrial Aug 27 '11 at 09:17
  • I've never had a need for `ArrayObject` myself, but I suddenly realized I could put it to use here. Thank you for having a look at my answer again and choosing it; appreciated. – Decent Dabbler Aug 27 '11 at 13:56
  • I've simplified `offsetSet()` by the way. But perhaps you realized this yourself already: it was unnecessarily verbose. – Decent Dabbler Aug 27 '11 at 13:59

6 Answers6

7

It is possible to do what you want.

This example is largly inspired by Zend_Config and the example given in the PHP docs on the ArrayAccess interface.

edit:
With one minor caveat: you need to call toArray() on data representing an array, to convert it to an array, as the class internally needs to covert array data to an instance of itself, to allow access with the object property operator ->:

Eh, that's not really necessary anymore of course, since it implements ArrayAccess now. ;-)
/edit

class Config
    implements ArrayAccess
{
    protected $_data;

    public function __construct( array $data )
    {
        foreach( $data as $key => $value )
        {
            $this->$key = $value;
        }
    }

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

    public function __isset( $key )
    {
        return $this->offsetExists( $key );
    }

    public function __set( $key, $value )
    {
        $this->offsetSet( $key, $value );
    }

    public function __unset( $key )
    {
        $this->offsetUnset( $key );
    }

    public function offsetSet( $offset, $value )
    {
        $value = is_array( $value ) ? new self( $value ) : $value;

        if( is_null( $offset ) )
        {
            $this->_data[] = $value;
        }
        else
        {
            $this->_data[ $offset ] = $value;
        }
    }

    public function offsetExists( $offset )
    {
        return isset( $this->_data[ $offset ] );
    }

    public function offsetUnset( $offset )
    {
        unset( $this->_data[ $offset ] );
    }

    public function offsetGet( $offset )
    {
        return isset( $this->_data[ $offset ] ) ? $this->_data[ $offset ] : null;
    }

    public function toArray()
    {
        $array = array();
        $data = $this->_data;
        foreach( $data as $key => $value )
        {
            if( $value instanceof Config )
            {
                $array[ $key ] = $value->toArray();
            }
            else
            {
                $array[ $key ] = $value;
            }
        }
        return $array;
    }
}

edit 2:
The Config class can even be greatly simplified by extending ArrayObject. As an added benefit, you can cast it to a proper array also.

class Config
    extends ArrayObject
{
    protected $_data;

    public function __construct( array $data )
    {
        parent::__construct( array(), self::ARRAY_AS_PROPS );
        foreach( $data as $key => $value )
        {
            $this->$key = $value;
        }
    }

    public function offsetSet( $offset, $value )
    {
        $value = is_array( $value ) ? new self( $value ) : $value;

        return parent::offsetSet( $offset, $value );
    }
}

Example usage:

$configData = array(
    'some' => array(
        'deeply' => array(
            'nested' => array(
                'array' => array(
                    'some',
                    'data',
                    'here'
                )
            )
        )
    )
);
$config = new Config( $configData );
// casting to real array
var_dump( (array) $config->some->deeply->nested->array );

$config->some->deeply->nested->array = array( 'new', 'awsome', 'data', 'here' );
// Config object, but still accessible as array
var_dump( $config->some->deeply->nested->array[ 0 ] );

$config[ 'some' ][ 'deeply' ][ 'nested' ][ 'array' ] = array( 'yet', 'more', 'new', 'awsome', 'data', 'here' );
var_dump( $config[ 'some' ][ 'deeply' ][ 'nested' ][ 'array' ] );

$config[ 'some' ][ 'deeply' ][ 'nested' ][ 'array' ][] = 'append data';
var_dump( $config[ 'some' ][ 'deeply' ][ 'nested' ][ 'array' ] );

var_dump( isset( $config[ 'some' ][ 'deeply' ][ 'nested' ][ 'array' ] ) );

unset( $config[ 'some' ][ 'deeply' ][ 'nested' ][ 'array' ] );
var_dump( isset( $config[ 'some' ][ 'deeply' ][ 'nested' ][ 'array' ] ) );

// etc...
Decent Dabbler
  • 22,532
  • 8
  • 74
  • 106
5

I too have had this problem, and I solved this with this code. However it was based on API like: Config::set('paths.command.default.foo.bar').

<?php

$name = 'paths.commands.default';
$namespaces = explode('.', $name);

$current = &$this->data; // $this->data is your config-array
foreach ( $namespaces as $space )
{
    $current = &$current[$space];
}
$current = $value;

It is just looping through the array and holding track of the current value with a reference variable.

Erik Rothoff
  • 4,826
  • 9
  • 41
  • 59
0

I'd make a function with an indefinite amount of arguments, and use func_get_args() to get the arguments, from there, it's simply updating.

Madara's Ghost
  • 172,118
  • 50
  • 264
  • 308
0

Some time ago I needed a function that would let me access an array through string path, maybe you can make use of that:

function PMA_array_write($path, &$array, $value)
{
    $keys = explode('/', $path);
    $last_key = array_pop($keys);
    $a =& $array;
    foreach ($keys as $key) {
        if (! isset($a[$key])) {
            $a[$key] = array();
        }
        $a =& $a[$key];
    }
    $a[$last_key] = $value;
}

Example: PMA_array_write('onelevel/twolevel', $array, 'value');

piotrp
  • 3,755
  • 1
  • 24
  • 26
0

Well, you said you parse them into array. Why not parse them into stdObjects and then simply do $config->onelevel->twolevel = 'changed' as you want?:)

Quamis
  • 10,924
  • 12
  • 50
  • 66
0

You could either build a type on your own, that is providing the interface you're looking for or you go with the helper function you describe.

This is a code example of an override function Demo:

$array = array(
   'a' => array( 'b' => array( 'c' => 'value') ),
   'b' => array( 'a' => 'value' ),
);

function override($array, $value) {
    $args = func_get_args();
    $array = array_shift($args);
    $value = array_shift($args);
    $set = &$array;
    while(count($args))
    {
        $key = array_shift($args);
        $set = &$set[$key];
    }
    $set = $value;
    unset($set);
    return $array;
}

var_dump(override($array, 'new', 'a', 'b', 'c'));
hakre
  • 193,403
  • 52
  • 435
  • 836