BehavioralPHPverifiedVerified
Strategy Pattern in PHP
Defines a family of algorithms, encapsulates each one, and makes them interchangeable so the algorithm can vary independently from the clients that use it.
How to Implement the Strategy Pattern in PHP
1Step 1: Define the strategy interface
interface SortStrategy
{
/** @param int[] $data */
public function sort(array &$data): void;
}2Step 2: Implement concrete strategies
class BubbleSort implements SortStrategy
{
public function sort(array &$data): void
{
$n = count($data);
for ($i = 0; $i < $n - 1; $i++) {
for ($j = 0; $j < $n - $i - 1; $j++) {
if ($data[$j] > $data[$j + 1]) {
[$data[$j], $data[$j + 1]] = [$data[$j + 1], $data[$j]];
}
}
}
}
}
class QuickSort implements SortStrategy
{
public function sort(array &$data): void
{
sort($data); // Using PHP's built-in sort for simplicity
}
}3Step 3: Implement the context that uses a strategy
class Sorter
{
public function __construct(private SortStrategy $strategy) {}
public function setStrategy(SortStrategy $strategy): void
{
$this->strategy = $strategy;
}
/** @param int[] $data */
public function sort(array &$data): void
{
$this->strategy->sort($data);
}
}
// Usage
$sorter = new Sorter(new BubbleSort());
$data = [5, 3, 8, 1, 2];
$sorter->sort($data);
print_r($data); // [1, 2, 3, 5, 8]
$sorter->setStrategy(new QuickSort());
$data2 = [9, 4, 7, 6];
$sorter->sort($data2);<?php
declare(strict_types=1);
// [step] Define compression strategy interfaces
interface CompressionStrategyInterface
{
public function compress(string $data): string;
public function decompress(string $data): string;
public function getName(): string;
public function getExtension(): string;
}
final readonly class CompressionResult
{
public function __construct(
public string $data,
public string $algorithm,
public int $originalSize,
public int $compressedSize,
public float $ratio,
public float $durationMs,
) {}
}
interface LoggerInterface
{
public function info(string $message, array $context = []): void;
public function error(string $message, array $context = []): void;
}
// [step] Implement concrete compression strategies
final class GzipStrategy implements CompressionStrategyInterface
{
public function __construct(private readonly int $level = 6) {}
public function compress(string $data): string
{
$result = gzencode($data, $this->level);
if ($result === false) {
throw new \RuntimeException('Gzip compression failed');
}
return $result;
}
public function decompress(string $data): string
{
$result = gzdecode($data);
if ($result === false) {
throw new \RuntimeException('Gzip decompression failed');
}
return $result;
}
public function getName(): string { return 'gzip'; }
public function getExtension(): string { return '.gz'; }
}
final class DeflateStrategy implements CompressionStrategyInterface
{
public function compress(string $data): string
{
$result = gzdeflate($data);
if ($result === false) {
throw new \RuntimeException('Deflate compression failed');
}
return $result;
}
public function decompress(string $data): string
{
$result = gzinflate($data);
if ($result === false) {
throw new \RuntimeException('Deflate decompression failed');
}
return $result;
}
public function getName(): string { return 'deflate'; }
public function getExtension(): string { return '.zz'; }
}
final class NoCompressionStrategy implements CompressionStrategyInterface
{
public function compress(string $data): string { return $data; }
public function decompress(string $data): string { return $data; }
public function getName(): string { return 'none'; }
public function getExtension(): string { return ''; }
}
// [step] Implement the context with strategy selection and metrics
final class FileCompressor
{
/** @var array<string, CompressionStrategyInterface> */
private array $strategies = [];
private CompressionStrategyInterface $currentStrategy;
public function __construct(
private readonly LoggerInterface $logger,
CompressionStrategyInterface $defaultStrategy,
) {
$this->currentStrategy = $defaultStrategy;
$this->registerStrategy($defaultStrategy);
}
public function registerStrategy(CompressionStrategyInterface $strategy): self
{
$this->strategies[$strategy->getName()] = $strategy;
return $this;
}
public function setStrategy(string $name): self
{
if (!isset($this->strategies[$name])) {
throw new \InvalidArgumentException("Unknown compression strategy: {$name}");
}
$this->currentStrategy = $this->strategies[$name];
$this->logger->info("Strategy changed to: {$name}");
return $this;
}
public function compress(string $data): CompressionResult
{
$start = hrtime(true);
$originalSize = strlen($data);
try {
$compressed = $this->currentStrategy->compress($data);
$compressedSize = strlen($compressed);
$durationMs = (hrtime(true) - $start) / 1e6;
$result = new CompressionResult(
data: $compressed,
algorithm: $this->currentStrategy->getName(),
originalSize: $originalSize,
compressedSize: $compressedSize,
ratio: $originalSize > 0 ? $compressedSize / $originalSize : 0,
durationMs: $durationMs,
);
$this->logger->info('Compression complete', [
'algorithm' => $result->algorithm,
'ratio' => round($result->ratio, 4),
'durationMs' => round($result->durationMs, 2),
]);
return $result;
} catch (\Throwable $e) {
$this->logger->error('Compression failed', [
'algorithm' => $this->currentStrategy->getName(),
'error' => $e->getMessage(),
]);
throw $e;
}
}
/** @return string[] */
public function getAvailableStrategies(): array
{
return array_keys($this->strategies);
}
}Strategy Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Strategy Pattern in the Real World
“Consider a GPS app offering route options: fastest, shortest, or avoid tolls. The destination is the same, but the navigation algorithm changes based on your preference. The app (context) simply hands the journey off to whichever routing strategy you selected; you can switch strategies mid-trip without the app needing to change its structure.”