【问题标题】:Using a string path to set nested array data [duplicate]使用字符串路径设置嵌套数组数据[重复]
【发布时间】:2019-08-27 23:42:27
【问题描述】:

我有一个不寻常的用例,我正在尝试为其编码。目标是这样的:我希望客户能够提供一个字符串,例如:

"cars.honda.civic = On"

使用此字符串,我的代码将设置一个值,如下所示:

$data['cars']['honda']['civic'] = 'On';

将客户输入标记化很容易:

$token = explode("=",$input);
$value = trim($token[1]);
$path = trim($token[0]);
$exploded_path = explode(".",$path);

但是现在,我如何使用 $exploded path 来设置数组而不做像 eval 这样讨厌的事情?

【问题讨论】:

    标签: php arrays token


    【解决方案1】:

    使用引用运算符获取连续的现有数组:

    $temp = &$data;
    foreach($exploded as $key) {
        $temp = &$temp[$key];
    }
    $temp = $value;
    unset($temp);
    

    【讨论】:

    • 有没有办法获得一个值而不是设置?
    • @MaraisRossouw 你的评论/问题是旧的,但看看stackoverflow.com/a/36042293/1371433 它作为一个 getter 和/或 setter
    • 最后两行不是多余的吗?就像如果我们需要取消设置$temp 那么为什么还要将它设置在上面一行呢?
    • @MohdAbdulMujib $tempreference,最后一行写入引用变量(在本例中是嵌套数组项),最后一行删除引用,因此 $temp 未链接到那个变量。
    • 啊……明白了。感谢您的解释。
    【解决方案2】:

    基于alexisdm's response

    /**
     * Sets a value in a nested array based on path
     * See https://stackoverflow.com/a/9628276/419887
     *
     * @param array $array The array to modify
     * @param string $path The path in the array
     * @param mixed $value The value to set
     * @param string $delimiter The separator for the path
     * @return The previous value
     */
    function set_nested_array_value(&$array, $path, &$value, $delimiter = '/') {
        $pathParts = explode($delimiter, $path);
    
        $current = &$array;
        foreach($pathParts as $key) {
            $current = &$current[$key];
        }
    
        $backup = $current;
        $current = $value;
    
        return $backup;
    }
    

    【讨论】:

    • 小调整:如果路径上的“节点”之一已经设置但不是数组,则此功能将致命。 $a = ['foo'=>'not an array']; set_nested_array($a, 'foo/bar', 'new value');修复:首先插入foreach的第一行if (!is_array($current)) { $current = array(); }
    【解决方案3】:

    经过良好测试且 100% 有效的代码。使用“parents”从数组中设置、获取、取消设置值。父母可以是array('path', 'to', 'value') 或字符串path.to.value。基于Drupal的代码

     /**
     * @param array $array
     * @param array|string $parents
     * @param string $glue
     * @return mixed
     */
    function array_get_value(array &$array, $parents, $glue = '.')
    {
        if (!is_array($parents)) {
            $parents = explode($glue, $parents);
        }
    
        $ref = &$array;
    
        foreach ((array) $parents as $parent) {
            if (is_array($ref) && array_key_exists($parent, $ref)) {
                $ref = &$ref[$parent];
            } else {
                return null;
            }
        }
        return $ref;
    }
    
    /**
     * @param array $array
     * @param array|string $parents
     * @param mixed $value
     * @param string $glue
     */
    function array_set_value(array &$array, $parents, $value, $glue = '.')
    {
        if (!is_array($parents)) {
            $parents = explode($glue, (string) $parents);
        }
    
        $ref = &$array;
    
        foreach ($parents as $parent) {
            if (isset($ref) && !is_array($ref)) {
                $ref = array();
            }
    
            $ref = &$ref[$parent];
        }
    
        $ref = $value;
    }
    
    /**
     * @param array $array
     * @param array|string $parents
     * @param string $glue
     */
    function array_unset_value(&$array, $parents, $glue = '.')
    {
        if (!is_array($parents)) {
            $parents = explode($glue, $parents);
        }
    
        $key = array_shift($parents);
    
        if (empty($parents)) {
            unset($array[$key]);
        } else {
            array_unset_value($array[$key], $parents);
        }
    }
    

    【讨论】:

    • 感谢您发布此信息!我刚刚开始编写自己的版本,用于自定义 Drupal 模块大声笑我不知道这是核心。
    【解决方案4】:
    $data = $value;
    foreach (array_reverse($exploded_path) as $key) {
        $data = array($key => $data);
    }
    

    【讨论】:

    • 除非您使用类似 array_merge_recursive 的内容,否则您将替换 $data 已包含的先前值。
    • 这实际上是一个好点,假设 $data 已经包含值。
    【解决方案5】:

    基于Ugo Méda's response

    这个版本

    • 允许您仅将其用作吸气剂(保持源数组不变)
    • 修复了遇到非数组值时的致命错误问题 (Cannot create references to/from string offsets nor overloaded objects)

    没有致命错误示例

    $a = ['foo'=>'not an array'];
    arrayPath($a, ['foo','bar'], 'new value');
    

    $a现在

    array(
        'foo' => array(
            'bar' => 'new value',
        ),
    )
    

    用作吸气剂

    $val = arrayPath($a, ['foo','bar']);  // returns 'new value' / $a remains the same
    

    将值设置为空

    $v = null; // assign null to variable in order to pass by reference
    $prevVal = arrayPath($a, ['foo','bar'], $v);
    

    $prevVal 是“新值”
    $a现在

    array(
        'foo' => array(
            'bar' => null,
        ),
    )
    

     

    /**
     * set/return a nested array value
     *
     * @param array $array the array to modify
     * @param array $path  the path to the value
     * @param mixed $value (optional) value to set
     *
     * @return mixed previous value
     */
    function arrayPath(&$array, $path = array(), &$value = null)
    {
        $args = func_get_args();
        $ref = &$array;
        foreach ($path as $key) {
            if (!is_array($ref)) {
                $ref = array();
            }
            $ref = &$ref[$key];
        }
        $prev = $ref;
        if (array_key_exists(2, $args)) {
            // value param was passed -> we're setting
            $ref = $value;  // set the value
        }
        return $prev;
    }
    

    【讨论】:

    • 您可以选择检查路径是否为字符串,并使用 explode 转换为数组,例如。 $path = explode('.', $path); 所以你可以使用流行的点符号,例如。 $val = arrayPath($a, 'foo.bar');
    • “PHP 致命错误:只能通过引用传递变量”arrayPath($a, ['foo','bar'], 'new value');
    【解决方案6】:

    你需要使用 Symfony PropertyPath

    <?php
    // ...
    $person = array();
    
    $accessor->setValue($person, '[first_name]', 'Wouter');
    
    var_dump($accessor->getValue($person, '[first_name]')); // 'Wouter'
    // or
    // var_dump($person['first_name']); // 'Wouter'
    

    【讨论】:

    • 正是我需要的!
    • 这个解决方案的问题,如果不存在,它不会创建属性
    【解决方案7】:

    这正是 this method 的用途:

    Arr::set($array, $keys, $value);
    

    它将您的 $array 放在应该设置元素的位置,并以点分隔格式或后续键数组接受 $keys

    因此,在您的情况下,您只需通过以下方式即可获得所需的结果:

    $data = Arr::set([], "cars.honda.civic", 'On');
    
    // Which will be equivalent to
    $data = [
      'cars' => [
        'honda' => [
          'civic' => 'On',
        ],
      ],
    ];
    

    更重要的是,$keys参数也可以接受创建自动索引,所以你可以像这样使用它:

    $data = Arr::set([], "cars.honda.civic.[]", 'On');
    
    // In order to get
    $data = [
      'cars' => [
        'honda' => [
          'civic' => ['On'],
        ],
      ],
    ];
    
    

    【讨论】:

      【解决方案8】:

      你就不能这样做吗

      $exp = explode(".",$path);
      $array[$exp[0]][$exp[1]][$exp[2]] = $value
      

      【讨论】:

      • 我只是假设$exp的数量不固定
      • deceze 是正确的——我不能假设我会知道客户将提供多少个节点。不过,感谢您的反馈。
      猜你喜欢
      • 2012-03-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多