Design patterns in PHP: Strategy
This tutorial describes a TDD approach to refactor code to Strategy pattern. Starting from working code and some test, refactoring steps are applied to obtain cleaner code

Introduction
In the previous article I described how to use the Refactor to the Replace Constructors with creational methods pattern, which is used to create a cleaner and more descriptive interface for similar objects creation.
In this tutorial, I will go further with the same example and introduce another common Design Pattern, the Strategy pattern. Its main purpose is to replace conditionals logic with classes implementing a delegate method, each implementing the calculation of each variant. This leads to the following advantages:
- Decreases complexity from code by eliminating conditionals
- Enables runtim algorithm hot-swap
- Simplifies class by moving algorithms to hiearachies, thus increasing level of abstraction
Initial setup
We start from the arrival point of the previous article, using the following class ParamManager
class ParamManager
{
private $param1;
private $param2;
public function __construct($param1 = null, $param2 = null)
{
$this->param1 = $param1;
$this->param2 = $param2;
}
public static function withTwoArguments($param1, $param2)
{
return new ParamManager($param1, $param2);
}
public static function withOneArgument($param1)
{
return new ParamManager($param1);
}
public static function withNoArguments()
{
return new ParamManager();
}
public function getValue()
{
if ($this->param1 && $this->param2) {
return 'both';
}
if ($this->param1) {
return 'first';
}
return 'none';
}
}
which has three creational methods for the three variants:
- Both
param1
andparam2
set - Only
param1
set - No params set
Depending on the values of param1
and param2
, the getValue()
methods returns a different value. The purpose of the Strategy pattern is to eliminate this conditional.
We also have a backing test ensuring that the refactoring steps don't change code behavior:
class ExampleTest extends TestCase
{
/** @test */
public function it_builds_param_manager_with_two_parameters()
{
$manager = ParamManager::withTwoArguments('a', 'b');
$this->assertEquals('both', $manager->getValue());
}
/** @test */
public function it_builds_param_manager_with_one_parameter()
{
$manager = ParamManager::withOneArgument('a');
$this->assertEquals('first', $manager->getValue());
}
/** @test */
public function it_builds_param_manager_with_no_parameters()
{
$manager = ParamManager::withNoArguments();
$this->assertEquals('none', $manager->getValue());
}
}
Refactoring
In order to implement the pattern we must follow these steps:
1. Create a strategy concrete class
Create a strategy concrete class by naming it as one of your intended strategy, for example ParametersStrategy
class ParametersStrategy
{
}
2. Create the strategy method
Create the strategy method which will perform the calculation and move the original calculation to the Strategy:
class ParametersStrategy
{
+ public function getValue($param1, $param2)
+ {
+ if ($param1 && $param2) {
+ return 'both';
+ }
+
+ if ($param1) {
+ return 'first';
+ }
+
+ return 'none';
+ }
}
class ParamManager
{
private $param1;
private $param2;
public function __construct($param1 = null, $param2 = null)
{
$this->param1 = $param1;
$this->param2 = $param2;
}
public static function withTwoArguments($param1, $param2)
{
return new ParamManager($param1, $param2);
}
public static function withOneArgument($param1)
{
return new ParamManager($param1);
}
public static function withNoArguments()
{
return new ParamManager();
}
public function getValue()
{
+ return (new ParametersStrategy())->getValue($this->param1, $this->param2);
- if ($this->param1 && $this->param2) {
- return 'both';
- }
-
- if ($this->param1) {
- return 'first';
- }
-
- return 'none';
}
}
Note that, since both $param1
and $param2
are fields of ParamManager
, they must be passed. Optionally we could have passed the whole ParamManager
instance and get their value by creating getters or making them public
.
3. Make ParametersStrategy
instance a field and creates it in the creational methods.
This step is crucial for the next step.
class ParamManager
{
private $param1;
private $param2;
+ private $parametersStrategy;
+ public function __construct(ParametersStrategy $parametersStrategy, $param1 = null, $param2 = null)
- public function __construct($param1 = null, $param2 = null)
{
+ $this->parametersStrategy = $parameterStrategy;
$this->param1 = $param1;
$this->param2 = $param2;
}
public static function withTwoArguments($param1, $param2)
{
+ return new ParamManager(new ParametersStrategy(), 'a', 'b');
- return new ParamManager('a', 'b');
}
public static function withOneArgument($param1)
{
+ return new ParamManager(new ParametersStrategy(), 'a');
- return new ParamManager('a');
}
public static function withNoArguments()
{
+ return new ParamManager(new ParametersStrategy());
- return new ParamManager();
}
public function getValue()
{
+ return $this->parametersStrategy->getValue($this->param1, $this->param2);
- return (new ParametersStrategy())->getValue($this->param1, $this->param2);
}
}
4. Replace conditionals with polymorphism
At the current step we have just delegated the computation to another class without removing the conditionals. In this step we make the ParametersStrategy
and its getValue()
method abstract
and create a concrete subclass for each algorithm variation, thus removing the conditionals:
+abstract class ParametersStrategy
-class ParametersStrategy
{
+ public abstract function getValue($param1, $param2);
- public function getValue($param1, $param2)
- {
- if ($param1 && $param2) {
- return 'both';
- }
-
- if ($param1) {
- return 'first';
- }
-
- return 'none';
- }
}
-
class TwoParametersStrategy extends ParametersStrategy
{
public function getValue($param1, $param2)
{
return 'both';
}
}
-
class OneParameterStrategy extends ParametersStrategy
{
public function getValue($param1, $param2)
{
return 'first';
}
}
-
class NoParametersStrategy extends ParametersStrategy
{
public function getValue($param1, $param2)
{
return 'none';
}
}
The last step is to change ParamManager
creational methods to instantiate the correct concrete ParametersStrategy:
class ParamManager
{
private $param1;
private $param2;
private $parametersStrategy;
public function __construct(ParametersStrategy $parametersStrategy, $param1 = null, $param2 = null)
{
$this->parametersStrategy = $parameterStrategy;
$this->param1 = $param1;
$this->param2 = $param2;
}
public static function withTwoArguments($param1, $param2)
{
+ return new ParamManager(new TwoParametersStrategy(), 'a', 'b');
- return new ParamManager(new ParametersStrategy(), 'a', 'b');
}
public static function withOneArgument($param1)
{
+ return new ParamManager(new OneParameterStrategy(), 'a');
- return new ParamManager(new ParametersStrategy(), 'a');
}
public static function withNoArguments()
{
+ return new ParamManager(new NoParametersStrategy());
- return new ParamManager(new ParametersStrategy());
}
public function getValue()
{
return $this->parametersStrategy->getValue($this->param1, $this->param2);
}
}
5. Move parameters to their respective strategies
At this, point (as Roy stated in the comments section), there is no reason to keep the strategy parameters in ParamManager
class, so I decided to move them in the respective strategies:
class ParamManager
{
- private $param1;
- private $param2;
private $parametersStrategy;
+ public function __construct(ParametersStrategy $parametersStrategy)
- public function __construct(ParametersStrategy $parametersStrategy, $param1 = null, $param2 = null)
{
$this->parametersStrategy = $parameterStrategy;
- $this->param1 = $param1;
- $this->param2 = $param2;
}
public static function withTwoArguments($param1, $param2)
{
+ return new ParamManager(new TwoParametersStrategy('a', 'b'));
- return new ParamManager(new TwoParametersStrategy(), 'a', 'b');
}
public static function withOneArgument($param1)
{
+ return new ParamManager(new OneParameterStrategy('a'));
- return new ParamManager(new OneParameterStrategy(), 'a');
}
public static function withNoArguments()
{
return new ParamManager(new NoParametersStrategy());
}
public function getValue()
{
return $this->parametersStrategy->getValue($this->param1, $this->param2);
}
}
+abstract class ParametersStrategy
-class ParametersStrategy
{
+ public abstract function getValue();
- public abstract function getValue($param1, $param2);
-
class TwoParametersStrategy extends ParametersStrategy
{
+ private $param1;
+ private $param2;
+ public function __construct($param1, $param2)
+ {
+ $this->param1 = $param1;
+ $this->param2 = $param2;
+ }
+ public function getValue()
- public function getValue($param1, $param2)
{
return 'both';
}
}
-
class OneParameterStrategy extends ParametersStrategy
{
+ private $param1;
+ public function __construct($param1)
+ {
+ $this->param1 = $param1;
+ }
+ public function getValue()
- public function getValue($param1, $param2)
{
return 'first';
}
}
-
class NoParametersStrategy extends ParametersStrategy
{
+ public function getValue()
- public function getValue($param1, $param2)
{
return 'none';
}
}
Final notes
Now, getValue()
will execute the correct concrete strategy, and conditionals were removed, without changing client code (the tests remain the same and still pass)
Note that, in order to be compliant with abstract method definition, some (or even all) parameters need to be passed to strategy method even if they are not required for calculation.
If you have doubts, or corrections, please leave a comment below.

In the previous article TDD IMPLEMENTATION OF FINITE STATE MACHINE (FSM) WITH LARAVEL I discussed a basic approach for implement a Finite State Machine (FSM) on a Laravel model. In this article I will further discuss the topic by applying a more engineered approach. It involves the usage of the State pattern