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.