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 教程。