메인 콘텐츠로 건너뛰기

ForwardsCalls 트레이트란

Illuminate\Support\Traits\ForwardsCalls는 어떤 오브젝트로의 메서드 위임을 공통화하는 트레이트입니다. 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는 static proxy를 제공한다

Illuminate\Support\Facades\Facade__callStatic()으로 root instance에 직접 위임합니다. Facade는 static 호출이므로 ForwardsCalls가 아니라 직접 전달하는 구현입니다.
2

Eloquent Builder는 forwardCallTo를 사용한다

Illuminate\Database\Eloquent\Builder::__call()은 해결되지 않은 메서드를 내부 Query Builder로 forwardCallTo($this->query, ...)로 전달하고, 마지막에 $this를 반환하여 fluent chain을 유지합니다.
3

Relation / Mail / Event는 forwardDecoratedCallTo를 사용한다

Illuminate\Database\Eloquent\Relations\Relation, Illuminate\Mail\Message, Illuminate\Events\NullDispatcher에서는 forwardDecoratedCallTo가 사용되어, 내부 오브젝트에 위임하면서 외부 API의 체인을 유지합니다.

기본적인 프록시 구현(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);
    }
}
이러한 형태라면, CourierProxyCourierDriver의 public API를 투명하게 노출할 수 있습니다.

메서드 체인 대응 프록시(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()
적합한 케이스API 확장Proxy / 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를 주입하고, 호출은 forwardCallTo로 그대로 흘려서 호출 횟수와 인수를 검증할 수 있습니다.

관련 페이지

Macroable 트레이트

기존 클래스에 새로운 메서드를 추가하는 확장 패턴을 배웁니다.

Conditionable 트레이트

when() / unless()를 사용한 조건 분기 체인의 설계를 배웁니다.
마지막 수정일 2026년 7월 13일