
この記事は弁護士ドットコム Advent Calendar 2025の 14 日目の記事です。
はじめに
リーガルブレイン基盤チームのtsuchiyaです。
イベント駆動の処理を PHP で実装するにあたり、Bref の PHP functions runtime を採用しました。 本記事では、Bref の function ランタイムの基本的な使い方に加え、DI コンテナとの統合や Terraform + lambroll によるデプロイ構成を紹介します。
Bref とは
Bref は Composer で配布されている OSS のパッケージで、主に AWS Lambda 上で PHP を実行するための実行環境を提供しています。
Bref では以下の 3 つの実行環境を提供しています。
- ウェブアプリケーション向けの PHP-FPM ランタイム
- イベント駆動アプリケーション向けの function ランタイム
- CLI コマンド向けの console ランタイム
上記に加え、Laravel や Symfony のためのインテグレーションを提供しており、フレームワークユーザーも簡単にアプリケーションを Lambda にデプロイできます。
function ランタイム
function ランタイムは、AWS サービスからのイベントやデータを処理する際に用いるランタイムで、SQS による非同期タスクの例 や S3 のファイル処理の例が紹介されています。
Bref の function runtime にある例を以下に引用します。
<?php require __DIR__ . '/vendor/autoload.php'; return function ($event) { return 'Hello ' . ($event['name'] ?? 'world'); };
Dependency Injection コンテナとの統合
引用したコードは単純な例です。実際のアプリケーションにおいては、いくつかのクラスを組み合わせて実装したいニーズがあります。 PSR-11 の ContainerInterface を実装したクラスを利用することで、依存解決済みのクラスをハンドラーとして使えます。
以下のような、Bref::setContainer() に PSR-11 ContainerInterface を返す callable を渡す PHP ファイルを作成します。
<?php // init.php use Bref\Bref; use App; Bref::setContainer(static function () { return ContainerFactory::newInstance(); // PSR-11 を実装したクラスのインスタンスを生成し return する。 });
composer.json の autoload.files に、上記のファイルを設定します。
以下は、Bref の DI 統合の例 から引用した composer.json の設定例です。
{
"autoload": {
"psr-4": {
// ...
},
"files": [
"init.php"
]
},
}
ここまでで、任意のクラスをハンドラーとして指定して実行できるようになります。
Lambda のハンドラーとして、完全修飾名で対象となるクラスを指定します。
以下は、Bref の DI 統合の例 から引用した、serverless.yml の設定例です。
ここでは、MyApp\Handler という完全修飾名のクラスが指定されています。
functions:
hello:
handler: MyApp\Handler
型付きイベントハンドラ
Bref は汎用的な Handler インタフェースに加えて、AWS サービスごとの型付きハンドラを提供しています。これらを使うことでイベントのパース処理が不要になり、型安全にイベントを扱えます。
例えば SQS イベントを処理する場合は SqsHandler を継承します。
<?php namespace MyApp; use Bref\Context\Context; use Bref\Event\Sqs\SqsEvent; use Bref\Event\Sqs\SqsHandler; use MyApp\Service\MessageProcessor; class MySqsHandler extends SqsHandler { public function __construct( private MessageProcessor $processor ) {} public function handleSqs(SqsEvent $event, Context $context): void { foreach ($event->getRecords() as $record) { $body = $record->getBody(); $this->processor->process($body); } } }
SqsEvent や SqsRecord といった型付きオブジェクトを通じてメッセージにアクセスでき、生の配列を扱う必要がありません。
同様に S3 イベント用の S3Handler や EventBridge 用の EventBridgeHandler なども用意されています。
実際の構成
ここからは、実際に使用している構成を紹介します。
インフラ管理とデプロイ
インフラは Terraform で管理し、関数のデプロイには lambroll を使用しています。
Terraform では Lambda 関数のリソースを作成しつつ、lambroll の function.json で管理するパラメータは ignore_changes で無視します。
resource "aws_lambda_function" "example_function" { lifecycle { ignore_changes = [ architectures, environment, ephemeral_storage, handler, layers, logging_config[0].log_format, memory_size, runtime, snap_start, timeout, tracing_config, filename, source_code_hash, vpc_config, ] } function_name = "${var.prefix}-example-function" role = aws_iam_role.example-function.arn handler = "dummy" runtime = "provided.al2" filename = data.archive_file.dummy.output_path }
lambroll の function.json では Terraform の output を参照します。
{ "FunctionName": "{{ tfstate `output.lambda_example_function_name` }}", "Role": "{{ tfstate `output.lambda_example_function_role_arn` }}", "Runtime": "provided.al2", "Handler": "MyApp\\Handler", "MemorySize": 256, "Timeout": 5 }
この構成により、インフラの変更は Terraform で、関数コードや実行パラメータの調整は lambroll で、それぞれ独立してデプロイできます。
Ray.Di による依存解決
DI には Ray.Di を利用しています。 モジュールという単位で束縛ルールを管理・インストール可能で、弊社では複数のサービスで利用しています。
CompiledInjector
Ray.Di の通常の Injector は、実行時に依存グラフを解析しますが、Lambda 環境ではコールドスタートのオーバーヘッドを抑えたいところです。
CompiledInjector を使うことで、ビルド時に依存解決のコードを事前生成できます。
ビルド時にコード生成を済ませておくことで、実行時はリフレクションによる解析が不要になり、高速に起動できます。
以下は、コードを事前生成するためのスクリプトの例です。
<?php use Ray\Compiler\Compiler; use MyApp\AppModule; $appDir = dirname(__DIR__); require $appDir . '/vendor/autoload.php'; $module = new AppModule(); $compiler = new Compiler(); try { $compiler->compile($module, $appDir . '/var/tmp'); } catch (Throwable $e) { echo $e . PHP_EOL; exit(1); } echo 'Compiled successfully.' . PHP_EOL; exit(0);
Bref::setContainer を呼び出すファイルは、以下のようになります。
<?php declare(strict_types=1); use Bref\Bref; use MyApp\PsrContainer; use Ray\Compiler\CompiledInjector; Bref::setContainer(static function () { $tmpPath = __DIR__ . '/var/tmp'; // コンパイル済みのスクリプトのパスを指定 $injector = new CompiledInjector($tmpPath); return new PsrContainer($injector); // PSR-11 のラッパーでラップする });
まとめ
Bref の function ランタイムと PSR-11 コンテナの統合と実際に使用している Terraform + lambroll によるデプロイ構成や Ray.Di の活用例を紹介しました。 イベント駆動の処理を PHP で実装する際の参考になれば幸いです。