PHP:修改多维数组最干净的方法?

时间:2022-10-28 16:14:55

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

我的应用程序中有一个Config类,它加载静态配置设置并将它们解析为数组。因为我需要在运行时覆盖一些元素,所以我需要通过这样做来访问Config类中的公共变量; $ config-> values ['onelevel'] ['twolevel'] ='已更改';

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.

我想创建一个名为override的方法为我做这个,但是我无法理解这是最好的方法,因为我的配置文件将来可能会有未知数量的嵌套级别。

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.

做一些像$ config-> onelevel-> twolevel ='changed'这样的东西会很可爱,让__set魔术方法来处理嵌套,但从我所知道的情况来看,这是不可能的。

What would be the best way to do this?

最好的方法是什么?

6 个解决方案

#1


6  

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.

这个例子受到Zend_Config和ArrayAccess接口上PHP文档中给出的示例的启发。

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 ->:

编辑:有一个小警告:您需要在表示数组的数据上调用toArray(),将其转换为数组,因为类内部需要将数组数据转换为自身的实例,以允许使用object属性运算符进行访问 - >:

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

呃,当然不再需要了,因为它现在实现了ArrayAccess。 ;-) /编辑

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.

编辑2:通过扩展ArrayObject,甚至可以大大简化Config类。作为额外的好处,您也可以将其转换为适当的数组。

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

#2


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

我也有这个问题,我用这个代码解决了这个问题。但它基于API,如: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.

它只是循环遍历数组并使用引用变量保持当前值的跟踪。

#3


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.

我使用无限量的参数创建一个函数,并使用func_get_args()来获取参数,从那里,它只是更新。

#4


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?:)

好吧,你说你把它们解析成数组。为什么不将它们解析为stdObjects,然后只需要根据需要执行$ config-> onelevel-> twolevel ='changed'?:)

#5


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:

这是覆盖函数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'));

#6


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

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

#1


6  

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.

这个例子受到Zend_Config和ArrayAccess接口上PHP文档中给出的示例的启发。

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 ->:

编辑:有一个小警告:您需要在表示数组的数据上调用toArray(),将其转换为数组,因为类内部需要将数组数据转换为自身的实例,以允许使用object属性运算符进行访问 - >:

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

呃,当然不再需要了,因为它现在实现了ArrayAccess。 ;-) /编辑

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.

编辑2:通过扩展ArrayObject,甚至可以大大简化Config类。作为额外的好处,您也可以将其转换为适当的数组。

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

#2


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

我也有这个问题,我用这个代码解决了这个问题。但它基于API,如: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.

它只是循环遍历数组并使用引用变量保持当前值的跟踪。

#3


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.

我使用无限量的参数创建一个函数,并使用func_get_args()来获取参数,从那里,它只是更新。

#4


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?:)

好吧,你说你把它们解析成数组。为什么不将它们解析为stdObjects,然后只需要根据需要执行$ config-> onelevel-> twolevel ='changed'?:)

#5


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:

这是覆盖函数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'));

#6


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

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