初めに
「ある処理が実行された時に、別の処理を割り込みで実行させたい」という時ありますよね?「APIのレスポンスが作成されたら、それをログに書き出す」のような。
このようなイベント管理の処理を実現するために、Flowには"Signal & Slot"と呼ばれる機能が備わっています。
イベント管理とは
イベント管理は、「ある処理をトリガーとして別の処理が実行される」という概念です。GUIアプリケーションにおいて、ユーザの操作に応じて対応する処理が実行される仕組みを指します。バックエンドよりはフロントエンドの開発においてより頻繁に利用される印象です。
Flowにおけるイベント管理
Flowでは、Signal & Slotという機能でイベント管理を行います。
特定のイベント(Signal)が発生したときに特定の処理(Slot)を実行するというものです。
※AOPと似てますよね。違いについては後述。
Signal & Slotは、C++のQt(キュート)というフレームワークで利用されていた概念です。Flowではその概念を元にイベント管理を実現しています。
試してみる
それでは、実際にイベント管理を実装してみましょう。
「APIのレスポンスが作成されたら、それをログに書き出す」という処理を実装してみます。
以下の3ステップで実装します。
- Signal(レスポンスを書き出す処理)を定義
- Slot(ログに書き出す処理)を定義
- SignalとSlotを結びつける
プロジェクト構成は以下です。
Packages/
├ Application/
| └ Neos.Welcome/
| └ Classes/
| ├ Controller/
| | └ EventController.php
| |
| ├ Service/
| | ├ SignalService.php
| | └ SlotService.php
| |
| └ Package.php
|
├ Framework/
└ Libraries/
1. Signalを定義
Signalを定義します。
以下のルールを満たすようにしてください。
- ポイント1:メソッド名を
emit~~
にする - ポイント2:
@Flow\Signal
を付与する
<?php
namespace Neos\Welcome\Service;
use Neos\Flow\Annotations as Flow;
class SignalService
{
/**
* @Flow\Signal
*/
public function emitCreateResponse($responseValue): array
{
return ['response' => $responseValue];
}
}
2. Slotを定義
続いてSlotを定義します。
Slotには特にルールはありません。普通にメソッドを書くように定義します。
<?php
namespace Neos\Welcome\Service;
use Neos\Flow\Annotations as Flow;
use Psr\Log\LoggerInterface;
class SlotService
{
/**
* @Flow\Inject(name="Neos.Flow:SystemLogger")
* @var LoggerInterface
*/
protected $logger;
public function outputResponse($responseValue): void
{
$this->logger->debug("Outputting response:" . $responseValue);
}
}
3. SignalとSlotを結びつける
SignalとSlotを結びつけます。
各パッケージのClasses配下にPackage.php
を作成し、bootメソッド内で以下のように書くことでSignalとSlotを紐づけることができます。
<?php
namespace Neos\Welcome;
use Neos\Flow\Package\Package as BasePackage;
use Neos\Flow\Core as Core;
class Package extends BasePackage
{
public function boot(Core\Bootstrap $bootstrap)
{
$dispatcher = $bootstrap->getSignalSlotDispatcher();
$dispatcher->connect(
\Neos\Welcome\Service\SignalService::class, 'createResponse',
\Neos\Welcome\Service\SlotService::class, 'outputResponse'
);
}
}
4. 動作確認
動作確認してみましょう。
適当なControllerのアクションメソッドからSignalで定義したメソッドを呼び出してみます。
<?php
namespace Neos\Welcome\Controller;
use Neos\Flow\Annotations as Flow;
/**
* Event controller for the Neos.Welcome package
*/
class EventController extends \Neos\Flow\Mvc\Controller\ActionController
{
/**
* @Flow\Inject
* @var \Neos\Flow\Mvc\View\JsonView
*/
protected $view;
/**
* @Flow\Inject
* @var \Neos\Welcome\Service\SignalService
*/
protected $signalService;
/**
* @return void
*/
public function indexAction()
{
$response = $this->signalService->emitCreateResponse("testResponse");
$this->view->assign('value', $response);
}
}
実行してみます。
レスポンスは問題なく返ってきました。
$ curl http://localhost:8081/Neos.Welcome/Event/index | jq
{
"response": "testResponse"
}
ログにもSlotで定義した形式で出力されていることが確認できました。
24-03-10 05:32:52 1220 DEBUG Outputting response:testResponse
AOPと何が違うの?
ここまで読んでみて、AOPと何が違うの?と思われた方もいると思います。私もその一人でした。
Flowの公式ドキュメントにも以下のような記載があり、"Signal & Slot"はAOPの機能を利用して実現されていることが分かります。
The Signal annotation is picked up by the AOP framework and the method is filled with implementation code as needed.
であれば、ますますAOPでいいのでは?と思ってしまいますよね。
Signal & Slotの強み
そんな"Signal & Slot"ですが、明確な強み一つが存在します。
それは、FWが最初からSignalを用意しているという点です。
以下は、Flowがあらかじめ用意しているSignalの一覧になります。
一部のSignalを抜粋してみました。
Signal | 説明 |
---|---|
compiledClasses | コンパイルが完了した時のSignal |
configurationManagerReady | 設定ファイルの読み込みが完了した時のSignal |
beforeControllerInvocation | 各Controllerへの割り振り前のSignal |
また、これらはすべてFW内の処理内容がトリガーになっているというのもポイントです。
このようなトリガーをAOP単体で書くのは難しいと思います。
こういったところで、AOPと"Signal & Slot"の差別化ができるのかなと感じました。
終わりに
今回はFlowにおける"Signal & Slot"を利用したイベント管理方法を紹介しました。使いこなすにはFlowのフレームワーク内部の処理をよく知る必要がありますね。
これからもいろいろ調べて知識を深めようと思います。
ここまで読んでいただきありがとうございました!