Using the Strategy Pattern in Laravel
Tags: laravel, php, programming, webdev
Published: 2021-06-17 08:02:50

## Introduction

In software and web development, it's always important to write code that is maintainable and extendable. The solution that you first create will likely change over time. So, you need to make sure you write your code in a way that doesn't require a whole rewrite or refactor in the future.

The **strategy pattern** can be used to improve the extendability of your code and also improve the maintainability over time.

### Intended Audience

This post is written for Laravel developers who have an understanding of how interfaces work and how to use them to decouple your code. If you're a little unsure about this subject, check out my post from last week that discusses [using interfaces to write better PHP code](https://ashallendesign.co.uk/blog/using-interfaces-to-write-better-php-code).

It's also strongly advised that you have an understanding of <a href="https://laravel.com/docs/8.x/controllers#dependency-injection-and-controllers" target="_blank" rel="noopener noreferrer">dependency injection</a> and how the <a href="https://laravel.com/docs/8.x/container#introduction" target="_blank" rel="noopener noreferrer">Laravel service container</a> works.

## What Is the Strategy Pattern?

<a href="https://refactoring.guru/design-patterns/strategy" target="_blank" rel="noopener noreferrer">Refactoring Guru</a> defines the strategy pattern as a "behavioural design pattern that lets you define a family of algorithms, put each of them into a separate class, and make their objects interchangeable.". This might sound a bit scary at first, but I promise that it's not as bad as you think. If you want to read more into design patterns, I'd highly recommend checking out <a href="https://refactoring.guru/" target="_blank" rel="noopener noreferrer">Refactoring Guru</a>. They do a great job of explaining the strategy pattern in depth as well as other structural patterns.

The strategy pattern is basically a pattern that helps us to decouple our code and make it super extendable. In fact, you probably use it every day with Laravel without even noticing whenever you use the `Storage` and `Cache` facades  (and a few other places too). Let's say that you use this code:

```php
Cache::put('name', 'Ash Allen', 600);
```

The code above is resolving a class from the service container by using the <a href="https://laravel.com/docs/8.x/facades#introduction" target="_blank" rel="noopener noreferrer">facade</a>. I won't get into how facades work as it's not really the purpose of this article, but the important bit to know is that the `Cache` facade here is binding an interface (`Illuminate\Contracts\Cache\Factory` to be specific) to a class and using it. It is then storing the word '*Ash Allen*' under the key '*name*' for 10 minutes.

As you'll have probably noticed in the <a href="https://laravel.com/docs/8.x/cache#introduction" target="_blank" rel="noopener noreferrer">Laravel documentation</a> and in your project's config, Laravel supports a few different drivers for caching, including: <a href="https://redis.io/" target="_blank" rel="noopener noreferrer">Redis</a>, <a href="https://aws.amazon.com/dynamodb/" target="_blank" rel="noopener noreferrer">DynamoDB</a>, <a href="https://memcached.org/" target="_blank" rel="noopener noreferrer">Memcached</a> and the database. So, for example, if we were to set our cache driver in our *.env* file to `CACHE_DRIVER=redis`, when we run our code snippet above, the data would be stored in our Redis cache. However, if we were to change the driver to be `CACHE_DRIVER=database`, this would result in data being stored in the database instead.

Each different cache driver has it's own respective class that deals with how the framework interacts with the cache. So, when we change the driver in our *.env*, Laravel needs to know which driver to use. This is where the strategy pattern steps in. Under the hood, whenever we use the `Cache` facade, Laravel is actually resolving an `Illuminate\Contracts\Cache\Factory` interface from the service container. It does this by checking the config value (e.g. *redis*, *database*, etc.) and then mapping that interface to a class. For example, whenever we have our cache driver set to `CACHE_DRIVER=redis` and we try to resolve the `Factory` interface, we will get a class that specifically interacts with the Redis cache and nothing else.

As you can see, the strategy pattern can improve the extendability of your code. For example, if we wanted to create our own custom cache driver, we could just create the implementation and then let Laravel know that it is available for using. For a bit more context, <a href="https://laravel.com/docs/8.x/cache#adding-custom-cache-drivers" target="_blank" rel="noopener noreferrer">check out the Laravel documentation</a> to see an example of how you can add your own driver.

## Using the Strategy Pattern in Laravel

Now that we have a basic idea of what the strategy pattern is, let's look at how we can use it ourselves in our own Laravel application.

Let's imagine that we have a Laravel application that users can use for getting exchange rates and currency conversions. Now, let's say that our app uses an external API (<a href="https://exchangeratesapi.io" target="_blank" rel="noopener noreferrer">exchangeratesapi.io</a>) for getting the latest currency conversions.

We could create this class for interacting with the API:

```php
class ExchangeRatesApiIO
{
    public function getRate(string $from, string $to): float
    {
        // Make a call to the exchangeratesapi.io API here and fetch the exchange rate.

        return $rate;
    }
}
```

Now, let's use this class in a controller method so that we can return the exchange rate for a given currency. We're going to use dependency injection to resolve the class from the container:

```php
class RateController extends Controller
{
    public function __invoke(ExchangeRatesApiIO $exchangeRatesApiIO): JsonResponse
    {
        $rate = $exchangeRatesApiIO->getRate(
            request()->from,
            request()->to,
        );

        return response()->json(['rate' => $rate]);
    }
}
```

This code will work as expected, but we've tightly coupled the `ExchangeRatesApiIO` class to the controller method. This means that if we decide to migrate over to using a different API, such as <a href="https://fixer.io" target="_blank" rel="noopener noreferrer">Fixer</a>, in the future, we'll need to replace everywhere in the codebase that uses the `ExchangeRatesApiIO` class with our new class. As you can imagine, in large projects, this can be a slow and tedious task sometimes. So, to avoid this issue, instead of trying to instantiate a class in the controller method, we can use the strategy pattern to bind and resolve an interface instead.

Let's start by creating a new `ExchangeRatesService` interface:

```php
interface ExchangeRatesService
{
    public function getRate(string $from, string $to): float;
}
```

We can now update our `ExchangeRatesApiIO` class to implement this interface:

```php
class ExchangeRatesApiIO implements ExchangeRatesService
{
    public function getRate(string $from, string $to): float
    {
        // Make a call to the exchangeratesapi.io API here and fetch the exchange rate.

        return $rate;
    }
}
```

Now that we've done that, we can update our controller method to inject the interface rather than the class:

```php
class RateController extends Controller
{
    public function __invoke(ExchangeRatesService $exchangeRatesService): JsonResponse
    {
        $rate = $exchangeRatesService->getRate(
            request()->from,
            request()->to,
        );

        return response()->json(['rate' => $rate]);
    }
}
```

Of course, we can't instantiate an interface; we want to instantiate the `ExchangeRatesApiIO` class. So, we need to tell Laravel what to do whenever we try and resolve the interface from the container. We can do this by using a <a href="https://laravel.com/docs/8.x/providers#introduction" target="_blank" rel="noopener noreferrer">service provider</a>. Some people prefer to keep things like this inside their `AppServiceProvider` and keep all of their bindings in one place. However, I prefer to create a separate provider for each binding that I want to create. It's purely down to personal preference and whatever you feel fits your workflow more. For this example, we're going to create our own service provider.

Let's create a new service provider using the Artisan command:

```
php artisan make:provider ExchangeRatesServiceProvider
```

We'll then need to remember to register this service provider inside the ` app/config.php ` like below:

```php
return [
    
    'providers' => [
        // ...
        \App\Providers\ExchangeRatesServiceProvider::class,
        // ...
    ],
	
]
```

Now, we can add our code to the service provider to bind the interfaces and class:

```php
class ExchangeRatesServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(ExchangeRatesService::class, ExchangeRatesApiIO::class);
    }
}
```

Now that we've done all of this, when we dependency inject the `ExchangeRatesService` interface in our controller method, we'll receive an `ExchangeRatesApiIO` class that we can use.

## Taking It Further

Now that we know how to bind an interface to a class, let's take things a bit further. Let's imagine that we want to be able to decide whether to use the ExchangeRatesAPI.io or the Fixer.io API whenever we'd like just by updating a config field.

We don't have a class yet for dealing with the Fixer.io API yet, so let's create one and make sure that it implements the `ExchangeRatesService` interface:

```php
class FixerIO implements ExchangeRatesService
{
    public function getRate(string $from, string $to): float
    {
        // Make a call to the Fixer API here and fetch the exchange rate.

        return $rate;
    }
}
```

We'll now create a new field in our `config/services.php` file:

```php
return [

    //...

    'exchange-rates-driver' => env('EXCHANGE_RATES_DRIVER'),

];

```

We can now update our service provider to change which class will be returned whenever we resolve the interface from the container:

```php
class ExchangeRatesServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(ExchangeRatesService::class, function ($app) {
            if (config('services.exchange-rates-driver') === 'exchangeratesapiio') {
                return new ExchangeRatesApiIO();
            }
		  
            if (config('services.exchange-rates-driver') === 'fixerio') {
                return new FixerIO();
            }
		  
            throw new Exception('The exchange rates driver is invalid.');
        });
    }
}
```

Now if we set our exchanges rates driver in our *.env* to `EXCHANGE_RATES_DRIVER=exchangeratesapiio` and try to resolve the `ExchangeRatesService` from the container, we will receive an `ExchangeRatesApiIO` class. If we set our exchanges rates driver in our *.env* to `EXCHANGE_RATES_DRIVER=fixerio` and try to resolve the `ExchangeRatesService` from the container, we will receive an `FixerIO` class. If we set driver to anything else accidentally, an exception will be thrown to let us know that it's incorrect.

Due to the fact that both of the classes implement the same interface, we can seamlessly change the `EXCHANGE_RATES_DRIVER` field in the *.env* file and not need to change any other code anywhere.


## Conclusion

Is your brain fried yet? If it is, don't worry! Personally, I found this topic pretty difficult to understand when I first learnt about it. I don't think I started to really understand it until I put it into practice and used it myself. So, I'd advise spending a little bit of time experimenting with this yourself. Once you get comfortable with using it, I guarantee that you'll start using it in your own projects.

Hopefully, this article has given you an overview of what the strategy pattern is and how you can use it in Laravel to improve the extendability and maintainability of your code.

If you found this post useful, I'd love to hear about it in the comments.

Keep on building awesome things! 🚀