38. 数组操作
在drupal中关于数组的操作需求有很多是php没有直接提供的,但它们又被普遍使用,因此drupal自带了几个数组操作的类,在本系列之前写过一篇以数组合并为主题的分享,本篇将介绍其他几种类型的数组操作,它们是:
多维嵌套数组处理:Drupal\Component\Utility\ NestedArray
递归计算数组差集:Drupal\Component\Utilit\ DiffArray
对数组进行排序:Drupal\Component\Utility\SortArray
类文件位于:\core\lib\Drupal\Component\Utility中,值得说明的是在这个文件夹中的类都是web开发常用的实用工具类,开发者可以轻易用于其他项目中,节约很多时间。
首先说说多维嵌套数组处理类:Drupal\Component\Utility\ NestedArray
类方法全是以静态方法提供,方便使用,先来看一个需求:
在某对象中以$this->get(“a.b.c”)来获取$this->data[“a”][“b”][“c”]怎么实现?在配置系统中就用到了该特性,关键注意点是如a.b.c这样的路径深度是可变的,也就是说数组可以是多层嵌套的,这就是NestedArray::getValue方法的作用:
源文件如下:
public static function &getValue(array &$array, array $parents, &$key_exists = NULL) {
$ref = &$array;
foreach ($parents as $parent) {
if (is_array($ref) && array_key_exists($parent, $ref)) {
$ref = &$ref[$parent];
}
else {
$key_exists = FALSE;
$null = NULL;
return $null;
}
}
$key_exists = TRUE;
return $ref;
}
第一个参数是要查找的数组
可以将前文提到的a.b.c以点为分隔符拆分为数组作为该方法的第二个参数就可以得到它的值了。注意如果该参数为空数组将返回整个原数组。
第三个参数什么意思呢?该方法当值不存在时将返回NULL,但我们怎么知道是值不存在还是存在但值为NULL呢?这就是第三个参数的作用,当值存在时它会被设置为true,它以引用方式传递,不需要关注该区别可不传递。如果第二个参数为空数组,该参数也会被设置为true
有get就有set,对应的方法为:
public static function setValue(array &$array, array $parents, $value, $force = FALSE) {
$ref = &$array;
foreach ($parents as $parent) {
// PHP auto-creates container arrays and NULL entries without error if $ref
// is NULL, but throws an error if $ref is set, but not an array.
if ($force && isset($ref) && !is_array($ref)) {
$ref = [];
}
$ref = &$ref[$parent];
}
$ref = $value;
}
前两个参数和get方法意义相同,第三个为要设置的值,第四个参数$force是什么意思呢?
如前文对a.b.c赋值,即对$this->data[“a”][“b”][“c”]赋值,此时如果$this->data[“a”]的值是一个非数组值,而不是多维数组,那么怎么处理?是选择覆盖呢还是报错呢?这就是$force的作用,为true时总是覆盖,原来的$this->data[“a”]值将丢失,采用新的覆盖后的值,如果为false那么php将产生一个错误,错误类型如下列代码所示:
$yunke="love";
$ref=&$yunke["ok"];
$ref="you";
该方法$force默认为false,这样如果被赋值数组不符合结构要求,那么会产生以上错误,设置为true将总是覆盖,不会产生错误。
取消一个数组键:
public static function unsetValue(array &$array, array $parents, &$key_existed = NULL) {
$unset_key = array_pop($parents);
$ref = &self::getValue($array, $parents, $key_existed);
if ($key_existed && is_array($ref) && array_key_exists($unset_key, $ref)) {
$key_existed = TRUE;
unset($ref[$unset_key]);
}
else {
$key_existed = FALSE;
}
}
注意如果第二个参数为空数组,那么原数组不受影响,整个方法使用到了一个编程技巧,如下:
$a=["x"=>"a","b"];
$b=&$a["x"];
unset($b);
这样是没有用的,只是取消引用变量$b,变量$a并不受影响,但如果这样:
$a=["x"=>"a","b"];
$b=&$a;
unset($b["x"]);
那么就有效了,原变量$a将改变
判断数组中时候存在某个键:
public static function keyExists(array $array, array $parents) {
// Although this function is similar to PHP's array_key_exists(), its
// arguments should be consistent with getValue().
$key_exists = NULL;
self::getValue($array, $parents, $key_exists);
return $key_exists;
}
这个方法比较简单,完全利用了getValue方法,不多讲
数组合并:
public static function mergeDeepArray(array $arrays, $preserve_integer_keys = FALSE) {
$result = [];
foreach ($arrays as $array) {
foreach ($array as $key => $value) {
// Renumber integer keys as array_merge_recursive() does unless
// $preserve_integer_keys is set to TRUE. Note that PHP automatically
// converts array keys that are integer strings (e.g., '1') to integers.
if (is_integer($key) && !$preserve_integer_keys) {
$result[] = $value;
}
// Recurse when both values are arrays.
elseif (isset($result[$key]) && is_array($result[$key]) && is_array($value)) {
$result[$key] = self::mergeDeepArray([$result[$key], $value], $preserve_integer_keys);
}
// Otherwise, use the latter value, overriding any previous value.
else {
$result[$key] = $value;
}
}
}
return $result;
}
(数组合并可见本系列专门的主题,此处仅摘录过来)将多个要合并的数组组成一个数组作为第一个参数,它和php的array_merge_recursive函数类似,但又有区别,相似点是都是递归合并,不同点是可以指定参数,且对值有判断。
参数:$preserve_integer_keys指定了对数字索引具备的行为,默认为false,此时数字索引不会前或后覆盖,而是全部保留,并重新索引,和array_merge及array_merge_recursive函数行为一样,如果指定为TRUE,那么将数字索引完全等同于非数字索引。
非数字索引的处理体现了和array_merge_recursive函数的区别:
drupal实现是:相同键名间他们的值都为数组,则进行递归合并,否则只要有一个不是数组,将用后面的值覆盖前面的值。
如:
$a = ["a" => 1,"a"];
$b = ["a" => 4,"b"];
$c = array_merge_recursive($a, $b);的结果是:
Array
(
[a] => Array
(
[0] => 1
[1] => 4
)
[0] => a
[1] => b
)
$c = NestedArray::mergeDeepArray([$a, $b],false);的结果是:
Array
(
[a] => 4
[0] => a
[1] => b
)
$c = NestedArray::mergeDeepArray([$a, $b],true);的结果是:
Array
(
[a] => 4
[0] => b
)
当值为数组时,行为如下:
$a = ["a" => [1,2],"a"];
$b = ["a" => [3,4],"b"];
$c = array_merge_recursive($a, $b);时:
Array
(
[a] => Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
)
[0] => a
[1] => b
)
$c = NestedArray::mergeDeepArray([$a, $b],false);时和array_merge_recursive输出一致:
Array
(
[a] => Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
)
[0] => a
[1] => b
)
$c = NestedArray::mergeDeepArray([$a, $b],true);时
Array
(
[a] => Array
(
[0] => 3
[1] => 4
)
[0] => b
)
递归过滤数组:
public static function filter(array $array, callable $callable = NULL) {
$array = is_callable($callable) ? array_filter($array, $callable) : array_filter($array);
foreach ($array as &$element) {
if (is_array($element)) {
$element = static::filter($element, $callable);
}
}
return $array;
}
Php的数组过滤函数不是递归的,如果没有提供 callback 函数,将删除 array 中所有等值为 FALSE 的条目,等值使用松散比较,不是严格比较。
递归计算数组差集:Drupal\Component\Utilit\DiffArray
Php提供了一些计算数组差集的函数,这个类DiffArray是array_diff_assoc()的一个变种,array_diff_assoc()官方文档可以看http://www.php.net/manual/zh/function.array-diff-assoc.php,它是带键名比较数组差集,也就是键名和键值均相等才认为相等而去除,但对于多维数组仅仅提供第一维的比较,而这个类将进行递归比较。
对数组进行排序:Drupal\Component\Utility\SortArray
这个类用于对数组元素排序,和drupal关联是比较紧密的,在drupal系统中经常需要对列表按权重或标题进行排序,这个类就是为此而设计,实现比较简单,不细讲,值得注意的是没有指定权重,那么默认为0,对于标题的比较是使用php的strnatcasecmp($a_title, $b_title)函数,使用“自然顺序”算法比较字符串(不区分大小写)。