← jordandalton.com

Advanced Laravel: Contract & Implementations

If you're a programmer, you've likely heard the term "contracts" thrown around in discussions about software development. But what exactly are contracts, and how do they relate to programming?

Contracts are an advanced programming topic that is common to many programming languages. In technical terms, contracts are called "interfaces," but for the purposes of this article, we will use the term "contracts" to make things simpler.

A contract is like a business agreement in that it is an agreement between parties. Contracts usually have terms, which are the conditions of the agreement that both parties agree to follow. When we take this and translate it into code, we have something like this:

<?php

namespace App\Contracts;

interface Dvr
{
    public function play();

    public function pause();
}

In this contract, we are agreeing to do business with the DVR and the terms (methods) of the contract which we must abide to — play() and pause() the DVR. As you can imagine, there are multiple companies that provide DVR services. Here in the United States, two of the largest DVR providers are Honeywell and Haydon.

Let's now create an API service for both Honeywell and Haydon.

<?php

namespace App\Services;

class HoneywellApi
{
    public function pressPlay()
    {
        return 'Play Honeywell DVR';
    }

    public function pressPause()
    {
        return 'Pause Honeywell DVR';
    }
}
<?php

namespace App\Services;

class HaydonApi
{
    public function play()
    {
        return 'Play Haydon DVR';
    }

    public function pause()
    {
        return 'Pause Haydon DVR';
    }
}

Something you've likely noticed is that the method names used by HoneywellApi differ from those of HaydonApi, but this is completely fine. Why? Because we cannot make all API providers and SDKs be designed the same. We'll now create an implementation to represent each API while also abiding by the contract.

<?php

namespace App\Implementations;

use App\Contracts\Dvr;
use App\Services\HoneywellApi;

class Honeywell implements Dvr
{
    public function __construct(protected HoneywellApi $api) {}

    public function play()
    {
        return $this->api->pressPlay();
    }

    public function pause()
    {
        return $this->api->pressPause();
    }
}
<?php

namespace App\Implementations;

use App\Contracts\Dvr;
use App\Services\HaydonApi;

class Haydon implements Dvr
{
    public function __construct(protected HaydonApi $api) {}

    public function play()
    {
        return $this->api->play();
    }

    public function pause()
    {
        return $this->api->pause();
    }
}

In order to utilize one of these providers, we need to create a controller along with a route that points to it. As you're about to see, we will inject the interface into the constructor. This is a critical piece as it is what allows us to swap between API providers should we ever need to. (Later, we'll tap into Laravel's service container to bring it all together.)

<?php

namespace App\Http\Controllers;

use App\Contracts\Dvr;

class DvrController extends Controller
{
    public function __construct(protected Dvr $dvr) {}

    public function play()
    {
        return $this->dvr->play();
    }

    public function pause()
    {
        return $this->dvr->pause();
    }
}

The controller now only cares about playing or pausing the DVR — it doesn't care (nor should it) about who will be providing the underlying service.

Moving along, we'll create a route for each controller method in our api.php routes file.

<?php

use Illuminate\Support\Facades\Route;

Route::get('dvr/play', [\App\Http\Controllers\DvrController::class, 'play']);
Route::get('dvr/pause', [\App\Http\Controllers\DvrController::class, 'pause']);

When we fire up the browser and go to our play route (http://example.dev/api/dvr/play) we run into the following:

Illuminate\Contracts\Container\BindingResolutionException

Target [App\Contracts\Dvr] is not instantiable while building [App\Http\Controllers\DvrController].

What is happening is that Laravel sees the Dvr contract, so it is attempting to resolve it to an implementation, but cannot locate one, which produces the error.

Now let's have a little fun. We will go into our AppServiceProvider and tell Laravel that when the application looks for an implementation of Dvr, we'll return the Honeywell implementation.

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(
            \App\Contracts\Dvr::class,
            \App\Implementations\Honeywell::class
        );
    }

    public function boot(): void
    {
        //
    }
}

Now we'll go back to the page we just visited and refresh it.

Play Honeywell DVR

Pretty cool, huh? Now let's visit the pause route (http://example.dev/api/dvr/pause):

Pause Honeywell DVR

Now let's say your manager comes in and states we're going to switch from Honeywell to Haydon. You'll only need to update the binding in AppServiceProvider:

$this->app->bind(
    \App\Contracts\Dvr::class,
    \App\Implementations\Haydon::class
);

Visit the play route and you get:

Play Haydon DVR

And the pause route:

Pause Haydon DVR

Supporting multiple providers

Let's now get even more advanced. Imagine the manager comes back and says "We actually want to be able to use both Honeywell and Haydon. Can you make that happen?" This is where you say "I've got you covered, boss!"

Given we'll now be offering multiple providers, let's take a DRY approach to our controllers.

<?php

namespace App\Http\Controllers;

class HaydonController extends DvrController {}
<?php

namespace App\Http\Controllers;

class HoneywellController extends DvrController {}

Both extend the DvrController we originally created. Let's update our api.php routes file to correspond to them:

<?php

use Illuminate\Support\Facades\Route;

Route::get('dvr/play', [\App\Http\Controllers\DvrController::class, 'play']);
Route::get('dvr/pause', [\App\Http\Controllers\DvrController::class, 'pause']);

Route::get('dvr/play/honeywell', [\App\Http\Controllers\HoneywellController::class, 'play']);
Route::get('dvr/pause/honeywell', [\App\Http\Controllers\HoneywellController::class, 'pause']);

Route::get('dvr/play/haydon', [\App\Http\Controllers\HaydonController::class, 'play']);
Route::get('dvr/pause/haydon', [\App\Http\Controllers\HaydonController::class, 'pause']);

When we visit the Honeywell play endpoint (http://example.test/api/dvr/play/honeywell) we run into a slight problem:

Play Haydon DVR

Laravel currently knows that when we ask for Dvr we return the Haydon implementation — so both controllers get the same one. We need to give the service container more context:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(
            \App\Contracts\Dvr::class,
            \App\Implementations\Haydon::class
        );

        $this->app
            ->when(\App\Http\Controllers\HoneywellController::class)
            ->needs(\App\Contracts\Dvr::class)
            ->give(\App\Implementations\Honeywell::class);

        $this->app
            ->when(\App\Http\Controllers\HaydonController::class)
            ->needs(\App\Contracts\Dvr::class)
            ->give(\App\Implementations\Haydon::class);
    }

    public function boot(): void
    {
        //
    }
}

Now when we visit the endpoints for either Honeywell or Haydon we get the correct data returned. Winner.

Conclusion

Contracts and implementations are powerful tools in Laravel that allow you to define a standard interface and write code that can adapt to different implementations. By using contracts, you can build more modular, scalable, and maintainable code that can be easily updated or switched out with different implementations if needed. I hope this article has been informative and helpful in your Laravel journey, and I encourage you to continue exploring and experimenting with this fascinating framework!


Source code is available on GitHub. Follow me on Twitter and YouTube.

Enjoyed this?

Book a build day.

Take the ideas in here from concept to working software in one focused day.

Book a build day →
jordandalton.com — © 2026 Jordan DaltonBuilt in a day, naturally.