外部サービスからのイベントを受け取るよ（後編）、"RemoteEvent"

[Symfony Component Advent Calendar 2023](https://qiita.com/advent-calendar/2023/symfony-component)の16日目の記事です。

## 外部サービスからのイベントを受け取るよ、"RemoteEvent"

[RemoteEvent](https://github.com/symfony/remote-event)は、外部サービスからのイベントを『リモートからのイベント』とみなし、イベントを発火させるコンポーネントです。

### インストール

```
composer require symfony/remote-event
```

### 使い方

Symfonyの偉い人は考えました。『Webhookって要はリモートからのイベントじゃん。』
webhookでSymfonyにアクセスされた時、それを`RemoteEvent`に置き換えてイベント発火させ、処理を行います。処理の流れはこんな感じです。

1. 外部サービスのwebhookを解析する`RequestParser`を作る
1. 解析したリクエストを`RemoteEvent`にする
1. イベントを受け取って実行する`Consumer`を作る
1. webhookとして設定する


このうち、3,4は前編の[Webhook](https://qiita.com/ippey_s/private/2bac555da5edad6f0748)として説明しましたが、再度記載します。


#### 1. RequestParserを作る & 2. RemoteEventを作る

```php: SampleWebhookParser.php

namespace App\Webhook;

use Symfony\Component\HttpFoundation\ChainRequestMatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestMatcher\HostRequestMatcher;
use Symfony\Component\HttpFoundation\RequestMatcher\IsJsonRequestMatcher;
use Symfony\Component\HttpFoundation\RequestMatcher\MethodRequestMatcher;
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
use Symfony\Component\RemoteEvent\RemoteEvent;
use Symfony\Component\Webhook\Client\AbstractRequestParser;

class SampleWebhookParser extends AbstractRequestParser
{

    protected function getRequestMatcher(): RequestMatcherInterface
    {
        return new ChainRequestMatcher([
            new HostRequestMatcher('github.com'),
            new IsJsonRequestMatcher(),
            new MethodRequestMatcher('POST')
        ]);
    }

    protected function doParse(Request $request, #[\SensitiveParameter] string $secret): ?RemoteEvent
    {
        $content = $request->toArray();
        return new RemoteEvent('sample', 1, [
            'name' => $content['repository']['full_name'],
            'action' => $content['action'],
            'secret' => $secret,
        ]); // RemoteEvent('{イベント名}', '{イベントID}', {ペイロード})
    }
}
```

`getRequestMatcher()`でどのURLからアクセスされたものをRemoteEventにするか設定します。
上記の場合、

- github.comからのアクセス
- リクエストボディがJSON
- メソッドがPOST

の場合に、このクラスの`doParse()`が実行されます。
`doParse()`では、Requestオブジェクトを使って、リクエスト内容を取得し、その内容を使って`RemoteEvent`オブジェクトを作って返します。

#### 3. Consumerを作る

```php:SampleEventConsumer.php

namespace App\RemoteEvent;

use Symfony\Component\RemoteEvent\Attribute\AsRemoteEventConsumer;
use Symfony\Component\RemoteEvent\Consumer\ConsumerInterface;
use Symfony\Component\RemoteEvent\Event\Mailer\AbstractMailerEvent;
use Symfony\Component\RemoteEvent\Event\Mailer\MailerDeliveryEvent;
use Symfony\Component\RemoteEvent\RemoteEvent;
use Symfony\Contracts\EventDispatcher\Event;

#[AsRemoteEventConsumer(name: 'sample')]
class SampleEventConsumer implements ConsumerInterface
{
    public function __construct(private readonly LoggerInterface $logger)
    {
    }
    
    public function consume(RemoteEvent $event): void
    {
        $payload = $event->getPayload();
        $name = $payload['name'];
        $action = $payload['action'];
        $this->logger->info("{$name}: {$action}");
    }
}
```

`#[AsRemoteEventConsumer]`に2で作成したRemoteEventのイベント名を設定します。そうすることで、RemoteEventが発火した際にこのConsumerの`consume()`が実行されます。
`consume()`内は、やりたいことを好きに書いてください。気をつけないといけないのはこのメソッドはvoidなので、何も返しません。

#### 4. webhookの設定

```yaml:config/packages/webhook.yaml
framework:
    webhook:
        routing:
            sample:
                service: 'App\Webhook\SampleWebhookParser'
```

webhookの設定を行います。この設定により、`/webhook/sample`で、github.comからPOSTでJSONを送られてきた時用のwebhookが作成されます。

## まとめ

今回は`RemoteEvent`でした。`Webhook`で定義されていない外部サービスでも、このようにすればwebhookを作成することができます！