跳转到主要内容

什么是高阶消息

高阶消息(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 与自定义类中的应用方法。
最后修改于 2026年7月13日