Skip to content
This repository has been archived by the owner on Apr 11, 2019. It is now read-only.

Latest commit

 

History

History
178 lines (137 loc) · 8.83 KB

contracts.md

File metadata and controls

178 lines (137 loc) · 8.83 KB

Contracts

Introduction

Laravel's Contracts are a set of interfaces that define the core services provided by the framework. For example, a Queue contract defines the methods needed for queueing jobs, while the Mailer contract defines the methods needed for sending e-mail.

Each contract has a corresponding implementation provided by the framework. For example, Laravel provides a Queue implementation with a variety of drivers, and a Mailer implementation that is powered by SwiftMailer.

All of the Laravel contracts live in their own GitHub repository. This provides a quick reference point for all available contracts, as well as a single, decoupled package that may be utilized by other package developers.

Why Contracts?

You may have several questions regarding contracts. Why use interfaces at all? Isn't using interfaces more complicated?

Let's distill the reasons for using interfaces to the following headings: loose coupling and simplicity.

Loose Coupling

First, let's review some code that is tightly coupled to a cache implementation. Consider the following:

<?php namespace App\Orders;

class Repository {

	/**
	 * The cache.
	 */
	protected $cache;

	/**
	 * Create a new repository instance.
	 *
	 * @param  \Package\Cache\Memcached  $cache
	 * @return void
	 */
	public function __construct(\SomePackage\Cache\Memcached $cache)
	{
		$this->cache = $cache;
	}

	/**
	 * Retrieve an Order by ID.
	 *
	 * @param  int  $id
	 * @return Order
	 */
	public function find($id)
	{
		if ($this->cache->has($id))
		{
			//
		}
	}

}

In this class, the code is tightly coupled to a given cache implementation. It is tightly coupled because we are depending on a concrete Cache class from a package vendor. If the API of that package changes our code must change as well.

Likewise, if we want to replace our underlying cache technology (Memcached) with another technology (Redis), we again will have to modify our repository. Our repository should not have so much knowledge regarding who is providing them data or how they are providing it.

Instead of this approach, we can improve our code by depending on a simple, vendor agnostic interface:

<?php namespace App\Orders;

use Illuminate\Contracts\Cache\Repository as Cache;

class Repository {

	/**
	 * Create a new repository instance.
	 *
	 * @param  Cache  $cache
	 * @return void
	 */
	public function __construct(Cache $cache)
	{
		$this->cache = $cache;
	}

}

Now the code is not coupled to any specific vendor, or even Laravel. Since the contracts package contains no implementation and no dependencies, you may easily write an alternative implementation of any given contract, allowing you to replace your cache implementation without modifying any of your cache consuming code.

Simplicity

When all of Laravel's services are neatly defined within simple interfaces, it is very easy to determine the functionality offered by a given service. The contracts serve as succinct documentation to the framework's features.

In addition, when you depend on simple interfaces, your code is easier to understand and maintain. Rather than tracking down which methods are available to you within a large, complicated class, you can refer to a simple, clean interface.

Contract Reference

This is a reference to most Laravel Contracts, as well as their Laravel "facade" counterparts:

Contract Laravel 4.x Facade
Illuminate\Contracts\Auth\Guard Auth
Illuminate\Contracts\Auth\PasswordBroker Password
Illuminate\Contracts\Cache\Repository Cache
Illuminate\Contracts\Cache\Factory Cache::driver()
Illuminate\Contracts\Config\Repository Config
Illuminate\Contracts\Container\Container App
Illuminate\Contracts\Cookie\Factory Cookie
Illuminate\Contracts\Cookie\QueueingFactory Cookie::queue()
Illuminate\Contracts\Encryption\Encrypter Crypt
Illuminate\Contracts\Events\Dispatcher Event
Illuminate\Contracts\Filesystem\Cloud  
Illuminate\Contracts\Filesystem\Factory File
Illuminate\Contracts\Filesystem\Filesystem File
Illuminate\Contracts\Foundation\Application App
Illuminate\Contracts\Hashing\Hasher Hash
Illuminate\Contracts\Logging\Log Log
Illuminate\Contracts\Mail\MailQueue Mail::queue()
Illuminate\Contracts\Mail\Mailer Mail
Illuminate\Contracts\Queue\Factory Queue::driver()
Illuminate\Contracts\Queue\Queue Queue
Illuminate\Contracts\Redis\Database Redis
Illuminate\Contracts\Routing\Registrar Route
Illuminate\Contracts\Routing\ResponseFactory Response
Illuminate\Contracts\Routing\UrlGenerator URL
Illuminate\Contracts\Support\Arrayable  
Illuminate\Contracts\Support\Jsonable  
Illuminate\Contracts\Support\Renderable  
Illuminate\Contracts\Validation\Factory Validator::make()
Illuminate\Contracts\Validation\Validator  
Illuminate\Contracts\View\Factory View::make()
Illuminate\Contracts\View\View  

How To Use Contracts

So, how do you get an implementation of a contract? It's actually quite simple. Many types of classes in Laravel are resolved through the service container, including controllers, event listeners, filters, queue jobs, and even route Closures. So, to get an implementation of a contract, you can just "type-hint" the interface in the constructor of the class being resolved. For example, take a look at this event handler:

<?php namespace App\Handlers\Events;

use App\User;
use App\Events\NewUserRegistered;
use Illuminate\Contracts\Redis\Database;

class CacheUserInformation {

	/**
	 * The Redis database implementation.
	 */
	protected $redis;

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

	/**
	 * Handle the event.
	 *
	 * @param  NewUserRegistered  $event
	 * @return void
	 */
	public function handle(NewUserRegistered $event)
	{
		//
	}

}

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