Strategy design pattern in PHP

Design patterns are usually one of the most interesting topics in software programming. Patterns, schemes, algorithms, they are software concepts and solutions for commonly occurring issues in software engineering which are created and designed to make the interaction between classes and objects better, smoother, more elegant and probably the most important, reusable. As interesting this sounds as a definition and as a solution, it is equally important not to overwhelm software solutions implementing a huge number of complicated design patterns which, if are going on the over engineering path, could easily make the opposite, making the code hard to understand and debug, even hard to be tested.

However, if are used correctly and carefully, design patterns could make the project code base better from every point of view.

What I would like to write about in this article, is the Strategy design pattern. I’ll try to explain the nature of the pattern, I’ll show some concrete examples in PHP and will try to point you some real examples of how we can use this pattern in real case scenarios.

The strategy is a pattern with which you can implement clean solution for different implementations which are from very similar nature. There are several slightly different variations of this pattern, like using it in a combination with another design patterns, like the Factory pattern for example, but the concept and the purpose of it is the same. The Strategy pattern is a behavioral design pattern. The general idea behind the pattern is writing different implementations for similar functionalities which has the same purpose based on a single contract, which in our case is an interface.

Using Strategy design pattern in custom PHP code base

Let’s say that we need to implement a payment processor with a couple of different options for paying, like an option to pay a certain service or a product (if we are working on an e-commerce platform) with a credit card and PayPal. The purpose of this features are the same, to pay for the product or service, but we have two different implementations, paying with a credit card and paying with PayPal. So we have two different strategies for the same purpose. If we try to implement the Strategy pattern here using custom PHP implementation, we will have something like this:

interface PaymentProcessor 
{
    public function pay($data);
}

Next, the concrete implementations would something like:

class PayPalProcessor implements PaymentProcessor
{
    public function pay($data)
    {
        // Proceed data to PayPal payment processor
    }
}

And the other implementation for paying with credit card would be:

class CreditCardProcessor implements PaymentProcessor
{
    public function pay($data)
    {
        // Proceed data to credit card payment processor
    }
}

The next thing we need to do is to write our client class which will be in charge for implementing our simple payment strategy.

class PaymentClient
{
    private $processor;

    public function __construct(PaymentProcessor $processor)
    {
        $this->processor = $processor;
    }

    public function proceedToPay($data)
    {
        return $this->processor->pay($data);
    }
}

As you can see from the example above, this way, we are making our payment client class dependent only from our contract (our interface) which we made before, so our further different paying implementations will need to implement this contract and will need to contain the “pay” method. This is the essence behind the Strategy pattern.

So after this, the execution of the code will be:

// Pay with credit card
$client = new PaymentClient(new CreditCardProcessor());
$client->proceedToPay($data);

// Pay with PayPal
$client = new PaymentClient(new PayPalProcessor());
$client->proceedToPay($data);

Using Strategy design pattern in Laravel application

Let’s say that we are using a modern PHP framework for a concrete project we are working on, maybe an API or some SaaS platform. Let’s say that we have different options for the output of our API responses. For an example, maybe we need different outputs for certain responses, like a json output format in some case and array output format in another case. Using the Laravel framework, implementing our Strategy pattern in this case will look something like the following:

namespace App\ResponseStrategy\Contracts\ResponseInterface;

interface ResponseInterface
{
    public function load($data);
}

And our first implementation class which will load json response would look like:

namespace App\ResponseStrategy\Implementations\JsonStringOutput;

use App\ResponseStrategy\Contracts\ResponseInterface;

class JsonStringOutput implements ResponseInterface {

    public function __construct()
    {

    }

    public function load($data)
    {
        return json_encode($data);
    }
}

And the other implementation class which will return an array would look something like:

namespace App\ResponseStrategy\Implementations\ArrayOutput;

use App\ResponseStrategy\Contracts\ResponseInterface;

class ArrayOutput implements ResponseInterface
{

    public function __construct()
    {

    }

    public function load($data)
    {
        return (array) $data;
    }
}

Next, as we did in our first example, we need a client class to implement the contract for the different implementations:

namespace App\ResponseStrategy\OutputClient;

use App\ResponseStrategy\Contracts\ResponseInterface;

class OutputClient
{

    private $response;

    public function __construct()
    {
  
    }

    public function setOutput(ResponseInterface $response)
    {
        $this->response = $response;
    }

    public function response($data)
    {
        return $this->response->load($data);
    }

}

And now, let’s say that we have a controller with a method that decides which strategy will be implemented for the response format of a list of articles:

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\ResponseStrategy\Implementations\ArrayOutput\ArrayOutput;
use App\ResponseStrategy\Implementations\JsonStringOutput\JsonStringOutput;
use App\ResponseStrategy\OutputClient\OutputClient;
use App\Services\ArticlesService;

class ArticlesController extends Controller
{

    private $outputClient;

    private $arrayOutput;

    private $jsonStringOutput;

    private $articlesService;

    public function __construct(
        OutputClient $outputClient,
        ArrayOutput $arrayOutput,
        JsonStringOutput $jsonStringOutput,
        ArticlesService $articlesService
    ) {
        $this->outputClient = $outputClient;
        $this->arrayOutput = $arrayOutput;
        $this->jsonStringOutput = $jsonStringOutput;
        $this->articlesService = $articlesService;
    }

    public function getArticles(Request $request)
    {
        try {
            $outputFormat = $request->get('outputFormat');
            $articles = $this->articlesService->get();

            if ($outputFormat === 'array') {
                $this->outputClient->setOutput($this->arrayOutput);
            }

            if ($outputFormat === 'json') {
                return $this->outputClient->setOutput($this->jsonStringOutput);
            }

            return $this->outputClient->response($articles);
        } catch (\Exception $e) {
            return response()->json(['response' => 'Output format is not properly defined']);
        }
    }
}

As you can notice, in this case, the strategy implementation is triggered by a request parameter which is deciding what would be the output format. Currently we have two different implementations, but if we like a XML output or serialized array output, we can easily add implementation classes without the need to change any of the previous implementations and just add a simple check of the request parameter if the specified output format is XML or serialized array. And that’s it. Simple and elegant way of extending and maintaining of the existing code i probably the biggest benefit of using design patterns and that’s also the case with the Strategy pattern too.

Notices

Please be aware that the code examples of the article are just simple examples of how we can use the Strategy pattern technically. If you try to copy and just paste the code in your particular solutions, be aware that it might not work correctly or at least are not completed and polished solutions. Be aware of better structuring your code with better folder splitting and namespaces, better request handling and validation, proper authentication and authorization, using proper type hints etc.

Refactoring your huge “switch” “case” or “if” “else” statements with implementing a strategy pattern for the crucial parts, is probably a good idea and might be very beneficial for your further maintenance of the code, scaling and painless extending and testing.

However, replacing two if statements which contain some simple implementations, with fully implemented Strategy pattern with separate implementation classes and interface, might be an over-engineering method. If you really have a scenario when a piece of code which is crucial and too repetitive and the purpose and the nature of those code options is very similar, implementing a strategy might be a good solution. Try to think what you might get and what you might loose if you use the strategy pattern as a solution in certain case.