Symfony Component Advent Calendar 2022の13日目の記事です。半分きた。
最初に
SymfonyはPHPのフレームワークのひとつです。しかし、公式サイトの説明文には
Symfony is a set of PHP Components, a Web Application framework, a Philosophy, and a Community — all working together in harmony.
(SymfonyはPHPコンポーネントのセットで、Webアプリケーションフレームワークで、哲学、そしてコミュニティです。それらがハーモニーを奏でながら動作しています。)
と書かれている通り、PHPコンポーネントのセットで、たくさんのコンポーネントを提供しており、それらを組み合わせてひとつのフレームワークとして動作しています。Symfonyのコンポーネントは、Symfony上だけで動作するのではなく、他のPHPフレームワークやアプリケーションでも動作している強力なものが揃っています。
今回はそれらの中から、役立ちそうなもの・お薦めしたいものを紹介していきたいと思います。
※記事内ではautoloadのインポートは省略します。
他のアプリケーションにメッセージを送信、"Messenger"
Messengerは、他のアプリケーションと連携するためにメッセージを送受信するためのコンポーネントです。EventDispatcherと似てますが、ちょっと違います。
インストール
composer require symfony/messenger
メッセージとは
ある処理を実行した後、その処理のコンテキスト外の処理を他のアプリケーションやサービスにお願いするときがあります。例えば、メール送信とか集計処理とか。そのような場合に『つづきの処理よろしく!』と、後をまかせる際に使うのがメッセージです。基本、本来の処理へ後処理の処理結果は影響しません。(メッセージを受け取った処理がどうなろうが、本来の処理は正常終了します)。ただし、メッセージの受け取り先が同じSymfonyプロジェクトですぐに処理を実行する場合は影響があります。
Symfonyの場合、メッセージ送信後すぐに処理を行うこともできますし、後でゆっくり実行する(いわゆる、キュー)ことも可能です。
メッセージの送り方
Symfonyでのメッセージの送り方は、MessageBus
, Message
, Envelope
を使います。
1. Message
クラスを作る
Message
クラスを作り、他のアプリケーションに渡すデータをセットします。 bin/soncole make:message
で簡単に生成できます。
use App\Entity\Item;
class SendMailMessage
{
public function __construct(private readonly Item $item)
{
}
public function getItem(): Item
{
return $this->item;
}
}
2. Envelope
(封筒)に入れる
Message
オブジェクトをEnvelope
オブジェクトに入れます。入れる際にStamp
を押すこともできます。(後述)
use App\Entity\Item;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Stamp\DelayStamp;
class ItemController
{
public function publish(): Response
{
// なにか処理
$item = new Item();
...
// Messageオブジェクト作成
$message = new SendMailMessage($item);
// Envelopeに入れる
$envelope = new Envelope($message, [
new DelayStamp(5000)
]);
}
}
3. MessageBus
で送る
MessageBus
オブジェクトを使って、メッセージを送信します。dispatch()
メソッドにEnvelope
オブジェクトを渡しますが、Stamp
が特にない場合は、直接Message
を渡すことも可能です。
use App\Entity\Item;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Messenger\Stamp\DelayStamp;
class ItemController
{
public function publish(MessageBusInterface $bus): Response
{
// なにか処理
$item = new Item();
...
// Messageオブジェクト作成
$message = new SendMailMessage($item);
// Envelopeに入れる
$envelope = new Envelope($message, [
new DelayStamp(5000)
]);
// 送信する
$bus->dispatch($envelope);
// 特にStampがなければ、そのままMessageを渡すことで、dispatch()ないでEnvelopeに入れてくれます
$bus->dispatch($message);
}
}
備考:Stamp
Stamp
は、メッセージをどのように処理するかであったり、現在のメッセージの状況を設定できます。
例えば、上記の例で挙げたDelayStamp
はメッセージを送るタイミングをずらすことができます。
また、状況を表すReceivedStamp
, SentStamp
というものも存在します。
StampInterfece
を実装することで、独自のスタンプも作れます。押されたStamp
はEnvelop
のメソッドを利用することで取得できます。
$message = new SendMailMessage($item);
$envelope = new Envelope($message);
$envelope->last(); // 最後に押されたスタンプを取得
$envelope->all(); // 押されているスタンプを全て取得
メッセージを受け取るMessageHandler
Symfonyでメッセージを受け取るって処理をする場合、MessageHandler
クラスを作ってその中に処理を記述します。bin/console make:message
でメッセージを作ると同時にこのハンドラーも自動生成されます。ここもオートワイヤリング可能なので、必要なものをDIできます。
use App\Message\SendMailMessage;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
final class SendMailMessageHandler implements MessageHandlerInterface
{
public function __construct(private readonly MailerInterface $mailer)
{
}
public function __invoke(SendMailMessage $message)
{
// メッセージを受け取って行う処理
$item = $message->getItem();
...
}
}
非同期(あとで処理)にする
デフォルトはメッセージをすぐに送信しますが、非同期に設定することもできます。async
オプションを指定し、どのサービスプロバイダを使ってキューを貯めるか指定します。
framework:
messenger:
transports:
async: "%env(MESSENGER_TRANSPORT_DSN)%"
# AMQP
MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages
# DB
MESSENGER_TRANSPORT_DSN=doctrine://default
# Redis
MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages
まとめ
今回はMessenger
を紹介しました。実際に使う場合は非同期で本処理が終了した後で行うような処理を実行することが多いと思います。
Messenger
やEventDispatcher
をうまく活用して、きれいにコンテキストを分けるときに便利です。