Contracts


When to use contracts

Each contract has an implementation provided by the corresponding framework. For example, Laravel provides multiple driver queue implementations and uses SwiftMailer to implement email contracts. All Laravel contracts are in

their respective GitHub repositories
. This provides a quick reference entry for all available contracts and a single, decoupled package that extension package developers may use.

Contracts Vs. Facades

Laravel's Facades and helper functions provide an easy way to use Laravel services without type hints, and can also resolve contracts outside the service container. In most cases, each Facade has an equivalent contract.

Unlike Facades (which do not require the constructor in your class to reference dependencies), contracts allow you to define explicit dependencies for your class. Some developers prefer dependencies to be clearly defined and so prefer to use contracts, while other developers enjoy the convenience brought by facades.

{tip} In most applications, whether you prefer Facades or Contracts is fine. However, if you are building an extension package, you should strongly consider using contracts, as they make it easier to test within the context of the package.

When to use contracts

To sum up, whether to use contracts or Facades to a large extent Depends on your personal or team preference. Both Contracts and Facades can be used to build robust, well-tested Laravel applications. As long as you keep the responsibilities of your classes simple, you'll find that the actual difference between using contracts and Facades is very small.

However, you may still have many questions about the contract. For example, why do you use interface? Isn't it more complicated to use interface? Let us distill the reasons in the following content ("low coupling" and "simplicity").

Low coupling

First, let’s look at some high-coupling code for cache implementation. Suppose there is the following code:

<?php
   namespace App\Orders;
   class Repository{  
    /**
     * 缓存实例.
     */   
    protected $cache; 
    /**
     * 创建一个新的仓库实例.
     *
     * @param  \SomePackage\Cache\Memcached  $cache
     * @return void
     */  
      public function __construct(\SomePackage\Cache\Memcached $cache) 
         {   
              $this->cache = $cache;  
          }
    /**
     * 根据 ID 获取订单.
     *
     * @param  int  $id
     * @return Order
     */  
      public function find($id)  
        {   
             if ($this->cache->has($id)) 
                {      
                      //  
                 }  
          }
      }

In this class, the code is highly coupled with the given cache implementation. It is highly coupled because we rely on a specific cache class in an extension package. If the extension's API changes, our code will have to change as well.

Similarly, if we want to replace the underlying caching technology (Memcached) with another caching technology (Redis), we have to modify our code base again. Our codebase shouldn't know too much about who provided the data or how the data was provided.

We can improve our code by relying on a simple interface that has nothing to do with the extension package, instead of the previous implementation:

<?php
  namespace App\Orders;
  use Illuminate\Contracts\Cache\Repository as Cache;
  class Repository{  
    /**
     * 缓存实例.
     */    protected $cache;    /**
     * 创建一个新的仓库实例.
     *
     * @param  Cache  $cache
     * @return void
     */  
    public function __construct(Cache $cache) 
       {      
          $this->cache = $cache; 
        }
    }

The current code is not consistent with Any specific extension package is coupled, not even related to Laravel. Since the contract extension package does not contain any implementations and dependencies, you can easily code an alternative implementation for a given contract, allowing you to replace the cached implementation without modifying any cache code.

Simple

When all of Laravel's services are neatly defined in simple interfaces, it's easy to determine the functionality provided by a given service. The contract serves as a concise document of the framework's functionality.

Additionally, when you rely on simple interfaces, your code is easier to understand and maintain. Instead of tracking which methods are available in large, complex classes, you can refer to a simple, clean interface.

How to use a contract

So, how do you implement a contract? It's actually quite simple.

Many types of classes in Laravel are resolved through the service container, including controllers, event listeners, middleware, queue jobs, and even routing closures, etc. Then, to obtain the implementation of a contract, you only need to type the "type hint" interface in the constructor of the class to be parsed.

For example, take a look at this event listener:

<?php
  namespace App\Listeners;
  use App\User;use App\Events\OrderWasPlaced;
  use Illuminate\Contracts\Redis\Factory;
  class CacheOrderInformation{  
    /**
     * Redis 工厂实现。
     */   
     protected $redis;  
   /**
     * 创建一个新的事件处理器实例。
     *
     * @param  Factory  $redis
     * @return void
     */   
     public function __construct(Factory $redis)  
       {     
          $this->redis = $redis;  
        } 
     /**
     * 处理事件。
     *
     * @param  OrderWasPlaced  $event
     * @return void
     */  
    public function handle(OrderWasPlaced $event) 
       {    
           //  
        }
    }

When the event listener is resolved, the service container will read the type hint on the constructor of the class and inject the appropriate value. To learn more about registering with a service container, check out the service container documentation.

Contract Reference

This table provides a quick reference of all Laravel contracts and their equivalent facades:

ContractReferences 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()
本文章首发在 LearnKu.com 网站上。