跳转到主要内容

什么是 ForwardsCalls trait

Illuminate\Support\Traits\ForwardsCalls 是用于统一某个对象方法委托的 trait。在 Laravel 本体中,它被 Eloquent、Mail 等「包装对象」使用。
实现位于 src/Illuminate/Support/Traits/ForwardsCalls.php。当转发到不存在的方法时,会重新抛出一个包含调用方类名的 BadMethodCallException

核心 API

forwardCallTo($object, $method, $parameters)

将方法原样转发到指定对象。通常从 __call() 中调用。

forwardDecoratedCallTo($object, $method, $parameters)

在 Builder 或 Decorator 中希望保持链式调用时使用。若转发目标方法的返回值是「目标对象自身」,则替换为「调用方对象($this)」返回。

__call() / __callStatic() 的组合

ForwardsCalls 本身不提供魔术方法。你需要在自己的类中实现 __call()(必要时还有 __callStatic()),并在其中调用 forwardCallTo 系列方法。

实际使用示例(Laravel 内部)

1

Facade 提供静态代理

Illuminate\Support\Facades\Facade 通过 __callStatic() 直接委托到 root instance。Facade 是静态调用,因此并没有使用 ForwardsCalls,而是直接转发实现。
2

Eloquent Builder 使用 forwardCallTo

Illuminate\Database\Eloquent\Builder::__call() 会通过 forwardCallTo($this->query, ...) 将未解析的方法转发给内部 Query Builder,最后返回 $this 以保持链式调用。
3

Relation / Mail / Event 使用 forwardDecoratedCallTo

Illuminate\Database\Eloquent\Relations\RelationIlluminate\Mail\MessageIlluminate\Events\NullDispatcher 使用 forwardDecoratedCallTo,一边委托到内部对象,一边保持外层 API 的链式调用。

基础 Proxy 实现(forwardCallTo

use Illuminate\Support\Traits\ForwardsCalls;

class CourierProxy
{
    use ForwardsCalls;

    public function __construct(
        protected CourierDriver $driver
    ) {}

    public function __call(string $method, array $parameters): mixed
    {
        return $this->forwardCallTo($this->driver, $method, $parameters);
    }
}
这样,CourierProxy 就能透明地对外暴露 CourierDriver 的 public API。

支持链式调用的 Proxy(forwardDecoratedCallTo

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Traits\ForwardsCalls;

class QueryProxy
{
    use ForwardsCalls;

    public function __construct(
        protected Builder $query
    ) {}

    public function __call(string $method, array $parameters): static
    {
        $this->forwardDecoratedCallTo($this->query, $method, $parameters);

        return $this;
    }

    public function get(): \Illuminate\Support\Collection
    {
        return $this->query->get();
    }
}
$users = (new QueryProxy(User::query()))
    ->where('active', true)
    ->orderByDesc('created_at')
    ->limit(10)
    ->get();
与其手动写 return $this,不如利用 forwardDecoratedCallTo 的规则——「返回值若为转发目标自身,则替换为 $this」——更安全。

自动抛出 BadMethodCallException

调用不存在的方法时,ForwardsCalls 会用调用方类名重新抛出 BadMethodCallException
try {
    (new CourierProxy($driver))->missingMethod();
} catch (\BadMethodCallException $e) {
    // 例:Call to undefined method App\Services\CourierProxy::missingMethod()
    report($e);
}
这样用户可以立即定位「是哪个外层 API 出了问题」。

与 Macroable 的对比

视角MacroableForwardsCalls
目的向既有类新增方法将方法委托给另一个对象
主要入口macro()mixin()forwardCallTo()forwardDecoratedCallTo()
适用场景扩展 APIProxy / Decorator / Adapter
若目的是「增加方法」却使用 ForwardsCalls,那么委托对象未实现的方法会一直失败。若目的在于扩展 API,请选择 Macroable

包开发中的应用示例

1) 切换 Driver 的 Manager

use Illuminate\Support\Traits\ForwardsCalls;

class SmsManager
{
    use ForwardsCalls;

    public function __construct(
        protected SmsDriver $driver
    ) {}

    public function via(string $name): static
    {
        $this->driver = app(SmsDriverFactory::class)->make($name);

        return $this;
    }

    public function __call(string $method, array $parameters): mixed
    {
        return $this->forwardCallTo($this->driver, $method, $parameters);
    }
}

2) 支持多种后端的 Adapter

可以把 HTTP / Queue / WebSocket 等不同后端统一到同一个 API 后再委托。

3) 用于测试的 Spy / Stub

用 Spy 代替真实 Driver 注入,调用通过 forwardCallTo 原样转发,从而校验调用次数与参数。

相关页面

Macroable trait

学习向既有类添加新方法的扩展模式。

Conditionable trait

学习基于 when() / unless() 的条件分支链式设计。
最后修改于 2026年7月13日