Массив: установить значение с помощью точечной нотации? - PullRequest
13 голосов
/ 21 октября 2011

Просматривая документацию по Kohana, я обнаружил эту действительно полезную функцию , которую они используют для получения значений из многомерного массива с использованием точечной нотации, например:

$foo = array('bar' => array('color' => 'green', 'size' => 'M'));
$value = path($foo, 'bar.color', NULL , '.');
// $value now is 'green'

Интересно,есть способ установить значение массива таким же образом:

set_value($foo, 'bar.color', 'black');

Единственный способ, который я нашел, это восстановить нотацию массива ($ array ['bar'] ['color']) и затем установите значение .. используя eval.

Есть идеи, чтобы избежать eval?

Ответы [ 7 ]

38 голосов
/ 21 октября 2011
function set_val(array &$arr, $path,$val)
{
   $loc = &$arr;
   foreach(explode('.', $path) as $step)
   {
     $loc = &$loc[$step];
   }
   return $loc = $val;
}
9 голосов
/ 21 октября 2011

Конечно, это возможно.

Код

function set_value(&$root, $compositeKey, $value) {
    $keys = explode('.', $compositeKey);
    while(count($keys) > 1) {
        $key = array_shift($keys);
        if(!isset($root[$key])) {
            $root[$key] = array();
        }
        $root = &$root[$key];
    }

    $key = reset($keys);
    $root[$key] = $value;
}

Как его использовать

$foo = array();
set_value($foo, 'bar.color', 'black');
print_r($foo);

Выходы

Array
(
    [bar] => Array
        (
            [color] => black
        )

)

Увидеть это в действии .

5 голосов
/ 13 ноября 2013

Посмотрите на https://gist.github.com/elfet/4713488

$dn = new DotNotation(['bar'=>['baz'=>['foo'=>true]]]);

$value = $dn->get('bar.baz.foo'); // $value == true

$dn->set('bar.baz.foo', false); // ['foo'=>false]

$dn->add('bar.baz', ['boo'=>true]); // ['foo'=>false,'boo'=>true]
4 голосов
/ 21 марта 2014

Таким образом, вы можете установить следующие значения более одного раза для одной и той же переменной.

Вы можете сделать эти два способа (по статической переменной и ссылочной переменной):

<?php



    function static_dot_notation($string, $value)
    {
        static $return;

        $token = strtok($string, '.');

        $ref =& $return;     

        while($token !== false)
        {  
            $ref =& $ref[$token];
            $token = strtok('.');
        }


        $ref = $value;

        return $return;
    }

    $test = static_dot_notation('A.1', 'A ONE');
    $test = static_dot_notation('A.2', 'A TWO');
    $test = static_dot_notation('B.C1', 'C ONE');
    $test = static_dot_notation('B.C2', 'C TWO');
    $test = static_dot_notation('B.C.D', 'D ONE');

    var_export($test);

    /**
        array (
          'A' => 
              array (
                1 => 'A ONE',
                2 => 'A TWO',
              ),
          'B' => 
              array (
                'C1' => 'C ONE',
                'C2' => 'C TWO',
                'C' => 
                array (
                  'D' => 'D ONE',
                ),
          ),

    */



    function reference_dot_notation($string, $value, &$array)
    {
        static $return;

        $token = strtok($string, '.');

        $ref =& $return;     

        while($token !== false)
        {  

            $ref =& $ref[$token];
            $token = strtok('.');
        }

        $ref = $value;
        $array = $return;
    }

    reference_dot_notation('person.name', 'Wallace', $test2);
    reference_dot_notation('person.lastname', 'Maxters', $test2);

    var_export($test2);

    /**
        array (
          'person' => 
          array (
            'name' => 'Wallace',
            'lastname' => 'Maxters',
          ),
        )

    */
3 голосов
/ 27 марта 2013

Я создал небольшой класс только для этого!

http://github.com/projectmeta/Stingray

$stingray = new StingRay();

//To Get value
$stingray->get($array, 'this.that.someother'):

//To Set value
$stingray->get($array, 'this.that.someother', $newValue):
0 голосов
/ 22 сентября 2018

Ни один из приведенных здесь примеров не работал для меня, поэтому я нашел решение, использующее eval () (о рисках здесь , но если вы не используете пользовательские данные, оно не должнобыть большой проблемой).Предложение if в методе set позволяет вам поместить ваш элемент в новый или существующий массив в этом месте ($ location [] = $ item).

class ArrayDot {
    public static function get(array &$array, string $path, string $delimiter = '.') {
        return eval("return ".self::getLocationCode($array, $path, $delimiter).";");
    }

    public static function set(array &$array, string $path, $item, string $delimiter = '.') : void {
        //if the last character is a delimiter, allow pushing onto a new or existing array
        $add = substr($path, -1) == $delimiter ? '[]': '';
        eval(self::getLocationCode($array, $path, $delimiter).$add." = \$item;");
    }

    public static function unset(array &$array, $path, string $delimiter = '.') : void {
        if (is_array($path)) {
            foreach($path as $part) {
                self::unset($array, $part, $delimiter);
            }
        }
        else {
            eval('unset('.self::getLocationCode($array, $path, $delimiter).');');
        }
    }

    public static function isSet(array &$array, $path, string $delimiter = '.') : bool {
        if (is_array($path)) {
            foreach($path as $part) {
                if (!self::isSet($array, $part, $delimiter)) {
                    return false;
                }
            }
            return true;
        }
        return eval("return isset(".self::getLocationCode($array, $path, $delimiter).");");
    }

    private static function getLocationCode(array &$array, string $path, string $delimiter) : string {
        $path = rtrim($path, $delimiter);           //Trim trailing delimiters
        $escapedPathParts = array_map(function ($s) { return str_replace('\'', '\\\'', $s); }, explode($delimiter, $path));
        return "\$array['".implode("']['", $escapedPathParts)."']";
    }
}

Пример использования:

echo '<pre>';
$array = [];
ArrayDot::set($array, 'one.two.three.', 'one.two.three.');
ArrayDot::set($array, 'one.two.three.four.', 'one.two.three.four.');
ArrayDot::set($array, 'one.two.three.four.', 'one.two.three.four. again');
ArrayDot::set($array, 'one.two.three.five.', 'one.two.three.five.');
ArrayDot::set($array, 'one.two.three.direct set', 'one.two.three.direct set');

print_r($array);
echo "\n";
echo "one.two.three.direct set: ".print_r(ArrayDot::get($array, 'one.two.three.direct set'), true)."\n";
echo "one.two.three.four: ".print_r(ArrayDot::get($array, 'one.two.three.four'), true)."\n";

Выход:

Array
(
    [one] => Array
        (
            [two] => Array
                (
                    [three] => Array
                        (
                            [0] => one.two.three.
                            [four] => Array
                                (
                                    [0] => one.two.three.four.
                                    [1] => one.two.three.four. again
                                )

                            [five] => Array
                                (
                                    [0] => one.two.three.five.
                                )

                            [direct set] => one.two.three.direct set
                        )

                )

        )

)

one.two.three.direct set: one.two.three.direct set
one.two.three.four: Array
(
    [0] => one.two.three.four.
    [1] => one.two.three.four. again
)
0 голосов
/ 15 февраля 2016

Обновлен ответ @hair resins для удовлетворения:

  • Когда подпуть уже существует или
  • Когда подпуть не является массивом

    function set_val(array &$arr, $path,$val)
    {
       $loc = &$arr;
       $path = explode('.', $path);
       foreach($path as $step)
       {
           if ( ! isset($loc[$step]) OR ! is_array($loc[$step]))
               $loc = &$loc[$step];
       }
       return $loc = $val;
    }
    
...