14/04/2018 - SYMFONY
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.
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.
Ö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.
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;
}
}
}
}
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;
}
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);
}
}
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);
}
}
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);
}
}
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)]);
}
}
}
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());
}
}
services:
App\Strategy\EmailStrategy:
tags:
- { name: message_strategy }
App\Strategy\SmsStrategy:
tags:
- { name: message_strategy }
App\Strategy\VoiceStrategy:
tags:
- { name: message_strategy }
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');
}
}
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"
}
$ 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