메인 콘텐츠로 건너뛰기

Contract란

Laravel의 “Contracts”는 프레임워크가 제공하는 핵심 서비스를 정의하는 인터페이스 세트입니다. 예를 들어 Illuminate\Contracts\Queue\Queue Contract는 잡의 큐잉에 필요한 메서드를 정의하고, Illuminate\Contracts\Mail\Mailer Contract는 메일 전송에 필요한 메서드를 정의하고 있습니다. 각 Contract에는 프레임워크가 제공하는 대응하는 구현이 있습니다. 예를 들어 Laravel은 다양한 드라이버의 큐 구현과 Symfony Mailer를 사용한 메일러 구현을 제공하고 있습니다. 모든 Laravel Contracts는 전용 GitHub 리포지토리에 있습니다. 이는 모든 이용 가능한 Contract에 대한 빠른 참조를 제공하며, Laravel 서비스와 연동하는 패키지를 구축할 때 이용할 수 있는 단일의 분리된 패키지입니다.
Contract는 단순한 인터페이스입니다. PHP의 인터페이스와 정확히 같은 구조로 동작합니다. Laravel은 이 인터페이스에 대한 구현을 제공하고, 서비스 컨테이너를 통해 주입합니다.

Contract와 Facade의 차이

파사드와 헬퍼 함수는 서비스 컨테이너에서 Contract를 타입 힌트로 해결할 필요 없이 Laravel의 서비스를 간단하게 이용하는 방법을 제공합니다. 대부분의 경우, 각 파사드에는 대응하는 Contract가 있습니다. 파사드와 Contract의 주요 차이는 다음과 같습니다.
관점파사드Contract
의존 선언불필요(어디서든 호출 가능)생성자에서 명시적으로 선언
테스트shouldReceive()로 모의화표준적인 모의 객체 라이브러리로 교체
주요 용도애플리케이션 내에서의 손쉬운 사용패키지 개발, 명시적 의존 관리
코드 가독성임포트가 한 줄로 끝남생성자에서 의존이 한눈에 명확
파사드는 클래스의 생성자에서 요구할 필요가 없지만, Contract는 클래스의 생성자에서 명시적인 의존으로 정의할 수 있습니다. 일부 개발자는 이 명시적인 의존의 정의를 선호하여 Contract를 사용합니다. 다른 개발자는 파사드의 편리함을 선호합니다. 일반적으로 대부분의 애플리케이션은 개발 중에 파사드를 문제없이 사용할 수 있습니다.

Contract를 언제 사용할 것인가

Contract와 Facade 중 어느 쪽을 사용할지는 개인의 취향이나 개발 팀의 취향에 달려 있습니다. Contract와 Facade는 어느 쪽이든 견고하고 테스트하기 쉬운 Laravel 애플리케이션을 만들기 위해 사용할 수 있습니다. Contract와 Facade는 서로 배타적이지 않습니다. 애플리케이션의 일부에서는 Facade를 사용하고, 다른 부분에서는 Contract에 의존하는 것도 가능합니다. 특히 Contract가 유용한 상황은 다음과 같습니다.
  • 여러 PHP 프레임워크와 연동하는 패키지를 구축하는 경우illuminate/contracts 패키지를 사용해 Laravel 서비스와의 연동을 정의함으로써, composer.json에 Laravel의 구체적인 구현을 요구하지 않아도 됩니다.
  • 의존을 명시적으로 하고 싶은 경우 — 생성자만 봐도 클래스가 무엇에 의존하고 있는지 한눈에 알 수 있습니다.
  • 구현을 교체하고 싶은 경우 — 서비스 컨테이너를 통해 다른 구현으로 교체하는 것이 용이해집니다.

Contract 사용법

Contract 구현을 획득하려면 어떻게 해야 할까요? 사실 매우 간단합니다. 컨트롤러, 이벤트 리스너, 미들웨어, 큐 잡, 라우트 클로저 등 Laravel의 많은 종류의 클래스는 서비스 컨테이너를 통해 해결됩니다. 따라서 Contract 구현을 획득하려면 해결되고 있는 클래스의 생성자에서 인터페이스를 “타입 힌트”하기만 하면 됩니다. 예를 들어 다음 이벤트 리스너를 봐 주세요.
<?php

namespace App\Listeners;

use App\Events\OrderWasPlaced;
use App\Models\User;
use Illuminate\Contracts\Redis\Factory;

class CacheOrderInformation
{
    /**
     * 이벤트 리스너를 생성한다
     */
    public function __construct(
        protected Factory $redis,
    ) {}

    /**
     * 이벤트를 처리한다
     */
    public function handle(OrderWasPlaced $event): void
    {
        // ...
    }
}
이벤트 리스너가 해결되면 서비스 컨테이너는 클래스의 생성자의 타입 힌트를 읽어 들여 적절한 값을 주입합니다.

커스텀 Contract 생성

독자적인 Contract를 만들어 애플리케이션의 컴포넌트 간 의존을 명확히 할 수 있습니다.
1

인터페이스를 정의한다

app/Contracts 디렉터리에 인터페이스를 생성합니다.
<?php

namespace App\Contracts;

interface PaymentGateway
{
    /**
     * 지정된 금액을 결제한다
     */
    public function charge(int $amount, string $token): bool;

    /**
     * 결제를 환불한다
     */
    public function refund(string $transactionId): bool;
}
2

구현 클래스를 생성한다

Contract를 구현하는 클래스를 생성합니다.
<?php

namespace App\Services;

use App\Contracts\PaymentGateway;

class StripePaymentGateway implements PaymentGateway
{
    public function charge(int $amount, string $token): bool
    {
        // Stripe API를 사용한 결제 처리...
        return true;
    }

    public function refund(string $transactionId): bool
    {
        // Stripe API를 사용한 환불 처리...
        return true;
    }
}
3

서비스 프로바이더에서 바인딩한다

서비스 프로바이더에서 Contract와 구현을 바인딩합니다.
use App\Contracts\PaymentGateway;
use App\Services\StripePaymentGateway;

$this->app->singleton(PaymentGateway::class, StripePaymentGateway::class);
4

타입 힌트로 주입을 받는다

컨트롤러나 다른 클래스의 생성자에서 타입 힌트합니다.
<?php

namespace App\Http\Controllers;

use App\Contracts\PaymentGateway;
use Illuminate\Http\Request;

class OrderController extends Controller
{
    public function __construct(
        protected PaymentGateway $payment,
    ) {}

    public function store(Request $request)
    {
        $this->payment->charge(
            $request->amount,
            $request->payment_token
        );

        // ...
    }
}
이 패턴을 통해 결제 서비스를 Stripe에서 다른 프로바이더로 전환하는 경우에도 바인딩을 한 곳만 변경하면 되며, 컨트롤러 코드는 변경할 필요가 없습니다.

주요 Contract 목록

자주 사용하는 Contract와 그 대응하는 파사드의 대응표입니다(일부 발췌).
Contract대응하는 파사드
Illuminate\Contracts\Auth\Access\GateGate
Illuminate\Contracts\Auth\FactoryAuth
Illuminate\Contracts\Bus\DispatcherBus
Illuminate\Contracts\Cache\FactoryCache
Illuminate\Contracts\Cache\RepositoryCache::driver()
Illuminate\Contracts\Config\RepositoryConfig
Illuminate\Contracts\Console\KernelArtisan
Illuminate\Contracts\Container\ContainerApp
Illuminate\Contracts\Encryption\EncrypterCrypt
Illuminate\Contracts\Events\DispatcherEvent
Illuminate\Contracts\Filesystem\FactoryStorage
Illuminate\Contracts\Filesystem\FilesystemStorage::disk()
Illuminate\Contracts\Hashing\HasherHash
Illuminate\Contracts\Mail\MailerMail
Illuminate\Contracts\Notifications\DispatcherNotification
Illuminate\Contracts\Queue\FactoryQueue
Illuminate\Contracts\Queue\QueueQueue::connection()
Illuminate\Contracts\Queue\ShouldQueue
Illuminate\Contracts\Redis\FactoryRedis
Illuminate\Contracts\Routing\ResponseFactoryResponse
Illuminate\Contracts\Routing\UrlGeneratorURL
Illuminate\Contracts\Session\SessionSession::driver()
Illuminate\Contracts\Translation\TranslatorLang
Illuminate\Contracts\Validation\FactoryValidator
Illuminate\Contracts\View\FactoryView
모든 Contract의 목록은 illuminate/contracts 리포지토리에서 확인할 수 있습니다.

다음 단계

파사드

파사드의 구조와 테스트 방법을 확인합니다.
마지막 수정일 2026년 7월 13일