🙌

Symfonyでwebhookを受け取る

2024/09/08に公開

概要

Symfonyでwebhookを受け取る方法についてメモしておきます。
API通信はApiPlatformを利用していますが、webhookではApiPlatformを利用するのではなく、Symfonyのコンポーネントを利用する手段を選択しました。

使用している技術

  • Symfony 6.3
  • PHP 8.2
  • ApiPlatform 3.2

経緯

ApiPlatformでPOSTリクエストを受け取れるようにするには、そのスキーマを定義する必要があります。
エンドポイントを提供する観点では、ApiPlatformによるスキーマ定義は堅牢なAPIを提供するためには重要です。
しかし、webhookを受け取る場合、webhookを提供するサービスのAPI定義通りにApiPlatformのスキーマを定義する必要があります。
単純にこれが辛いので、webhookを受け取る場合はApiPlatformを利用せず、Symfonyのコンポーネントを利用することにしました。

方法

  1. まず必要なコンポーネントをインストールします。symfonyのwebhookとremote-eventを利用します。
composer require symfony/webhook symfony/remote-event
  1. webhook.yamlを以下のように設定します。prefixはwebhookのエンドポイントのprefixを指定します。
    以下の例では、/webhookというエンドポイントにwebhookを受け取るように設定しています。
webhook:
    resource: '@FrameworkBundle/Resources/config/routing/webhook.xml'
    prefix: /webhook
  1. framework.yamlに以下の設定を追加します。
    以下例では、lineのwebhookを受け取ることを想定して設定しています。
    この設定を行うことで、/webhook/lineにリクエストが来た場合、App\Webhook\LineParserクラスのdoParseメソッドが呼び出されます。
framework:
    webhook:
        routing:
            line:
                service: App\Webhook\LineParser
  1. Parserクラスを作成します。これはwebhookのリクエストを受け取り、処理するクラスです。
    doParseメソッドはRemoteEventを返却する必要があります。
<?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 LineParser extends AbstractRequestParser
{
    protected function getRequestMatcher(): RequestMatcherInterface
    {
        return new ChainRequestMatcher([
           new HostRequestMatcher('hogehoge.com'), // リクエストのホスト名を指定することができます
            new IsJsonRequestMatcher(),
            new MethodRequestMatcher('POST'), // POSTリクエストのみを受け取る
        ]);
    }

    protected function doParse(Request $request, string $secret): ?RemoteEvent
    {
        $content = $request->toArray();
        return new RemoteEvent( // Remoteイベントを返却する
            name: 'line',
            id: 1,
            payload: $content
        );
    }
}
  1. Consumerクラスを作成します。これは4のクラスで返却されるRemoteEventを受け取り、処理するクラスです。
<?php

namespace App\RemoteEvent;

use App\Event\LineMessageEvent;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\RemoteEvent\Attribute\AsRemoteEventConsumer;
use Symfony\Component\RemoteEvent\Consumer\ConsumerInterface;
use Symfony\Component\RemoteEvent\RemoteEvent;

#[AsRemoteEventConsumer(name: 'line')]
class LineEventConsumer implements ConsumerInterface
{
    public function __construct(
        private LoggerInterface $logger,
        private EventDispatcherInterface $dispatcher,

    ){
    }

    public function consume(RemoteEvent $event): void
    {
        $payload = $event->getPayload();
        $events = $payload['events'];
        // 以下にwebhookを受け取った際の処理を記述します
    }
}

まとめ

以上でwebhookリクエストを受け取ることができます。
RemoteEventのメリットは、nameプロパティでイベント名を指定することができ、Consumerクラスでイベント名に応じた処理を記述することができる点です。
webhookリクエストの中には、typeのようなプロパティ名でイベントが指定されており、それに応じたRemoteEventの返却と、Consumerクラスを作成することによって、イベントに応じた処理を疎結合な状態を保ったまま処理を記述することができます。

参考

GitHubで編集を提案

Discussion