什么是高阶消息
高阶消息(Higher-order Messages)是以属性访问形式调用集合方法的语法。无需编写回调即可对每个元素调用方法或取属性。
// 常规写法
$names = $users -> map ( fn ( $user ) => $user -> name );
// 使用高阶消息的写法
$names = $users -> map -> name ;
$users->map->name 表示「取出每个用户的 name 属性并进行 map」。
机制 — HigherOrderCollectionProxy
当以属性方式访问 $collection->map 时,会触发 EnumeratesValues trait 的 __get()。
// src/Illuminate/Collections/Traits/EnumeratesValues.php
public function __get ( $key )
{
if ( ! in_array ( $key , static :: $proxies )) {
throw new Exception ( "Property [{ $key }] does not exist on this collection instance." );
}
return new HigherOrderCollectionProxy ( $this , $key );
}
若该键在 $proxies 列表中,则返回一个 HigherOrderCollectionProxy 实例。这个代理会同时持有集合与方法名。
通过 __get 代理属性访问
继续访问属性(->name)时,会调用代理的 __get()。
// src/Illuminate/Collections/HigherOrderCollectionProxy.php
public function __get ( $key )
{
return $this -> collection -> { $this -> method }( function ( $value ) use ( $key ) {
return is_array ( $value ) ? $value [ $key ] : $value -> { $key };
});
}
因此 $users->map->name 等价于:
$users -> map ( fn ( $user ) => $user -> name );
通过 __call 代理方法调用
如果继续调用方法(->activate()),会触发代理的 __call()。
public function __call ( $method , $parameters )
{
return $this -> collection -> { $this -> method }( function ( $value ) use ( $method , $parameters ) {
return is_string ( $value )
? $value :: { $method }( ... $parameters )
: $value -> { $method }( ... $parameters );
});
}
$users->each->activate() 等价于:
$users -> each ( fn ( $user ) => $user -> activate ());
支持的方法一览
可作为高阶消息使用的方法在 $proxies 数组中定义。
protected static $proxies = [
'average' , 'avg' ,
'contains' , 'doesntContain' , 'some' ,
'each' , 'every' ,
'filter' , 'reject' ,
'first' , 'last' ,
'flatMap' , 'map' ,
'groupBy' , 'keyBy' ,
'hasMany' , 'hasSole' ,
'max' , 'min' , 'sum' , 'percentage' ,
'partition' ,
'skipUntil' , 'skipWhile' ,
'sortBy' , 'sortByDesc' ,
'takeUntil' , 'takeWhile' ,
'unique' ,
'unless' , 'until' , 'when' ,
];
要新增自定义方法,使用 Collection::proxy()。
Collection :: proxy ( 'myCustomMethod' );
实战用例
Eloquent 模型集合
高阶消息在 Eloquent 集合中尤其方便。
$users = User :: with ( 'posts' ) -> get ();
// 方法调用:更新每个用户的状态
$users -> each -> markAsVerified ();
// 属性访问:只取名字
$names = $users -> map -> name ;
// 向方法传参
$users -> each -> sendPasswordReset ( 'zh-CN' );
// 发送邮件
$users -> each -> notify ( new WelcomeNotification );
$activeUsers = $users -> filter -> isActive ();
// 仅保留 isActive() 返回 true 的用户
// 等价于 $users->filter(fn ($user) => $user->isActive())
$inactiveUsers = $users -> reject -> isActive ();
// 各用户 post_count 属性求和
$totalPosts = $users -> sum -> post_count ;
// 各用户 score 的最大值
$highestScore = $users -> max -> score ;
// 各用户 age 的平均值
$averageAge = $users -> avg -> age ;
分组与排序
// 按 role 属性分组
$grouped = $users -> groupBy -> role ;
// 按 name 属性升序排列
$sorted = $users -> sortBy -> name ;
// 按 created_at 属性降序排列
$sorted = $users -> sortByDesc -> created_at ;
flatMap 展平嵌套
// 展开每个用户的 posts 关联,合并为单个集合
$posts = $users -> flatMap -> posts ;
条件判定
// 是否全部为管理员
$allAdmins = $users -> every -> isAdmin ();
// 是否至少一个人是活跃的
$hasActive = $users -> contains -> isActive ();
// 是否全部持有指定套餐
$allPro = $users -> every -> hasPlan ( 'pro' );
字符串集合
值为字符串的集合也可以使用。代理的 __call() 在字符串时以静态方法方式调用。
$names = collect ([ 'alice' , 'bob' , 'charlie' ]);
// 对字符串集合,使用闭包更为明确
$upper = $names -> map ( fn ( $name ) => strtoupper ( $name ));
// ['ALICE', 'BOB', 'CHARLIE']
字符串集合的高阶消息要求方法在字符串类型上是静态定义的。通常在 Eloquent 模型或自定义类的集合中使用更自然。
与常规闭包的对比
高阶消息简洁,但并不适用于所有场景。
// 高阶消息:适合简单的属性获取和方法调用
$emails = $users -> map -> email ;
$users -> each -> sendWelcomeMail ();
$admins = $users -> filter -> isAdmin ();
// 闭包:适合复杂逻辑或需要传参的情况
$formatted = $users -> map ( function ( $user ) {
return "{ $user -> name } <{ $user -> email }>" ;
});
$filtered = $users -> filter ( function ( $user ) use ( $minAge , $role ) {
return $user -> age >= $minAge && $user -> role === $role ;
});
「从每个元素取相同属性」「对每个元素调用相同方法」时,高阶消息可读性更佳。条件复杂或参数需要动态传入时,请使用闭包。
在自定义类中使用高阶消息
若希望通过 EnumeratesValues trait 使用 HigherOrderCollectionProxy 机制,可以调用 Collection::proxy() 将方法加入代理列表。
namespace App\Providers ;
use Illuminate\Support\ Collection ;
use Illuminate\Support\ ServiceProvider ;
class AppServiceProvider extends ServiceProvider
{
public function boot () : void
{
// 将自定义宏加入代理列表
Collection :: macro ( 'active' , function () {
return $this -> filter ( fn ( $item ) => $item -> isActive ());
});
Collection :: proxy ( 'active' );
}
}
// 注册后可作为高阶消息使用
$activeUsers = $users -> active ;
下一步
Conditionable trait 学习 when() / unless() 的内部实现,以及在 QueryBuilder 与自定义类中的应用方法。