A few years ago, I wrote an article titled Strategy pattern in Symfony. It was good for its time and is still relevant, but now it's a bit old-fashioned.
Using attributes
Now I'd rather define services using attributes instead of services.yml
:
#[AutoconfigureTag(self::TAG)] interface ImageResizeStrategyInterface { public const TAG = 'image_resize_strategy'; public function supports(string $imageType): bool; public function resize(string $filename, int $newWidth, int $newHeight); }
Using support method
Another change, I prefer supports
method to check if the strategy is applicable to the case.
Every time, every case. This way I don't have to spend any time thinking of naming :)
class ImageJpgResizeStrategy implements ImageResizeStrategyInterface { #[\Override] public function supports(string $imageType) { return 'JPG' === $imageType; } #[\Override] public function resize(string $filename, int $newWidth, int $newHeight) { ... } }
Using support
method our resizer looks quite different now
class ImageResizer { public function __construct( /** @var ImageResizeStrategyInterface[] */ #[TaggedIterator(ImageResizeStrategyInterface::TAG)] private readonly iterable $strategies, ) { } public function resize(string $filename, string $extension, int $newWidth, int $newHeight) { foreach($this->strategies as $strategy) { if($strategy->supports($extension)) { $strategy->resize($filename, $newWidth, $newHeight); return; } } throw new \Exception('Unhandled extension'); }
That's it
Top comments (1)
Thank you for sharing this. I like that you use the tag as a Constant. So you can use it later in the TaggedIterator Attribute.