我怎样才能获得array_replace_recursive来将数字索引数组视为值?

时间:2014-12-30 16:43:02

标签: php arrays

我使用array_replace_recursive作为创建级联配置系统的方法,但我遇到了一个问题:它将数字索引数组视为要递归的数组,而不是值:

$a = [
   'tournaments' => [
       'modes' => ['single', 'double', 'round-robin']
   ]
];

$b = [
   'tournaments' => [
       'modes' => ['single']
   ]
];

$c = [
   'tournaments' => [
       'modes' => ['double']
   ]
];

$result = array_replace_recursive($a, $b, $c);

返回

[
   'tournaments' => [
        0 => 'double',
        1 => 'double',
        2 => 'round-robin'
    ]
]

我真正想要的是什么:

[
   'tournaments' => [
        0 => 'double'
    ]
]

我可以自己编写array_replace_recursive的替代方法,但我也希望保持变量,这变得更加复杂。有没有办法将'modes'设置视为简单"列表"值而不是array_replace_recusive递归到的数组?

编辑:我觉得这是一种情况,PHP阵列的一体化灵活性实际上是一种损害而不是一种好处。如果PHP有一个简单的列表被视为一个简单的值(但仍然可以迭代),那将会非常好。

1 个答案:

答案 0 :(得分:1)

让我举一个例子,说明如何使用适当的对象和数组组合可以使这更简单。在这里,我向您展示了一组配置的JSON表示,因为从文件读取JSON配置或者已经将字符串值设置为某个变量并使用json_decode()转换为PHP对象是微不足道的。

{
    "tournaments": {
        "modes": [
            "single",
            "double",
            "round-robin"]
        ],
        "second_level_property": {
            "third_level_property": "foo"
        }
    },
    "first_level_property": "bar"
}

并且假设还有两个本地配置覆盖,如下所示,并打算按以下顺序应用:

{
   "tournaments": {
        "modes": ["single"]
   }
}

{    
   "tournaments": {
        "modes": [
            "single",
            "round-robin"
        ],
        "second_level_property": null
   },
   "first_level_property": "baz"
}

最终配置应如下所示:

{
    "tournaments": {
        "modes": [
            "single",
            "round-robin"
        ],
        "second_level_property": null
    },
    "first_level_property": "baz"
}

让我们看看我们如何在PHP中实现这一目标。首先,我们创建一个简单的函数来合并stdClass对象(在json_decode上创建的对象类)。

function stdClass_object_merge(stdClass $a, stdClass $b) {
    foreach ($b as $k => $v) {
        if ($v instanceof stdClass && isset($a->$k) && $a->$k instanceof stdClass) {
            // both reference and mergin objects have stdClass objects for this property
            // so we want to recursively merge these objects at this property
            $a->$k = stdClass_object_merge($a->$k, $v);
        } else {
            // this property may or may not be present on reference object
            // but in either case, we want to overwrite the value in reference object
            // with value from merging object
            $a->$k = $v;
        }
    }
    return $a;
}

请注意,此函数仅在相同属性键存在嵌套对象的情况下递归。它还将始终将合并对象(参数$ b)中的值视为权威,在引用数组中的相同属性上覆盖值,或者如果在引用数组上缺少属性,则添加属性。这意味着只有stdClass对象才能充当"节点"在配置中,并且所有其他数据类型都被视为"离开",如果在合并对象中传递非节点值,其中存在节点值,则可能会切断参考配置中的分支在引用对象中(就像在上面的合并示例中对锦标赛 - > second_level_property值所做的那样)..

用法变得非常简单:

// read default config into stdClass object from file
$config = json_decode(file_get_contents('/path/to/default_config.json'));

// specify local override to configuration and merge that config to existing
$local_config_1 = json_decode(file_get_contents('/path/to/local_config_1.json'));
$config = stdClass_object_merge($config, $local_config_1);

// merge another config    
$local_config_2 = json_decode(file_get_contents('/path/to/local_config_2.json'));
$config = stdClass_object_merge($config, $local_config_2);

虽然我基于它灵活的简洁语法对JSON略有偏见,但实际上你基本上可以在基于PHP的定义中做你现在正在做的事情。他们只是变得更加冗长。以上在PHP中完成的默认配置示例如下:

$config = new stdClass;
$config->tournaments->modes = [
    "single",
    "double",
    "round-robin"
]
$config->tournaments->second_level_property->third_level_property = 'foo';
$config->first_level_property = 'bar';

对我而言,这似乎并没有在视觉上传达配置的整体结构。我也偏向于拥有配置文件,这些配置文件本身需要代码来设置配置,并且能够将配置放入单独的非可执行文件中,这很容易使用JSON方法。