Extending core Laravel bindings

In this article, we are going to talk about how to extend core bindings in Laravel.

Orobo Lucky
Feb 3 · 5 min read
Photo by Diego PH on Unsplash

Laravel, by design, is an extensible framework. It makes extending core services, and even third-party services a cinch. It’s this flexibility that we so love about it. You could, for instance, replace a service with an entirely new one or just modify the service and have laravel resolve the original service to the modified one.

Laravel has a powerful Inversion of Control (IoC) / Dependency Injection Container. It helps us manage class dependencies and it’s the glue that holds the entire framework together.

Before we go ahead and talk about how to extend services in the container, let’s first get an idea of how we can register and resolve services into and out of the service container.

Binding and Resolution

The service container is something of a registry for a laravel application. You can store and retrieve objects in and out of the container. You define how an object should be created at one point in your application, usually, in a service provider — this is the binding — and whenever you need that object elsewhere in your application, you simply ask the service container for it and it will create that object and resolve its dependencies — the resolution.

Binding

This is the registration phase where you instruct the service container how to build your service. Say you have a simple class Service that has dependencies and provides a given service to your application:

<?phpclass Service
{
protected $dependency;
public function __construct($dependency)
{
$this->dependency = $dependency
}
public function serve()
{
//
}
}

To register it with the service container, simply grab an instance of the container and call the bind method on it:

app()->bind('service', function ($app) { 
// Make the dependency that the SC doesn't know about
$dependency = new Dependency();
return new Service($dependency);
});

This registers the service in the container and tells the service container how to create the object. There are other ways to register services in the container and the method you use depends on the context of the service and how it will be used within the application.

This is essentially the same pattern that’s being used internally by laravel to register its core services.

Resolution

After registering the service in the service container, you may now retrieve the service from the container when you need it:

$service = app()->make('service');

This instructs laravel to go into the container and retrieve (resolve) the service from it. This process resolves the service exactly how it was told to during the binding phase. If you’re interested to learn more about the service container, check out the laravel documentation.


Extending a Service

The laravel service container provides us with ways to configure or decorate resolved services from the container. This allows us to tap into resolved services and modify them. For example, you could resolve — or simply new up an instance — a custom dependency and inject it into a core laravel binding. This ability to extend bindings gives us the flexibility we need to modify resolved services and change their behavior.

Exactly how you extend a service depends entirely on context and how you want the service to behave after it has been resolved from the container.

For instance, we could adopt the decorator pattern and create a decorated service from the original service.

The decorator pattern is a design pattern that lets you dynamically change or modify the behavior of an object at runtime by wrapping them in an object of a decorated class.

What this means for our purposes is, we take the original resolved service, wrap it in a new decorated class and return an instance of the decorated class.


The Extend Method

Within the Container, laravel defines an extend method that lets you extend services in the container. The extend method takes the service you want to extend as the first argument and a Closure as the second. The Closure should run additional code to modify or decorate the service. It receives the original service and an instance of the container and should return the modified service:

app()->extend('service', function ($service, $app) {
return new DecoratedService($service);
});

DecoratedService now extends Service and provides added functionality to it.

How it works

When you call the extend method on the container and pass in the service you want to extend, laravel checks if the service already exists as a shared instance — registered as a singleton. If it does exist as a shared instance, laravel immediately calls the Closure you passed to the extend method and replaces the shared instance with the result. This then becomes the modified service.

If the service is not a shared instance, laravel adds the Closure as an extender to the service. Now when you go and resolve the original service from the container, laravel checks if the service has any defined extenders and applies them to the service. This is how you get back a modified instance of the original service.

Example — Injecting a RedisPresenceVerifier

This example demonstrates how to modify a resolved service from the container. We’ll be modifying the validator service (Illuminate\Validation\Factory) and configuring it to use a custom presence verifier that verifies against a Redis store, as opposed to the default DatabasePresenceVerifier that verifies against a database. The presence verifier is responsible for handling the unique and exists rules in a validator.

First, we’ll extend the validator service in our service provider:

AppServiceProvider

As previously mentioned, the resolved service would be returned from the container and passed as the first argument to the Closure specified when extending the service. Now that we have the resolved instance, we can configure the validator to use our custom presence verifier (RedisPresenceVerifier).

We extended the validator service and injected a new dependency — the RedisPresenceVerifier — and returned an instance of the modified validator service.

Next, let’s implementRedisPresenceVerifier. This class implements the Illuminate\Validation\PresenceVerifierInterface interface.

Now, each time the validator service is resolved from the container, it will be configured to use our custom verifier and we can now use the exists and unique rules which would verify against a Redis store.

Note that our RedisPresenceVerifier makes the assumption that data is being stored as a list and it’s not a complete implementation of a RedisPresenceVerifier.

Using a test, we can verify that the service was actually modified and that our dependency was injected into the service.

public function testPresenceVerifierIsRedis() 
{
$this->assertInstanceOf(
RedisPresenceVerifier::class,
app('validator')->getPresenceVerifier()
);
}
// Passes

Conclusion

We’ve talked about how to extend core laravel bindings in the container but this really isn’t exclusive to core bindings, the same can be applied to any binding in the service container.

One thing of note, as I’ve found, is that extending a service has a nice added advantage of always resolving the modified service out of the container. This is useful for cases when the original service is a deferred one and might ultimately override your service if you just replaced the binding in the container.

Thank you for reading.

Orobo Lucky

Written by

Software developer | Movie Enthusiast.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade