Click to share! ⬇️

laravel alisases and contracts

Interfaces are a huge part of the Laravel framework. You can also call them contracts, this is how they are referred to on Github. This is the collection of interfaces that the Laravel framework itself makes use of. By browsing the contracts repository at Github, you can become familiar with the entire API of the framework. All of the concrete classes of the framework implement these various interfaces. In fact, when getting to know a software project, it is not a bad idea to start by browsing all of the interfaces. This tells you what methods will be available to you.


Cache Repository

As an example, we can take a look at the Cache repository.

<?php

namespace Illuminate\Contracts\Cache;

use Closure;

interface Repository
{
    /**
     * Determine if an item exists in the cache.
     *
     * @param  string  $key
     * @return bool
     */
    public function has($key);

    /**
     * Retrieve an item from the cache by key.
     *
     * @param  string  $key
     * @param  mixed   $default
     * @return mixed
     */
    public function get($key, $default = null);

    /**
     * Retrieve an item from the cache and delete it.
     *
     * @param  string  $key
     * @param  mixed   $default
     * @return mixed
     */
    public function pull($key, $default = null);

    /**
     * Store an item in the cache.
     *
     * @param  string  $key
     * @param  mixed   $value
     * @param  \DateTime|int  $minutes
     * @return void
     */
    public function put($key, $value, $minutes);

    /**
     * Store an item in the cache if the key does not exist.
     *
     * @param  string  $key
     * @param  mixed   $value
     * @param  \DateTime|int  $minutes
     * @return bool
     */
    public function add($key, $value, $minutes);

    /**
     * Increment the value of an item in the cache.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return int|bool
     */
    public function increment($key, $value = 1);

    /**
     * Decrement the value of an item in the cache.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return int|bool
     */
    public function decrement($key, $value = 1);

    /**
     * Store an item in the cache indefinitely.
     *
     * @param  string  $key
     * @param  mixed   $value
     * @return void
     */
    public function forever($key, $value);

    /**
     * Get an item from the cache, or store the default value.
     *
     * @param  string  $key
     * @param  \DateTime|int  $minutes
     * @param  \Closure  $callback
     * @return mixed
     */
    public function remember($key, $minutes, Closure $callback);

    /**
     * Get an item from the cache, or store the default value forever.
     *
     * @param  string   $key
     * @param  \Closure  $callback
     * @return mixed
     */
    public function sear($key, Closure $callback);

    /**
     * Get an item from the cache, or store the default value forever.
     *
     * @param  string   $key
     * @param  \Closure  $callback
     * @return mixed
     */
    public function rememberForever($key, Closure $callback);

    /**
     * Remove an item from the cache.
     *
     * @param  string $key
     * @return bool
     */
    public function forget($key);
}

By taking a look at this interface, we can see that any concrete implementation will need to abide by the methods defined in the contract. We can see this would be things like has(), get(), pull(), put(), add(), increment(), decrement(), forever(), remember(), sear(), rememberForever(), and forget(). As we look at this, we can see this makes for great documentation. Need to know what methods are available to you in a class? Just look at the interface it implements. Laravel’s contracts component is a collection of interfaces for all of the various APIs that the Framework makes available to users.


Pre Configured Interface Bindings

Any concrete class can implement any interface it likes as long as it adheres to the contract. So how do we know what implementation we will get when we ask for a specific interface? If you make use of Laravel, of course you are going to want to make use of the functionality of the framework, not just it’s interfaces. We can find the mapping in registerCoreContainerAliases()

public function registerCoreContainerAliases()
{
    $aliases = [
        'app'                  => ['Illuminate\Foundation\Application', 'Illuminate\Contracts\Container\Container', 'Illuminate\Contracts\Foundation\Application'],
        'auth'                 => ['Illuminate\Auth\AuthManager', 'Illuminate\Contracts\Auth\Factory'],
        'auth.driver'          => ['Illuminate\Contracts\Auth\Guard'],
        'blade.compiler'       => ['Illuminate\View\Compilers\BladeCompiler'],
        'cache'                => ['Illuminate\Cache\CacheManager', 'Illuminate\Contracts\Cache\Factory'],
        'cache.store'          => ['Illuminate\Cache\Repository', 'Illuminate\Contracts\Cache\Repository'],
        'config'               => ['Illuminate\Config\Repository', 'Illuminate\Contracts\Config\Repository'],
        'cookie'               => ['Illuminate\Cookie\CookieJar', 'Illuminate\Contracts\Cookie\Factory', 'Illuminate\Contracts\Cookie\QueueingFactory'],
        'encrypter'            => ['Illuminate\Encryption\Encrypter', 'Illuminate\Contracts\Encryption\Encrypter'],
        'db'                   => ['Illuminate\Database\DatabaseManager'],
        'db.connection'        => ['Illuminate\Database\Connection', 'Illuminate\Database\ConnectionInterface'],
        'events'               => ['Illuminate\Events\Dispatcher', 'Illuminate\Contracts\Events\Dispatcher'],
        'files'                => ['Illuminate\Filesystem\Filesystem'],
        'filesystem'           => ['Illuminate\Filesystem\FilesystemManager', 'Illuminate\Contracts\Filesystem\Factory'],
        'filesystem.disk'      => ['Illuminate\Contracts\Filesystem\Filesystem'],
        'filesystem.cloud'     => ['Illuminate\Contracts\Filesystem\Cloud'],
        'hash'                 => ['Illuminate\Contracts\Hashing\Hasher'],
        'translator'           => ['Illuminate\Translation\Translator', 'Symfony\Component\Translation\TranslatorInterface'],
        'log'                  => ['Illuminate\Log\Writer', 'Illuminate\Contracts\Logging\Log', 'Psr\Log\LoggerInterface'],
        'mailer'               => ['Illuminate\Mail\Mailer', 'Illuminate\Contracts\Mail\Mailer', 'Illuminate\Contracts\Mail\MailQueue'],
        'auth.password'        => ['Illuminate\Auth\Passwords\PasswordBrokerManager', 'Illuminate\Contracts\Auth\PasswordBrokerFactory'],
        'auth.password.broker' => ['Illuminate\Auth\Passwords\PasswordBroker', 'Illuminate\Contracts\Auth\PasswordBroker'],
        'queue'                => ['Illuminate\Queue\QueueManager', 'Illuminate\Contracts\Queue\Factory', 'Illuminate\Contracts\Queue\Monitor'],
        'queue.connection'     => ['Illuminate\Contracts\Queue\Queue'],
        'redirect'             => ['Illuminate\Routing\Redirector'],
        'redis'                => ['Illuminate\Redis\Database', 'Illuminate\Contracts\Redis\Database'],
        'request'              => ['Illuminate\Http\Request', 'Symfony\Component\HttpFoundation\Request'],
        'router'               => ['Illuminate\Routing\Router', 'Illuminate\Contracts\Routing\Registrar'],
        'session'              => ['Illuminate\Session\SessionManager'],
        'session.store'        => ['Illuminate\Session\Store', 'Symfony\Component\HttpFoundation\Session\SessionInterface'],
        'url'                  => ['Illuminate\Routing\UrlGenerator', 'Illuminate\Contracts\Routing\UrlGenerator'],
        'validator'            => ['Illuminate\Validation\Factory', 'Illuminate\Contracts\Validation\Factory'],
        'view'                 => ['Illuminate\View\Factory', 'Illuminate\Contracts\View\Factory'],
    ];

    foreach ($aliases as $key => $aliases) {
        foreach ($aliases as $alias) {
            $this->alias($key, $alias);
        }
    }
}

It is in this method where all of the default aliases and their mappings are declared. All of this comes back to the concept of putting classes into the container and resolving objects out of the service container, also known as the IoC container. It is because of the service container and alias mapping that you can do things such as the following, that in effect are all the same exact thing.


// use a facade
Route::get('/', function () {
    return 'Interfaces are cool!';
});

// pass in key alias of the component
app('router')->get('/', function () {
    return 'Interfaces are cool!';
});

// use the app object with array access
// passing in key alias
app()['router']->get('/', function () {
    return 'Interfaces are cool!';
});

// pass in the class itself
app('Illuminate\Routing\Router')->get('/', function () {
    return 'Interfaces are cool!';
});

// pass in the interface or contract
app('Illuminate\Contracts\Routing\Registrar')->get('/', function () {
    return 'Interfaces are cool!';
});

Now, this is an example of the behavior we get for the built in routing class of Laravel. In addition to all this flexibility and syntactic sugar, the other huge benefit of all these contracts is that you can very easily swap out the default implementation with something else. Personally, I have had no reason so far to ever need to do this. If you have a situation where a particular requirement of a project demands that a particular class or implementation of a component be used, then you are not out of luck. You can still use Laravel, you just need to register your own component or class into the service container. Then, when you ask for that interface in your code, you will be given the correct implementation. Here is where you can learn more about the Laravel Service Container. Let’s examine a little bit more about how this works.


Register With The Service Container (Put classes into the container)

You will often hear the phrase, “register to the service container”, or “bind into the service container”. When I hear this, I simply think “Make classes available to the application by way of the service container”. Think of the service container as a two way operation. We register, or put classes into, the service container, and then later on we can resolve, or take out of the service container, an object, or instance of any registered classes that we might like to make use of. This usually happens with the register() method of a service provider. For example, here is that very registration for the Cache component of Laravel.

public function register()
{
	$this->app->singleton('cache', function ($app) {
		return new CacheManager($app);
	});

	$this->app->singleton('cache.store', function ($app) {
		return $app['cache']->driver();
	});

	$this->app->singleton('memcached.connector', function () {
		return new MemcachedConnector;
	});

	$this->registerCommands();
}

We can see that it is the singleton() method which does the work for us. This is mostly the same as if you were to call the bind() method. The difference between the two is that the singleton pattern is going to give the developer a shared instance of the object at all times. A new object is not created every time. Now in the snippet above, there are a few options for various keys. Those are cache, cache.store, and memcached.connector. Which one gets resolved depends on the configuration of the application. When a request is made of one of those keys, it is the closure that will return an instance of the given class.

Here is that singleton() method which is part of the Container class.

public function singleton($abstract, $concrete = null)
{
	$this->bind($abstract, $concrete, true);
}

This method actually makes use of the bind() method, but it sets the third parameter to true, which indicates that this is a shared instance. Here is that bind() method.


public function bind($abstract, $concrete = null, $shared = false)
{
	$abstract = $this->normalize($abstract);

	$concrete = $this->normalize($concrete);

	// If the given types are actually an array, we will assume an alias is being
	// defined and will grab this "real" abstract class name and register this
	// alias with the container so that it can be used as a shortcut for it.
	if (is_array($abstract)) {
		list($abstract, $alias) = $this->extractAlias($abstract);

		$this->alias($abstract, $alias);
	}

	// If no concrete type was given, we will simply set the concrete type to the
	// abstract type. This will allow concrete type to be registered as shared
	// without being forced to state their classes in both of the parameter.
	$this->dropStaleInstances($abstract);

	if (is_null($concrete)) {
		$concrete = $abstract;
	}

	// If the factory is not a Closure, it means it is just a class name which is
	// bound into this container to the abstract type and we will just wrap it
	// up inside its own Closure to give us more convenience when extending.
	if (! $concrete instanceof Closure) {
		$concrete = $this->getClosure($abstract, $concrete);
	}

	$this->bindings[$abstract] = compact('concrete', 'shared');

	// If the abstract type was already resolved in this container we'll fire the
	// rebound listener so that any objects which have already gotten resolved
	// can have their copy of the object updated via the listener callbacks.
	if ($this->resolved($abstract)) {
		$this->rebound($abstract);
	}
}

Read the comments to understand how it all works.


Resolve Out of The Service Container (Get Objects out of the container)

Resolving objects out of the service container simply means that when you go to use a component or object in your application code, it is the service container that handles building up any dependencies and requirements needed to simply hand over an object that is ready to do it’s work. It’s a convenience for the developer, and mostly happens with out you even realizing it.

Consider the app() helper function, which calls a make() method on the application class.


if (! function_exists('app')) {
    /**
     * Get the available container instance.
     *
     * @param  string  $make
     * @param  array   $parameters
     * @return mixed|\Illuminate\Foundation\Application
     */
    function app($make = null, $parameters = [])
    {
        if (is_null($make)) {
            return Container::getInstance();
        }

        return Container::getInstance()->make($make, $parameters);
    }
}

Here is that make() method it defers to.

/**
 * Resolve the given type from the container.
 *
 * (Overriding Container::make)
 *
 * @param  string  $abstract
 * @param  array   $parameters
 * @return mixed
 */
public function make($abstract, array $parameters = [])
{
	$abstract = $this->getAlias($abstract);

	if (isset($this->deferredServices[$abstract])) {
		$this->loadDeferredProvider($abstract);
	}

	return parent::make($abstract, $parameters);
}

Again, the comments help us understand what is happening. Ultimately the work is being done by the make() method of the Container, which is the Application parent. Here is that make() method.

public function make($abstract, array $parameters = [])
{
	$abstract = $this->getAlias($this->normalize($abstract));

	// If an instance of the type is currently being managed as a singleton we'll
	// just return an existing instance instead of instantiating new instances
	// so the developer can keep using the same objects instance every time.
	if (isset($this->instances[$abstract])) {
		return $this->instances[$abstract];
	}

	$concrete = $this->getConcrete($abstract);

	// We're ready to instantiate an instance of the concrete type registered for
	// the binding. This will instantiate the types, as well as resolve any of
	// its "nested" dependencies recursively until all have gotten resolved.
	if ($this->isBuildable($concrete, $abstract)) {
		$object = $this->build($concrete, $parameters);
	} else {
		$object = $this->make($concrete, $parameters);
	}

	// If we defined any extenders for this type, we'll need to spin through them
	// and apply them to the object being built. This allows for the extension
	// of services, such as changing configuration or decorating the object.
	foreach ($this->getExtenders($abstract) as $extender) {
		$object = $extender($object, $this);
	}

	// If the requested type is registered as a singleton we'll want to cache off
	// the instances in "memory" so we can return it later without creating an
	// entirely new instance of an object on each subsequent request for it.
	if ($this->isShared($abstract)) {
		$this->instances[$abstract] = $object;
	}

	$this->fireResolvingCallbacks($abstract, $object);

	$this->resolved[$abstract] = true;

	return $object;
}

We can see that after all the logic of this method happens, finally we are returned an Object. It is this object which is an instance of whatever you asked for in your application code. In other words, depending on the key you asked for, you get the desired (Object | Service | Component) back.

One thing to note in the make() method of the Application class, is that it makes use of a getAlias() method. This is how you are able to pass the key, alias, class, or contract, to the app() method, and the framework knows how to resolve everything correctly. Here is that very method.

protected function getAlias($abstract)
{
	return isset($this->aliases[$abstract]) ? $this->aliases[$abstract] : $abstract;
}

We can see it makes use of a convenient ternary operator to check if there is already an alias registered, and if so just return it. If not, we return the value of the $abstract variable which is most likely the key. This is how the contract or interface would be normalized into a standard alias key. In other words, if you pass something like Illuminate\Contracts\Routing\Registrar the software can translate this to the key of router.


Laravel Aliases and Contracts Summary

In this tutorial, we took a closer look at the aliases and contracts component of Laravel. We found that the interfaces themselves act as great documentation to help a developer explore the api that is exposed by any given class of the framework. We also dug into how when instantiating objects via the service container, there is a lot of flexibility in how to go about doing that. By looking closely at how all of this works, we can see how the Container and Application classes are really the meat and potatoes of Laravel. We register classes into the service container, and fetch objects out of it as needed. This is a key foundation of how Laravel works.

Click to share! ⬇️