Interfaces binding with implementations in Laravel

  • avatar
  • 1.5K Views
  • 5 Likes
  • 6 mins read

An interface is a programming structure that allows the computer to enforce certain properties on an object. In object oriented programming, an interface generally defines the set of methods that an instance of a class that has that interface could respond to. It is actually a concept of abstraction and encapsulation.

Say we have a car class, a motorcycle class and a bus class. Each of these three classes should have a start engine action. How the engine is started for each vehicle is left to each particular class implementation but the fact is that they all share the action of start engine. In other words, declaring the action in the interface forces all the classes that has the interface to implement that action.

Let's see some practical examples with Laravel. Therefore, PHP is used as programming language.

Prerequisites

We are going to use a fresh Laravel 9 installation for this guide, take a look on how to create your first Laravel project. To run Laravel locally a PHP setup is also required.

Understanding interfaces

Follow the case described before we are going to define the interface used by those classes. To make the example more comprehensive we will add methods to return the amount of wheels for each vehicle type and the type itself.

<?php

namespace App\Interfaces;

interface Vehicle
{
public function startEngine(): void;

public function amountOfWheels(): int;

public function type(): string;
}

The interface is ready, now we can start with implementations of this interface. First, Motorcycle class.

<?php

namespace App\Implementations;

use App\Interfaces\Vehicle;

class Motorcycle implements Vehicle
{
public function startEngine(): void
{
//TODO add some code here
}

public function amountOfWheels(): int
{
return 2;
}

public function type(): string
{
return 'Motorcycle';
}
}

Then, Car class.

<?php

namespace App\Implementations;

use App\Interfaces\Vehicle;

class Car implements Vehicle
{
public function startEngine(): void
{
//TODO add some code here
}

public function amountOfWheels(): int
{
return 4;
}

public function type(): string
{
return 'Car';
}
}

Finally, Bus class.

<?php

namespace App\Implementations;

use App\Interfaces\Vehicle;

class Bus implements Vehicle
{
public function startEngine(): void
{
//TODO add some code here
}

public function amountOfWheels(): int
{
return 6;
}

public function type(): string
{
return 'Bus';
}
}

That's it. We now have three type of vehicles that implement the same interface called Vehicle. We have detailed examples in our official GitHub repository with controllers loading the same interface but different implementation based on certain conditions.

Using interfaces

The use of interfaces make the system more flexible and easier to extend. An interface allows you to specify a contract that a class must implement. When a class implements an interface, it’s called a concrete class. The concrete class needs to implement all the methods of the interface.

Imagine having images library. Very simple interface is defined in order to allow uploading images to your library:

<?php

namespace App\Interfaces;

interface ImageManagement
{
public function upload(): bool;

public function remove(): bool;
}

The interface has two methods: first to upload a file and second to remove a file. In both cases boolean will be returned as a success flag. The concrete implementation will use local disk to store uploads. Note that our main goal is to show how interfaces are used for that reason implementations code is not so important.

<?php

namespace App\Implementations;

use App\Interfaces\ImageManagement;

class LocalDiskImageManagement implements ImageManagement
{
public function upload(): bool
{
// TODO implement code to save the file on local disk

return true; // File uploaded
}

public function remove(): bool
{
// TODO implement code to remove the file from local disk

return true; // File removed
}
}

Now, imagine that your images library grows very fast and you don't have enough space to save files locally. Let's switch the storage to some external service, for example, Google Drive. We already defined the contract and how uploads should work, so the implementation is only needed.

<?php

namespace App\Implementations;

use App\Interfaces\ImageManagement;

class GoogleDriveImageManagement implements ImageManagement
{
public function __construct()
{
//TODO inject Google Drive package
}

public function upload(): bool
{
// TODO implement code to save the file on Google Drive

return true; // File uploaded
}

public function remove(): bool
{
// TODO implement code to remove the file from Google Drive

return true; // File removed
}
}

Want to switch to a different service? Amazon S3?

<?php

namespace App\Implementations;

use App\Interfaces\ImageManagement;

class AmazonS3ImageManagement implements ImageManagement
{
public function __construct()
{
//TODO inject Amazon package
}

public function upload(): bool
{
// TODO implement code to save the file on Amazon S3

return true; // File uploaded
}

public function remove(): bool
{
// TODO implement code to remove the file from Amazon S3

return true; // File removed
}
}

Different implementations, same interface. We don't need to change core files and how other classes interact with this interface. Our domain logic remains untouched and only concrete implementation changes.

Binding interfaces

If you write a class that implements an interface and you wish to use dependency injection you must tell the container how to resolve that interface. The Laravel service container is a powerful tool for managing class dependencies and performing dependency injection.

App Service Provider

This is where you are supposed to set your application bindings. Locate App/Providers/AppServiceProvider.php and add the following lines to the register() method.

$this->app->bind(Interfaces\ImageManagement::class, Implementations\GoogleDriveImageManagement::class);

It will bind images management interface with the concrete Google Drive implementation. From now on, every time we inject the image management interface the Laravel service container will use Google Drive implementation.

Contextual Binding

In some cases, as our vehicles example, you may have several classes that utilize the same interface but you wish to conditionally inject different implementations. Laravel provides a simple, fluent interface for defining this behavior. Locate App/Providers/AppServiceProvider.php and add the following lines to the register() method.

$this->app->when(BusController::class)
->needs(Vehicle::class)
->give(function () {
return new Bus();
});

$this->app->when(CarController::class)
->needs(Vehicle::class)
->give(function () {
return new Car();
});

$this->app->when(MotorcycleController::class)
->needs(Vehicle::class)
->give(function () {
return new Motorcycle();
});

Depending on the controller that makes the request to the interface a different implementation will be provided.

Conclusion

Interfaces provides us a lot of power to decouple the code and the definition. We can have the same contract and different implementations. Or switch the implementation much easier maintaining the same contract. A solid understanding of what are interfaces and how to use them might help us a lot structuring projects and writing maintainable and understandable code.

Credits

Official GitHub: https://github.com/hibit-dev/laravel-bndings

 Join Our Newsletter

Get the latest news and articles to your inbox every month

0 Comments

Leave a Reply

Your email address will not be published.