PHP 数组过滤
最后修改于 2025 年 3 月 13 日
PHP 提供了强大的工具来过滤数组和对象,例如 array_filter、array_map 和 array_reduce。本教程通过实际例子来探讨这些函数。
基本数组过滤
array_filter 函数使用回调函数过滤数组元素,返回一个满足条件的新数组。
<?php $numbers = [1, 2, 3, 4, 5, 6]; $evenNumbers = array_filter($numbers, fn($num) => $num % 2 === 0); print_r($evenNumbers);
这使用箭头函数从 $numbers 中过滤出偶数,语法简洁。array_filter 函数遍历 $numbers 数组中的每个元素(这里是整数 1 到 6),并应用回调函数。箭头函数 fn($num) => $num % 2 === 0 检查该数字是否能被 2 整除且没有余数,从而识别出偶数。
只有返回 true 的元素才会被包含在新数组中。结果,存储在 $evenNumbers 中,是 [2, 4, 6]。请注意,原始数组保持不变,并且索引在输出中被保留,尽管由于这是一个数值数组,因此会按顺序重新索引。
使用键进行过滤
使用 array_filter 和 ARRAY_FILTER_USE_BOTH 根据键和值同时进行过滤。
<?php
$data = ["a" => 1, "b" => 2, "c" => 3, "d" => 4];
$filtered = array_filter($data, fn($v, $k) => $k === "b" || $v > 2,
ARRAY_FILTER_USE_BOTH);
print_r($filtered);
这保留了键为 "b" 或值大于 2 的元素。ARRAY_FILTER_USE_BOTH 标志告诉 array_filter 将值和键都传递给回调函数。这里,关联数组 $data 具有映射到整数的字符串键 ("a", "b", "c", "d")。
回调函数 fn($v, $k) => $k === "b" || $v > 2 评估每个元素。对于 "a" => 1,键不是 "b" 且 1 <= 2,因此它被排除。对于 "b" => 2,键匹配 "b",因此它被包含,尽管该值没有超过 2。对于 "c" => 3 和 "d" => 4,这些值超过 2,因此它们被包含。输出是 ["b" => 2, "c" => 3, "d" => 4],保留了原始键。
使用 array_map
array_map 通过回调函数转换每个数组元素,返回一个具有修改值的新数组。
<?php $numbers = [1, 2, 3, 4]; $squared = array_map(fn($num) => $num * $num, $numbers); print_r($squared);
这会计算 $numbers 中每个数字的平方。array_map 函数将回调函数应用于输入数组中的每个元素,这里是 [1, 2, 3, 4]。箭头函数 fn($num) => $num * $num 将每个数字乘以它本身,将 1 转换为 1,将 2 转换为 4,将 3 转换为 9,将 4 转换为 16。
结果,存储在 $squared 中,是 [1, 4, 9, 16]。与根据条件减少数组大小的 array_filter 不同,array_map 始终返回一个相同长度的数组,其中每个元素都被回调函数的返回值替换。原始数组保持不变。
使用属性提升过滤对象
PHP 8.0+ 支持构造函数属性提升。使用 array_filter 过滤此类对象。
<?php
class Product {
public function __construct(
public string $name,
public float $price) {}
}
$products = [
new Product("Laptop", 1000),
new Product("Phone", 500),
new Product("Tablet", 300)
];
$affordable = array_filter($products, fn($p) => $p->price < 800);
print_r($affordable);
这会过滤价格低于 800 的产品。Product 类使用构造函数属性提升,这是一个 PHP 8.0+ 的特性,它在构造函数中直接声明和分配公共属性。$products 数组中的每个 Product 对象都有一个 $name 和 $price。
array_filter 回调函数 fn($p) => $p->price < 800 访问每个对象的 $price 属性。"Laptop" (1000) 被排除,因为 1000 >= 800,而 "Phone" (500) 和 "Tablet" (300) 被包含,因为它们的价格低于 800。输出是一个包含这两个对象的数组,保留了它们的原始索引(1 和 2)。
结合 array_filter 和 array_map
结合 array_filter 和 array_map,在一个流程中过滤和转换数据。
<?php
$numbers = [1, 2, 3, 4, 5, 6];
$result = array_map(fn($num) => $num * 2,
array_filter($numbers, fn($num) => $num % 2 === 0));
print_r($result);
过滤偶数,然后将它们加倍。此示例嵌套了两个函数。首先,使用 fn($num) => $num % 2 === 0 的 array_filter 处理 $numbers,仅保留偶数 — 2、4 和 6 — 产生一个中间数组 [2, 4, 6]。
接下来,array_map 接受这个过滤后的数组并应用 fn($num) => $num * 2,将每个值加倍:2 变为 4,4 变为 8,6 变为 12。$result 中的最终结果是 [4, 8, 12]。这种链式操作对于多步骤转换非常有效,并且原始数组保持不变。
使用 array_reduce
array_reduce 通过迭代回调应用将数组缩减为单个值。
<?php $numbers = [1, 2, 3, 4, 5]; $sum = array_reduce($numbers, fn($carry, $num) => $carry + $num, 0); echo "Sum: $sum";
这会计算 $numbers 中所有数字的总和。array_reduce 函数遍历 [1, 2, 3, 4, 5],使用一个接受两个参数的回调函数:$carry(累积结果)和 $num(当前元素)。$carry 的初始值设置为 0。
在每次迭代中,$carry + $num 更新总数:0 + 1 = 1, 1 + 2 = 3, 3 + 3 = 6, 6 + 4 = 10, 10 + 5 = 15。$sum 的最终值是 15,输出为 "Sum: 15"。此方法适用于总和、乘积或自定义缩减等聚合。
过滤关联数组
使用 array_filter 和自定义条件过滤关联数组。
<?php
$users = [
["name" => "John", "age" => 25],
["name" => "Jane", "age" => 30],
["name" => "Doe", "age" => 17]
];
$adults = array_filter($users, fn($user) => $user["age"] >= 18);
print_r($adults);
过滤 18 岁或以上的用户。$users 数组包含关联数组,每个数组代表一个具有 "name" 和 "age" 键的用户。array_filter 回调函数 fn($user) => $user["age"] >= 18 检查每个子数组的 "age" 值。
对于 "John" (年龄 25) 和 "Jane" (年龄 30),条件为真,因此它们被包含。对于 "Doe" (年龄 17),它为假,因此它被排除。$adults 中的结果是一个包含两个元素的数组 — 保留索引 0 和 1 — 仅显示成年用户。这对于过滤结构化数据(如记录)很有用。
按多个条件过滤对象
使用提升的属性按多个条件过滤对象。
<?php
class Item {
public function __construct(
public string $name,
public float $price,
public int $stock) {}
}
$items = [
new Item("Laptop", 1000, 5),
new Item("Phone", 500, 10),
new Item("Tablet", 300, 0)
];
$inStock = array_filter($items, fn($i) => $i->price < 800 && $i->stock > 0);
print_r($inStock);
过滤价格低于 800 且有库存的商品。Item 类使用属性提升来定义 $name、$price 和 $stock。$items 数组包含三个代表具有这些属性的对象的对象。
回调函数 fn($i) => $i->price < 800 && $i->stock > 0 应用两个条件:价格低于 800 且库存高于 0。"Laptop" (1000, 5) 未通过价格检查,"Tablet" (300, 0) 未通过库存检查,但 "Phone" (500, 10) 通过了这两个检查。$inStock 中的输出是一个仅包含索引 1 处的 "Phone" 对象的数组。
过滤唯一值
将 array_unique 与 array_map 一起使用,以过滤唯一的转换值。
<?php $values = [1, 2, 2, 3, 3, 4]; $uniqueSquares = array_unique(array_map(fn($v) => $v * $v, $values)); print_r($uniqueSquares);
计算平方值并删除重复项。首先,array_map 和 fn($v) => $v * $v 将 [1, 2, 2, 3, 3, 4] 转换为 [1, 4, 4, 9, 9, 16]。每个元素都被平方,因此 2 和 2 等重复项都变为 4,3 和 3 都变为 9。
然后,array_unique 删除重复值,仅保留第一次出现的元素:[1, 4, 9, 16]。$uniqueSquares 中的结果是一个具有唯一平方值的重新索引的数组。这种组合对于数据规范化任务非常有用。
使用 array_reduce 过滤对象
使用 array_reduce 将对象过滤到一个新数组中。
<?php
class User {
public function __construct(public string $name, public int $age) {}
}
$users = [
new User("John", 25),
new User("Jane", 30),
new User("Doe", 17)
];
$adults = array_reduce($users, fn($carry, $u) =>
$u->age >= 18 ? [...$carry, $u] : $carry, []);
print_r($adults);
累积成年用户。$users 数组包含具有提升属性的 User 对象。array_reduce 函数从一个空数组 ([]) 作为初始的 $carry 值开始。
回调函数使用三元运算符:如果 $u->age >= 18,则使用展开运算符 (...) 将当前用户追加到 $carry;否则,$carry 保持不变。"John" (25) 和 "Jane" (30) 被添加,但 "Doe" (17) 被跳过。$adults 中的结果是一个由两个成年用户对象组成的数组。
使用 array_walk 进行过滤
array_walk就地修改元素,通常与 array_filter 配对使用。
<?php $numbers = [1, 2, 3, 4, 5, 6]; array_walk($numbers, fn(&$num) => $num = $num % 2 === 0 ? $num : null); $numbers = array_filter($numbers); print_r($numbers);
通过将奇数设置为 null 来保留偶数。array_walk 函数将回调函数应用于 $numbers 中的每个元素,直接修改它,因为 $num 是通过引用传递的(使用 &)。三元运算符将偶数设置为它们本身,将奇数设置为 null。
在 array_walk 之后,$numbers 变为 [null, 2, null, 4, null, 6]。然后,array_filter 删除所有 null 值,在 $numbers 中留下 [2, 4, 6]。这种方法会修改原始数组,这与纯 array_filter 不同。
过滤嵌套数组
使用递归逻辑和 array_filter 过滤嵌套数组。
<?php
$data = [1, [2, 3], [4, [5, 6]]];
$flatten = fn($arr) => array_merge(...array_map(fn($v) =>
is_array($v) ? $flatten($v) : [$v], $arr));
$evens = array_filter($flatten($data), fn($n) => $n % 2 === 0);
print_r($evens);
展平和过滤偶数。$data 数组具有嵌套的子数组。$flatten 函数使用 array_map 和 array_merge 递归地展平它。对于每个值,如果它是一个数组,则再次调用 $flatten;如果不是,则将其包装在一个数组中。
应用 $flatten($data) 会产生 [1, 2, 3, 4, 5, 6]。然后,使用 fn($n) => $n % 2 === 0 的 array_filter 保留偶数,产生存储在 $evens 中的 [2, 4, 6]。这非常适合处理分层数据结构。
使用 array_column 过滤
从多维数组中提取和过滤特定的列。
<?php
$users = [
["id" => 1, "name" => "John", "age" => 25],
["id" => 2, "name" => "Jane", "age" => 30],
["id" => 3, "name" => "Doe", "age" => 17]
];
$adultNames = array_filter(array_column($users, "name", "age"),
fn($n, $a) => $a >= 18, ARRAY_FILTER_USE_BOTH);
print_r($adultNames);
提取成年人的姓名。array_column 函数从 $users 中提取 "name" 列,使用 "age" 作为索引,生成 [25 => "John", 30 => "Jane", 17 => "Doe"]。这创建了一个将年龄映射到姓名的关联数组。
然后,使用 ARRAY_FILTER_USE_BOTH 的 array_filter 过滤此数组,保留键(年龄)为 18 或更大的条目。回调函数 fn($n, $a) => $a >= 18 排除 17 => "Doe",在 $adultNames 中留下 [25 => "John", 30 => "Jane"]。这有效地结合了提取和过滤。
按年龄过滤用户
根据出生日期计算年龄,使用 array_filter 过滤用户对象的数组。
<?php
class User {
public function __construct(
public string $first_name,
public string $last_name,
public string $city,
public string $email,
public string $date_of_birth
) {}
}
$users = [
new User("John", "Doe", "New York", "john.doe@example.com", "2000-05-15"),
new User("Jane", "Smith", "Los Angeles", "jane.smith@example.com", "1995-08-22"),
new User("Alice", "Johnson", "Chicago", "alice.j@example.com", "2010-03-10"),
new User("Bob", "Brown", "Houston", "bob.brown@example.com", "1998-11-30"),
new User("Emma", "Davis", "Seattle", "emma.davis@example.com", "2005-07-19"),
new User("Mike", "Wilson", "Boston", "mike.w@example.com", "1990-01-25"),
new User("Sarah", "Taylor", "Miami", "sarah.t@example.com", "2008-09-12"),
new User("Tom", "Moore", "Denver", "tom.moore@example.com", "1997-04-05"),
new User("Lisa", "Clark", "Phoenix", "lisa.clark@example.com", "2003-12-01"),
new User("David", "Lee", "Portland", "david.lee@example.com", "1985-06-18")
];
$adults = array_filter($users, fn($user) =>
(new DateTime())->diff(new DateTime($user->date_of_birth))->y > 18);
print_r($adults);
这会根据出生日期过滤 18 岁以上的用户对象。User 类使用构造函数属性提升来定义姓、名、城市、电子邮件和出生日期的属性。$users 数组包含 10 个 User 对象。
array_filter 回调函数通过将当前日期与每个用户的 $date_of_birth 属性进行比较来计算年龄。计算时 18 岁以下的用户将被排除。$adults 中的结果包括 "John" 和 "Jane" 等用户对象,他们都已年满 18 岁,并保留了它们的原始索引。
数组过滤的最佳实践
- 使用箭头函数: 使用箭头语法简化回调。
- 组合工具: 混合函数以进行复杂的转换。
- 尽早优化: 在映射之前进行过滤以减少开销。
- 利用对象: 使用提升的属性来实现干净的代码。
来源
本教程涵盖了使用现代技术(包括构造函数属性提升)在 PHP 中过滤数组和对象。
作者
列出 所有 PHP 教程。