我使用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
递归到的数组?
答案 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方法。