Design Patterns in PHP: Advanced Techniques with Code Examples
Design patterns are proven solutions to typical problems that arise during software development. If you have already mastered basic patterns (Singleton, Factory, Observer), it's time to move to the next level. In this article, we will cover four advanced behavioral patterns in PHP: Strategy, Command, State, and Chain of Responsibility. You will learn how they help write flexible, maintainable, and scalable code.
1. Strategy Pattern
The Strategy pattern allows you to define a family of algorithms, encapsulate each one, and make them interchangeable. This is especially useful when you have multiple ways to perform the same task and want to easily switch between them without modifying the main class.
When to use:
- You have several algorithm variants that may change at runtime.
- You want to avoid bulky conditional constructs (if-else or switch).
- Algorithms can be reused in different parts of the application.
Example: shipping cost calculation system
Imagine you have an online store, and the shipping cost depends on the selected carrier: Russian Post, DHL, or a courier service.
<?php
// Strategy interfaceinterface ShippingStrategy { public function calculate(float $weight, float $distance): float;}
// Concrete strategiesclass RussianPostShipping implements ShippingStrategy { public function calculate(float $weight, float $distance): float { return ($weight * 0.5) + ($distance * 0.1); }}
class DHLShipping implements ShippingStrategy { public function calculate(float $weight, float $distance): float { return ($weight * 1.2) + ($distance * 0.3) + 10; }}
class CourierShipping implements ShippingStrategy { public function calculate(float $weight, float $distance): float { return ($weight * 0.8) + ($distance * 0.2) + 5; }}
// Contextclass Order { private float $weight; private float $distance; private ShippingStrategy $strategy;
public function __construct(float $weight, float $distance, ShippingStrategy $strategy) { $this->weight = $weight; $this->distance = $distance; $this->strategy = $strategy; }
public function setStrategy(ShippingStrategy $strategy): void { $this->strategy = $strategy; }
public function getShippingCost(): float { return $this->strategy->calculate($this->weight, $this->distance); }}
// Usage$order = new Order(2.5, 100, new RussianPostShipping());echo "Shipping via Russian Post: " . $order->getShippingCost() . " rub.";
$order->setStrategy(new DHLShipping());echo "Shipping via DHL: " . $order->getShippingCost() . " rub.";?>In this example, we easily change the cost calculation algorithm without touching the Order class. Adding a new carrier simply involves creating a new class that implements the ShippingStrategy interface.
2. Command Pattern
The Command pattern turns requests into objects, allowing you to parameterize clients with different requests, queue requests, log them, or support undo operations.
When to use:
- You need to implement undo and redo operations.
- You need to queue operations or execute them asynchronously.
- You want to decouple the sender of a request from its receiver.
Example: text editor with undo functionality
<?php
// Command interfaceinterface Command { public function execute(): void; public function undo(): void;}
// Receiverclass