Latest Posts

Leveraging Laravel's built in driver functionality

Tutorials

Laravel makes heavy use of drivers for everything from session management to caching, and there's no reason why you shouldn't use it too!


Laravel provides an out of the box driver functionality which is already used extensively by Laravel core. It allows you to quickly and easily switch between different implementations of the same thing, such as on the Cache helper where you can choose from redis, database and file to name a few.

It's built to be flexible, so not only does this give you the opportunity to write your own drivers to extend on existing Laravel functionality, it also gives you the tools to easily create your own implementations too.

Use case

This architecture is perfect if you have multiple different classes that inherit a common interface and achieve the same end goal. In my use case, I had numerous systems to pull product and stock information from. To make my app as flexible as it could be, it made sense to turn each of these suppliers into a driver so that I could call on them from one central place.

Lets get started

The interface

Lets start with the interface, for the purpose of this demo I'll keep it short and sweet. We'll have one class the fetches the products fetchProducts and one that fetches the stock counts fetchStock.

namespace App\Stock\Contracts;

interface StockDriver
{
    public function fetchProducts(): array;
		
    public function fetchStockFigures(): array;
}
The drivers

For the sake of this example, lets use Google and Apple as our stock suppliers. For the purpose of the example we'll hard code the return values.

Apple:

namespace App\Stock\Drivers;

use App\Stock\Contracts\StockDriver;

class AppleDriver implements StockDriver
{
    public function fetchProducts(): array
    {
        return [
            [
                'sku' => 'iphone-xr',
                'name' => 'iPhone XR',
            ],
            [
                'sku' => 'iphone-13-pm',
                'name' => 'iPhone 13 Pro Max',
            ],
        ];
    }

    public function fetchStock(): array
    {
        return [
            'iphone-xr' => 12,
            'iphone-13-pm' => 24,
        ];
    }
}

And Google:

namespace App\Stock\Drivers;

use App\Stock\Contracts\StockDriver;

class GoogleDriver implements StockDriver
{
    public function fetchProducts(): array
    {
        return [
            [
                'sku' => 'pixel-7a',
                'name' => 'Google Pixel 7a',
            ],
            [
                'sku' => 'pixel-9-pro',
                'name' => 'Google Pixel 9 Pro',
            ],
        ];
    }

    public function fetchStock(): array
    {
        return [
            'pixel-7a' => 6,
            'pixel-9-pro' => 30,
        ];
    }
}

In all likelyhood, inside a real application these would be pinging off to an API, bringing back the results and then being standardised into some kind of Data Transfer Object - but I'll let you do that!

The manager

The manager is a very simple implementation that extends Laravel's default manager, and defines the default driver. This is the core that allows for easy switching between drivers.

namespace App\Stock;

use Illuminate\Support\Manager;
use App\Stock\Contracts\StockDriver;

/**
 * @mixin StockDriver
 */
class StockManager extends Manager
{
    public function getDefaultDriver()
    {
        return config('services.stock.driver');
    }
}

Notice the use of the PHPDoc @mixin. This allows us to get type-hints based on our interface when using our StockManager around our project - a super useful little trick!

You can supply any of your drivers as the default driver for this manager. That'll be used when a specific driver isn't specified. I'd normally use a mix of config files and environment variables to achieve this. Here's an example of how I'd use the services config file to achieve the above:

config/services.php

<?php

return [
    
    // the rest of your config here
    
    'stock' => [
        'driver' => env('STOCK_DRIVER'),
    ]
];	

Then in your .env file you can define the default stock driver to use, for example: STOCK_DRIVER=google

Let Laravel know

Now we've created our driver, we need to let Laravel know about it. We can do this by adding code within the register method of a service provider. In this case we'll just use Laravel's pre-existing AppServiceProvider.


namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Stock\StockManager;
use App\Stock\Drivers;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        $this->app->singleton(StockManager::class, function ($app) {
					
            // create an instance of our StockManager
            $manager = new StockManager($app);

            // let our manager know about our drivers
            $manager->driver('apple', fn() => new Drivers\AppleDriver());
            $manager->driver('google', fn() => new Drivers\GoogleDriver());
            $manager->driver('mock', fn() => new Drivers\MockDriver());

            return $manager;
        });
    }
}

And that's it, we've created ourself a StockManager that can call on Google or Apple's stock drivers whenever we need them. I also like to create a mock driver or a null driver that I can call upon during testing.

Putting it all together

Great, we've done everything we need for a fully functioning StockManager that can handle multiple drivers. Now wherever you are in your code, you can call on Laravel to give you an instance of your manager.

Lets use Laravel's dependency injection to write a quick console command that'll allow us to fetch products from any drivers we have registered:

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Stock\StockManager;

class FetchProductsCommand extends Command
{
    protected $signature = 'app:fetch-products {driver}';

    protected $description = 'Fetch and update our stock from the provider.';

    public function handle(StockManager $manager): void
    {
        $driver = $this->argument('driver');

        $data = $manager->driver($driver)->fetchProducts();

        // ToDo: something with our newly fetched $data!
    }
}

And with that, we have a newly created Artisan command where we can specify exactly what stock provider we want to use.

php artisan app:fetch-products apple
php artisan app:fetch-products google

The console command is just one of an infinite amount of possible use cases for this technique and what you do with it is really up to you! If you enjoyed reading this, please consider subscribing for future updates!