Bu örneğimizde Symfony Compiler Passes kullanarak Strategy Pattern kullanacağız. Bu işlem verilen girdiye göre, otomatik olarak alakalı servisi çalıştırarak kod tekrarlama işlemini engelleyecek.


Bilgi


Kullanıcı bir adrese istek göndererek mesaj atmak istiyor. İsteğin içinde mesaj mevcut. Mesaj tipi sadece email, sms veya voice olabilir. Eğer bu durumu geleneksel olarak kodlamak isteseydik, kodumuz aşağıdaki gibi olurdu.


if ($type === 'email') {
$emailSender->send($message);
} elseif ($type === 'sms') {
$smsSender->send($message);
} elseif ($type === 'voice') {
$voiceSender->send($message);
}

Gördüğümüz gibi bu biraz eski stil gibi duruyor. Ayrıca farklı bir mesaj tipi daha eklemek isteseydik, kodumuzu yenilemek zorunda kalacaktık. Aşağıdaki örneğimizde tüm işlemi tek bir satır kod $sender->send($type, $message); ile yapacağız. Bu durumda ise, farklı bir mesaj tipi daha eklemek isteseydik, yeni bir tane strategy class eklememiz yetecektir.


Dizayn


Örneğimizde mesajları gönderiyormuş gibi yapıp, aslında ilgili log dosyalarına yazacağız. Bunun tek nedeni ise kodu kısa kesmek istediğimdendir. Strategy classlar geriye her hangi bir sonuç döndürmüyorlar ama eğer siz isterseniz, duruma göre uygun bir şey döndürebilirsiniz.



Dosyalar


Strategy/Message.php


declare(strict_types=1);

namespace App\Strategy;

class Message
{
private $strategies;

public function addStrategy(StrategyInterface $strategy): void
{
$this->strategies[] = $strategy;
}

public function send(string $type, iterable $payload = []): void
{
/** @var StrategyInterface $strategy */
foreach ($this->strategies as $strategy) {
if ($strategy->isSendable($type, $payload)) {
$strategy->send($payload);

return;
}
}
}
}

Strategy/StrategyInterface.php


declare(strict_types=1);

namespace App\Strategy;

interface StrategyInterface
{
public const SERVICE_TAG = 'message_strategy';
public const MESSAGE_KEY = 'message';

public function isSendable(string $type, iterable $payload = []): bool;
public function send(iterable $payload = []): void;
}

Strategy/EmailStrategy.php


declare(strict_types=1);

namespace App\Strategy;

class EmailStrategy implements StrategyInterface
{
private $key = 'email';

public function isSendable(string $type, iterable $payload = []): bool
{
return $type === $this->key && isset($payload[self::MESSAGE_KEY]);
}

public function send(iterable $payload = []): void
{
file_put_contents($this->key.'.log', $payload[self::MESSAGE_KEY].PHP_EOL, FILE_APPEND);
}
}

Strategy/SmsStrategy.php


declare(strict_types=1);

namespace App\Strategy;

class SmsStrategy implements StrategyInterface
{
private $key = 'sms';

public function isSendable(string $type, iterable $payload = []): bool
{
return $type === $this->key && isset($payload[self::MESSAGE_KEY]);
}

public function send(iterable $payload = []): void
{
file_put_contents($this->key.'.log', $payload[self::MESSAGE_KEY].PHP_EOL, FILE_APPEND);
}
}

Strategy/VoiceStrategy.php


declare(strict_types=1);

namespace App\Strategy;

class VoiceStrategy implements StrategyInterface
{
private $key = 'voice';

public function isSendable(string $type, iterable $payload = []): bool
{
return $type === $this->key && isset($payload[self::MESSAGE_KEY]);
}

public function send(iterable $payload = []): void
{
file_put_contents($this->key.'.log', $payload[self::MESSAGE_KEY].PHP_EOL, FILE_APPEND);
}
}

Strategy/MessageCompilerPass.php


declare(strict_types=1);

namespace App\Strategy;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

class MessageCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$resolverService = $container->findDefinition(Message::class);

$strategyServices = array_keys($container->findTaggedServiceIds(StrategyInterface::SERVICE_TAG));

foreach ($strategyServices as $strategyService) {
$resolverService->addMethodCall('addStrategy', [new Reference($strategyService)]);
}
}
}

Kernel.php


namespace App;

use App\Strategy\MessageCompilerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
...

class Kernel extends BaseKernel
{
use MicroKernelTrait;

...

protected function build(ContainerBuilder $container): void
{
$container->addCompilerPass(new MessageCompilerPass());
}
}

config/services.yaml


services:

App\Strategy\EmailStrategy:
tags:
- { name: message_strategy }

App\Strategy\SmsStrategy:
tags:
- { name: message_strategy }

App\Strategy\VoiceStrategy:
tags:
- { name: message_strategy }

Controller/MessageController.php


declare(strict_types=1);

namespace App\Controller;

use App\Strategy\Message;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

/**
* @Route("/messages")
*/
class MessageController
{
private $message;

public function __construct(Message $message)
{
$this->message = $message;
}

/**
* @Route("/{type}")
* @Method({"POST"})
*/
public function index(Request $request, string $type)
{
$payload = json_decode($request->getContent(), true);

$this->message->send($type, $payload);

return new Response('OK');
}
}

Test


POST /messages/email
{
"message": "This is the message 1"
}

POST /messages/sms
{
"message": "This is the message 2"
}

POST /messages/voice
{
"message": "This is the message 3"
}

Sonuç


$ cat public/email.log 
This is the message 1

$ cat public/sms.log
This is the message 2

$ cat public/voice.log
This is the message 3