The design pattern strategy is one of my favorites, and in this article, I will show you how to implement it in real-life examples using PHP 8. You will learn more about the patterns, their pros, and cons.
About this series of articles
This article’s main goal is to teach you about this subject that I am passionate about. After writing two books about design patterns (In Brazil), I started this series of articles to share with them and help you become a more professional developer. I hope you enjoy it.
Keep it in mind: The most difficult part about learning design patterns is “when I should apply them.” My advice is to focus on the examples and try to think in different situations that you have a similar problem; it doesn’t matter the industry or type of project you work with. Design patterns are not related to a specific business. They are intended to solve programming problems related to algorithms’ behavior, the structure of your code, or even creating the objects on your application.
GitHub Repository: https://github.com/gabrielanhaia/php-design-patterns
Name: Strategy (also known as Policy)
Type: Behavioral (It changes your code at a behavior level).
Origin: from the book “Design Patterns: Elements of Reusable Object-Oriented Software.”
The Problem
I want to introduce you to an example of a widespread problem that We developers use to face. Let’s imagine that you work in an e-commerce platform focused on the food industry.
In this hypothetical situation, each product sold online has a tax of 30%. Let’s assume that you sell the types of the product above:
+-------------+-----+
| Category | Tax |
+-------------+-----+
| Foods | 30% |
| Electronics | 30% |
| Books | 30% |
+-------------+-----+
However, there was a change, and your country’s government started collecting taxes depending on what kind of product it is. Now the taxes look like this:
+-------------+-----+
| Category | Tax |
+-------------+-----+
| Foods | 20% |
| Electronics | 40% |
| Books | 0% |
+-------------+-----+
If you think in terms of algorithm, your solution could look like something like this:
switch ($product->getCategory()) { case 'foods': // All the logic related to the calculation with 20% rate. break; case 'electronics': // All the logic related to the calculation with 40% rate. break; case 'foods': // All the logic related to the calculation with 0% rate. break; }
It looks like a good solution, but if you look closer and start thinking about the future, it can be a big headache. We are not making use of object orientation, our software is not modular, and if we need to use the logic of tax calculation in another place, we probably will have to duplicate code. This is a simple example, and in real life, things are much more complex. Those Tax calculations would have much more complex logic and too many lines each one. If we put all different logics in the same class (It doesn’t matter if we are breaking them into methods), it will not be a reusable solution, it will be tough to test, and probably when it grows, and we have more categories (As you can imagine, it is a very probable situation) we will have problems changing the code.
We should see behind this problem that we have a group of algorithms with the same purpose (tax calculator); however, each has its own behavior and implementation.
The Strategy Pattern
The Strategy’s main goal is to define a family of algorithms that perform a similar operation, each with a different implementation. It splits each of the Algorithms (called strategies) in its own class, so we can use them through our whole application, test them easier, and even change the algorithms at run time.
The Strategy pattern is composed of two main elements:
- A Strategy Interface: This interface guarantees that all the strategies implemented will follow the same rules (with the same contract), and they will be interchangeable on our code.
- The concrete strategy classes: Those are the concrete implementations of our Strategies. We have one class with one/x method(s) defined in our interface. We can have as many strategies as necessary.
- Context class: This is the class that knows only the Strategy interface, it owns an instance of a concrete Strategy, and it is responsible for running the Algorithm.

Solving the Ecommerce problem with Strategy
After knowing the Strategy pattern and the problem it solves, we can assume that it fits very well in solving our e-commerce problem. We have different algorithms (each one responsible for calculating a product’s fees differently). Furthermore, we don’t know the exact number of categories we can have in the future and grow.
I will start creating an object that represents our products, and it’s just a DTO with getters and setters:
<?php class Product { private string $name; private string $category; private float $price; private float $taxes; public function getCategory(): string { return $this->category; } public function setCategory(string $category): Product { $this->category = $category; return $this; } public function getName(): string { return $this->name; } public function setName(string $name): Product { $this->name = $name; return $this; } public function getPrice(): float { return $this->price; } public function setPrice(float $price): Product { $this->price = $price; return $this; } public function getTaxes(): float { return $this->taxes; } public function setTaxes(float $taxes): Product { $this->taxes = $taxes; return $this; } }
The idea is to look into the category and use it to calculate and return its tax.
Let’s design the first element of our pattern, the Strategy interface:
interface TaxCalculatorStrategy { public function calculate(Product $product): float; }
If you look at the interface above, you will see that it defines a method responsible for calculating the Tax of a product passed as a param to this method.
Now we need to implement our concrete strategies, and the first one is for the foods category:
class FoodTaxStrategy implements TaxCalculatorStrategy { const TAX_RATE = 30.0; public function calculate(Product $product): float { return $product->getPrice() * (self::TAX_RATE / 100); } }
Now we will implement the electronics category:
class ElectronicTaxStrategy implements TaxCalculatorStrategy { const TAX_RATE = 40.0; public function calculate(Product $product): float { return $product->getPrice() * (self::TAX_RATE / 100); } }
and last but not least, the strategy used by the books category:
class TaxFreeStrategy implements TaxCalculatorStrategy { public function calculate(Product $product): float { return 0; } }
Note: I create this TaxFreeStrategy to be more generic and not attached to a specific category, so maybe I can use it in different categories that require it in the future.
Now let’s implement the last part of our pattern, the Context class (this class can be named what makes more sense into your project):
class Context { private TaxCalculatorStrategy $taxCalculatorStrategy; public function __construct(TaxCalculatorStrategy $taxCalculatorStrategy) { $this->taxCalculatorStrategy = $taxCalculatorStrategy; } public function calculateProduct(Product $product): void { $taxes = $this->taxCalculatorStrategy->calculate($product); $product->setTaxes($taxes); } }
If you take a closer look at this class, you will see that it encapsulates a strategy, it receives the interface, making it possible to use any Concrete Strategy. We need to define which class will be used and call the calculation method.
Outside of all those classes above is your project that can be a simple PHP script, a Laravel framework, Symfony (Controller, Model, Repository, Service layer), it doesn’t matter what it is, I will show the simple example above using those classes:
$product = new Product; $product->setName('Product Test') ->setCategory('electronic') ->setPrice(100); switch ($product->getCategory()) { case 'electronics': $strategy = new ElectronicTaxStrategy; break; case 'food': $strategy = new FoodTaxStrategy; break; case 'books': $strategy = new TaxFreeStrategy; break; default: throw new \Exception('Strategy not found for this category.'); } $context = new Context($strategy); $context->calculateProduct($product); echo $product->getTaxes();
If you are asking yourself why we still have this conditional block, and the answer is that we will not get rid of it by just applying the Strategy pattern, however, we have different approaches to do so. The most important is that we could split the algorithms responsible for the calculation into smaller blocks of code, and they are standardized and easy to change, and in use them depending on different conditions. We took the code’s spiral inside our unlimited number of if conditions and turned it into a much more modular code using object orientation.
I hope you liked this articles; let me know if you have any questions about it, and don’t forget to check the GitHub repository here https://github.com/gabrielanhaia/php-design-patterns
Don’t forget to add me on LinkedIn and follow me on GitHub for more updates!
Source: https://medium.com/mestredev/strategy-in-php-8-design-patterns-2044e5ef54ed