Symfony Component Advent Calendar 2022の18日目の記事です。
最初に
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のインポートは省略します。
実行環境を切り替える、"Runtime"
Runtimは、アプリケーションの実行環境を簡単に切り替えることができるコンポーネントです。Symfony以外でも利用できます。
インストール
composer require symfony/runtime
何をするのか?
Runtime
はすごくざっくり言うと、**『リクエストを受けて、レスポンスを返す処理をまとめたもの』**を用意しています。この場合のリクエストは、CLIでの実行リクエストも含みます。例えばHTTP通信であれば、前回のHttpKernelの、リクエスト生成からレスポンス出力までのロジックが記述されたRuntimeがあり、そのRuntimeを実行することで、前述の処理が毎回実行できるようになります。
RoadRunnerをつかう
例えば、RoadrunnerでSymfonyを動かしたい場合、Runtime
を使わなければ以下のようなWorkerを作成します。
use Spiral\RoadRunner;
use Nyholm\Psr7;
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface;
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\TerminableInterface;
include "vendor/autoload.php";
$psrFactory = new Psr7\Factory\Psr17Factory();
$worker = RoadRunner\Worker::create();
$worker = new RoadRunner\Http\PSR7Worker($worker, $psrFactory, $psrFactory, $psrFactory);
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$httpFoundationFactory = new HttpFoundationFactory();
$httpMessageFactory = new PsrHttpFactory($psrFactory, $psrFactory, $psrFactory, $psrFactory);
while ($request = $worker->waitRequest()) {
try {
$sfRequest = $httpFoundationFactory->createRequest($request);
$sfResponse = $kernel->handle($sfRequest);
$worker->respond($httpMessageFactory->createResponse($sfResponse));
if ($thiskernel instanceof TerminableInterface) {
$this->terminate($sfRequest, $sfResponse);
}
} catch (\Throwable $e) {
$worker->getWorker()->error((string) $e);
}
}
Workerを用意したら、.rr.yaml
に設定してrr serve
を実行すれば終了です。
server:
command: "php public/index.php"
http:
address: 0.0.0.0:8080
pool:
num_workers: 4
Runtimeを利用する
もちろん上記でも動作しますが、Symfonyが用意したpublic/index.php
に手を入れる必要があります。つまり、Symfonyがアップグレードした際に、public/index.php
に対して変更があった場合、その変更に合わせて再度手を入れる必要があります。
そこで、Runtime
を使うわけですが、上記のRoadrunnerように変更した箇所をRuntime
に入れることで、public/index.php
を修正することなく、Roadrunnerで実行できるようになります。
Runtime
はRunner
とRuntime
を実装します。
Runner
には上記のRoadrunner用に変更した処理を、Runtime
は、前述のRunner
を条件に合えば返します。
Runner
まずはRunner
ですが、上記の処理をほぼそのまま移します。オートワイヤリングできるので、オートワイヤリング可能なものは__construct()
でDIします。
namespace App\Runtime;
use Nyholm\Psr7;
use Spiral\RoadRunner;
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface;
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\TerminableInterface;
use Symfony\Component\Runtime\RunnerInterface;
class RoadrunnerRunner implements RunnerInterface
{
private $kernel;
private $httpFoundationFactory;
private $httpMessageFactory;
private $psrFactory;
public function __construct(HttpKernelInterface $kernel, ?HttpFoundationFactoryInterface $httpFoundationFactory = null, ?HttpMessageFactoryInterface $httpMessageFactory = null)
{
$this->kernel = $kernel;
$this->psrFactory = new Psr7\Factory\Psr17Factory();
$this->httpFoundationFactory = $httpFoundationFactory ?? new HttpFoundationFactory();
$this->httpMessageFactory = $httpMessageFactory ?? new PsrHttpFactory($this->psrFactory, $this->psrFactory, $this->psrFactory, $this->psrFactory);
}
public function run(): int
{
$worker = RoadRunner\Worker::create();
$worker = new RoadRunner\Http\PSR7Worker($worker, $this->psrFactory, $this->psrFactory, $this->psrFactory);
while ($request = $worker->waitRequest()) {
try {
$sfRequest = $this->httpFoundationFactory->createRequest($request);
$sfResponse = $this->kernel->handle($sfRequest);
$worker->respond($this->httpMessageFactory->createResponse($sfResponse));
if ($this->kernel instanceof TerminableInterface) {
$this->kernel->terminate($sfRequest, $sfResponse);
}
} catch (\Throwable $e) {
$worker->getWorker()->error((string) $e);
}
}
return 0;
}
}
Runtime
Runtime側はGenericRunner
の子クラスを作ります。
namespace App\Runtime;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Runtime\GenericRuntime;
use Symfony\Component\Runtime\RunnerInterface;
class RoadrunnerRuntime extends GenericRuntime
{
public function getRunner(?object $application): RunnerInterface
{
if ($application instanceof HttpKernelInterface) {
return new RoadrunnerRunner($application);
}
// HttpKernelではなかった場合は、デフォルトのRunnerを実行する
return parent::getRunner($application);
}
}
設定
上記で作成したRuntimeを使うように設定します。なんと.env
に書くだけで設定できます。
APP_RUNTIME=App\Runtime\RoadrunnerRuntime
あとは、rr serve
すれば動作します。
用意されているサードパーティRuntime
実は、すでにいくつかのRuntimeは用意されています。
現在、
- Swoole
- Roadrunner
- React PHP
- Bref
- Google Cloud
が公開されており、これらを使えば簡単に実行環境を切り替えることができます。
まとめ
今回は、Runtime
を紹介しました。現在のSymfonyではこのRuntime
を使うようにpublic/index.php
が記述されています。
どのようになってるか調べた記事があるので、こちらもぜひご覧ください。