Strategy Design Pattern in PHP

The strategy design pattern is another commonly used design pattern.

It is used when you might have multiple ways (different implementations) to do a task.

An example

for example if you have files or data that you need to save you might have the option of saving them to a database, to the local filesystem or upload it to a 3rd party cloud storage provider such as Amazon S3.

You could write a long winded if/elseif/else function like this, letting you decide where to store each item:

But don't write something like this! This is just to show what you could do...
<?php
function save_data($input, $whereTo='db')
{
    if ($whereTo == 'db') {
        var_dump(__FUNCTION__ . " Saving '$input' to database");
    }
    elseif ($whereTo == 'filesystem') {
        var_dump(__FUNCTION__ . " Saving '$input' to local filesystem");
    }
    elseif ($whereTo == 'amazons3') {
        var_dump(__FUNCTION__ . " Saving '$input' to amazon s3");
    }
    else {
        throw new \Exception;
    }
}
$data_to_save = "foo";
save_data($data_to_save,'db');

Which would output string(34) "save_data Saving 'foo' to database". But this code is really messy

(this is a very simplified example - in reality, there would be big chunks of messy code for a function like this).

It is much better to use the strategy design pattern. Here is how it would look:

Define an interface

Each way of doing a task will be its own class (so one class for DBSaver, one for LocalFileSaver, one for AmazonS3Saver). Each of these tasks (for this example) must have a save($input) method. So there should be an interface:

<?php
interface Saver
{
    public function save($input);
}

This is a very simple interface, with just one method. In the real world, you might have something a bit more complicated.

Now we need the 3 example classes for the different ways of doing this task (of saving)
<?php
class DBSaver implements Saver
{
    public function save($input)
    {
        var_dump(__METHOD__ . " Saving '$input' to DB table");
    }
}
class LocalFileSaver implements Saver
{
    public function save($input)
    {
        var_dump(__METHOD__ . " Saving '$input' to local filesystem");
    }
}
class AmazonS3Saver implements Saver
{
    public function save($input)
    {
        var_dump(__METHOD__ . " Saving '$input' to amazon s3 (cloud) storage");
    }
}
And now let's save a file

Maybe this data was submitted by a user, and they can select where it should be saved.

(I'm using a function here to keep things quicker to explain, rather than set up a whole class for this)

<?php
$user_submitted_data = "foo"; // the data to save
$user_submitted_where_to_save_to = 'local';  // where to save it
// so we can get the class to use, from the user submitted value
// I will assume some validation has already occurred and we know that $user_submitted_where_to_save_to is one of these keys
$savers = [
    'db'=>DBSaver::class,
    'local'=>LocalFileSaver::class,
    's3'=>AmazonS3Saver::class,
];
// create the required object - 
$saver_object = new $savers[$user_submitted_where_to_save_to]; // i.e. new $savers['local'] which is the same as new LocalFileSaver();
// and send that object (and the submitted data) to some function that will save it.
save($user_submitted_data, $saver_object);
// notice that the 2nd param for this function is an object that implements the Saver interface
function save($data, Saver $saver)
{
    return $saver->save($data);
}

The function at the end isn't needed in this simple example, but I wanted to just show that you can type hint the interface

Some notes on the strategy pattern

  • I've used just interfaces here. But you can also use abstract classes, if they will be sharing common code.

Real life examples of the strategy pattern

If you use Laravel then you are probably used to the config options, such as in config/logging.php you have this line:

  'default' => env('LOG_CHANNEL', 'stack'),

You can set it to various options such as 'stack', 'stderr', 'single' - each of these will load a different logging class - this uses the strategy pattern.

The same for encryption - you might have a main function (or method) to encrypt things. But in the config options, you might be able to define what encryption method to use. A strategy pattern will be used, with each encryption method being its own class that implements a common interface.

Design pattern type: Behavioural design pattern

Comments Strategy Design Pattern in PHP