본문으로 건너뛰기
버전: 8.x

컨트랙트 (Contracts)

소개

라라벨의 "컨트랙트(contract)"는 프레임워크가 제공하는 핵심 서비스들의 동작을 정의한 일련의 인터페이스입니다. 예를 들어, Illuminate\Contracts\Queue\Queue 컨트랙트는 작업(Job) 큐잉에 필요한 메서드를 정의하고, Illuminate\Contracts\Mail\Mailer 컨트랙트는 이메일을 발송하는 데 필요한 메서드를 정의합니다.

각 컨트랙트마다 라라벨에서 직접 제공하는 구현체가 존재합니다. 예를 들어, 라라벨은 다양한 드라이버를 지원하는 큐 시스템과 SwiftMailer를 기반으로 하는 메일 구현체를 제공합니다.

라라벨의 모든 컨트랙트는 전용 GitHub 저장소에 별도로 관리됩니다. 이 저장소를 통해 제공하는 모든 컨트랙트를 한눈에 참고할 수 있으며, 라라벨 서비스와 연동하는 패키지를 개발할 때 독립적으로 사용할 수 있는 분리된 패키지로 활용할 수 있습니다.

컨트랙트와 파사드 비교

라라벨의 파사드와 헬퍼 함수들은 라라벨의 서비스를 쉽게 사용할 수 있도록 해 줍니다. 즉, 서비스 컨테이너에서 컨트랙트를 직접 타입 힌트하거나 resolve(해결)하지 않아도 된다는 장점이 있습니다. 대부분의 경우, 각 파사드는 해당하는 컨트랙트가 존재합니다.

파사드는 클래스를 정의할 때 생성자에 별도의 의존성을 주입하지 않고도 사용할 수 있는 반면, 컨트랙트를 사용하면 클래스에 명확하게 의존성을 선언할 수 있습니다. 일부 개발자들은 이런 방식으로 의존성을 명확하게 드러내는 것을 선호해 컨트랙트를 사용하고, 다른 개발자들은 파사드의 간편함을 선호하기도 합니다. 일반적으로 대부분의 애플리케이션은 개발 과정에서 파사드를 문제 없이 사용할 수 있습니다.

컨트랙트를 사용해야 할 때

컨트랙트와 파사드 중 어떤 것을 사용할지는 개발자 개개인 또는 개발팀의 취향에 따라 달라질 수 있습니다. 두 방법 모두로 강력하고 테스트 가능한 라라벨 애플리케이션을 만들 수 있습니다. 그리고 컨트랙트와 파사드는 상호 배타적인 관계가 아니므로, 애플리케이션의 일부는 파사드를, 또 다른 일부는 컨트랙트에 의존하게 할 수 있습니다. 클래스의 역할과 책임만 명확하게 유지한다면, 컨트랙트와 파사드 중 무엇을 사용하든 실제로 큰 차이를 느끼기 어렵습니다.

일반적으로 대부분의 애플리케이션은 개발 중에 파사드를 사용하는 것이 무리 없이 가능합니다. 다만, 여러 PHP 프레임워크와 연동되는 패키지를 개발할 경우에는 illuminate/contracts 패키지 하나만 의존성에 추가해 라라벨 서비스와의 연동 부분만 컨트랙트로 명세하는 것이 좋습니다. 이렇게 하면, 패키지의 composer.json 파일에 라라벨의 구체적인 구현체까지 꼭 추가하지 않아도 됩니다.

컨트랙트 사용 방법

그렇다면, 컨트랙트의 구현체는 어떻게 가져올 수 있을까요? 실제로는 매우 간단합니다.

라라벨의 다양한 클래스(컨트롤러, 이벤트 리스너, 미들웨어, 큐 작업, 라우트 클로저 등)는 모두 서비스 컨테이너를 통해 resolve(해결)됩니다. 따라서 컨트랙트의 구현체를 사용하려면, 해당 클래스의 생성자에서 해당 인터페이스를 타입 힌트(명시적으로 작성)하면 됩니다.

예를 들어, 아래 이벤트 리스너 예시를 살펴보세요.

<?php

namespace App\Listeners;

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

class CacheOrderInformation
{
/**
* The Redis factory implementation.
*
* @var \Illuminate\Contracts\Redis\Factory
*/
protected $redis;

/**
* Create a new event handler instance.
*
* @param \Illuminate\Contracts\Redis\Factory $redis
* @return void
*/
public function __construct(Factory $redis)
{
$this->redis = $redis;
}

/**
* Handle the event.
*
* @param \App\Events\OrderWasPlaced $event
* @return void
*/
public function handle(OrderWasPlaced $event)
{
//
}
}

이벤트 리스너가 resolve(해결)될 때, 서비스 컨테이너가 클래스 생성자의 타입 힌트를 자동으로 읽어들여 적절한 값(여기서는 Redis 팩토리 구현 객체)을 주입해 줍니다. 서비스 컨테이너에 리소스를 등록하는 자세한 방법은 컨테이너 문서를 참고하세요.

컨트랙트 레퍼런스

아래 표는 라라벨에서 제공하는 모든 컨트랙트와, 각 컨트랙트와 대응되는 파사드(facade)를 빠르게 참고할 수 있도록 정리한 것입니다.

Contract대응되는 파사드(Facade)
Illuminate\Contracts\Auth\Access\Authorizable  
Illuminate\Contracts\Auth\Access\GateGate
Illuminate\Contracts\Auth\Authenticatable  
Illuminate\Contracts\Auth\CanResetPassword 
Illuminate\Contracts\Auth\FactoryAuth
Illuminate\Contracts\Auth\GuardAuth::guard()
Illuminate\Contracts\Auth\PasswordBrokerPassword::broker()
Illuminate\Contracts\Auth\PasswordBrokerFactoryPassword
Illuminate\Contracts\Auth\StatefulGuard 
Illuminate\Contracts\Auth\SupportsBasicAuth 
Illuminate\Contracts\Auth\UserProvider 
Illuminate\Contracts\Bus\DispatcherBus
Illuminate\Contracts\Bus\QueueingDispatcherBus::dispatchToQueue()
Illuminate\Contracts\Broadcasting\FactoryBroadcast
Illuminate\Contracts\Broadcasting\BroadcasterBroadcast::connection()
Illuminate\Contracts\Broadcasting\ShouldBroadcast 
Illuminate\Contracts\Broadcasting\ShouldBroadcastNow 
Illuminate\Contracts\Cache\FactoryCache
Illuminate\Contracts\Cache\Lock 
Illuminate\Contracts\Cache\LockProvider 
Illuminate\Contracts\Cache\RepositoryCache::driver()
Illuminate\Contracts\Cache\Store 
Illuminate\Contracts\Config\RepositoryConfig
Illuminate\Contracts\Console\Application 
Illuminate\Contracts\Console\KernelArtisan
Illuminate\Contracts\Container\ContainerApp
Illuminate\Contracts\Cookie\FactoryCookie
Illuminate\Contracts\Cookie\QueueingFactoryCookie::queue()
Illuminate\Contracts\Database\ModelIdentifier 
Illuminate\Contracts\Debug\ExceptionHandler 
Illuminate\Contracts\Encryption\EncrypterCrypt
Illuminate\Contracts\Events\DispatcherEvent
Illuminate\Contracts\Filesystem\CloudStorage::cloud()
Illuminate\Contracts\Filesystem\FactoryStorage
Illuminate\Contracts\Filesystem\FilesystemStorage::disk()
Illuminate\Contracts\Foundation\ApplicationApp
Illuminate\Contracts\Hashing\HasherHash
Illuminate\Contracts\Http\Kernel 
Illuminate\Contracts\Mail\MailQueueMail::queue()
Illuminate\Contracts\Mail\Mailable 
Illuminate\Contracts\Mail\MailerMail
Illuminate\Contracts\Notifications\DispatcherNotification
Illuminate\Contracts\Notifications\FactoryNotification
Illuminate\Contracts\Pagination\LengthAwarePaginator 
Illuminate\Contracts\Pagination\Paginator 
Illuminate\Contracts\Pipeline\Hub 
Illuminate\Contracts\Pipeline\Pipeline 
Illuminate\Contracts\Queue\EntityResolver 
Illuminate\Contracts\Queue\FactoryQueue
Illuminate\Contracts\Queue\Job 
Illuminate\Contracts\Queue\MonitorQueue
Illuminate\Contracts\Queue\QueueQueue::connection()
Illuminate\Contracts\Queue\QueueableCollection 
Illuminate\Contracts\Queue\QueueableEntity 
Illuminate\Contracts\Queue\ShouldQueue 
Illuminate\Contracts\Redis\FactoryRedis
Illuminate\Contracts\Routing\BindingRegistrarRoute
Illuminate\Contracts\Routing\RegistrarRoute
Illuminate\Contracts\Routing\ResponseFactoryResponse
Illuminate\Contracts\Routing\UrlGeneratorURL
Illuminate\Contracts\Routing\UrlRoutable 
Illuminate\Contracts\Session\SessionSession::driver()
Illuminate\Contracts\Support\Arrayable 
Illuminate\Contracts\Support\Htmlable 
Illuminate\Contracts\Support\Jsonable 
Illuminate\Contracts\Support\MessageBag 
Illuminate\Contracts\Support\MessageProvider 
Illuminate\Contracts\Support\Renderable 
Illuminate\Contracts\Support\Responsable 
Illuminate\Contracts\Translation\Loader 
Illuminate\Contracts\Translation\TranslatorLang
Illuminate\Contracts\Validation\FactoryValidator
Illuminate\Contracts\Validation\ImplicitRule 
Illuminate\Contracts\Validation\Rule 
Illuminate\Contracts\Validation\ValidatesWhenResolved 
Illuminate\Contracts\Validation\ValidatorValidator::make()
Illuminate\Contracts\View\Engine 
Illuminate\Contracts\View\FactoryView
Illuminate\Contracts\View\ViewView::make()