RRelayer
ホーム/機能

サービスと DI#

Relayer は Symfony の DI コンテナを使い、オートワイヤ + public が既定です。登録方法は 2 通りあり、併用できます。

config/services.yaml#

services:
  _defaults:
    autowire: true
    autoconfigure: true
    public: true

  App\Service\PdoUserRepository: ~

  App\Service\UserRepository:
    alias: App\Service\PdoUserRepository

.yaml / .yml / .php 形式に対応。引数なしで登録した定義には自動でオートワイヤと public が付与されます(PSR-11 の get($id) で取得可能)。

環境別の設定#

services.yaml は環境ごとに切り替えられます。<env>APP_ENV=dev (または development)なら dev、それ以外はすべて prod に解決されます。

when@<env> ブロック — 1 つの services.yaml 内で env 分岐する Symfony 標準の書き方です。

# config/services.yaml
services:
  App\Service\Mailer: ~

when@dev:
  services:
    App\Service\LoggingMailer: ~   # dev でだけ Mailer を差し替え

services.{env}.{yaml,yml,php} サイドファイル — 基本ファイルの後に読み込まれる環境専用ファイル。環境別の定義が増えたら切り出せます。

# config/services.dev.yaml
services:
  App\Service\LoggingMailer: ~

読み込み順(低 → 高、後勝ち):

対象
1フレームワーク既定値
2services.{yaml,yml,php}when@<env> ブロック含む)
3services.{env}.{yaml,yml,php}
4AppConfigurator(後述)

.yaml.yml は同じファイルの別綴りで、両方あれば先に見つかった方だけを読みます(重ねがけしません)。services.php / services.{env}.php も同様です。

AppConfigurator#

<?php
namespace App;

use Polidog\Relayer\AppConfigurator as BaseConfigurator;
use Symfony\Component\DependencyInjection\ContainerBuilder;

final class AppConfigurator extends BaseConfigurator
{
    public function configure(ContainerBuilder $container): void
    {
        $container->register(PdoUserRepository::class);
        $container->setAlias(UserRepository::class, PdoUserRepository::class)
            ->setPublic(true);
    }
}

Relayer::boot(__DIR__ . '/..', new App\AppConfigurator(__DIR__ . '/..'))->run(); のように渡します。services.yaml の後に走るので上書きも可能。プロジェクトルートは $this->projectRoot%app.project_root% パラメータも使えます。

ファクトリ(実行時に実装を選ぶ)#

このサイトの DocStore は、環境変数で接続先を切り替えるためファクトリで生成しています。

services:
  App\Docs\DocStore:
    factory: ['App\Docs\DocStoreFactory', 'create']
    arguments:
      - '%app.project_root%'
    autowire: false

DocStoreFactory::create()TURSO_DATABASE_URL があれば Turso、なければローカル SQLite を返します。ページや route.php は引数に DocStore を型宣言するだけで注入されます。

環境変数を引数に渡す(%env()%#

services.yaml では Symfony 標準の %env(VAR)% プレースホルダで環境変数を参照できます。

services:
  App\Logger\NewRelic:
    arguments:
      $apiKey: '%env(NEW_RELIC_API_KEY)%'
      $appId:  '%env(NEW_RELIC_APP_ID)%'

型変換 / フォールバックには Symfony 標準のプリフィックスが使えます。 %env(int:DB_PORT)%%env(bool:FEATURE_FLAG)%%env(default::API_TOKEN)% など。AppConfigurator から渡したい場合も同じプレースホルダを setParameter に入れます。

final class AppConfigurator extends Base
{
    public function configure(ContainerBuilder $container): void
    {
        $container->setParameter('app.api_token', '%env(API_TOKEN)%');
    }
}

%env(VAR)% は dev / 本番のどちらでも解決します。dev は live ContainerBuilder がコンパイル時に現在の env を読み、container:compileCLI 参照)でダンプ済みクラスを使う本番はその中の getEnv('VAR') 呼び出しがリクエストごとに現在の env を見にいきます。

container:compile とランタイム secret#

container:compile はデプロイ時に AppConfigurator::configure() を 1 回だけ走らせ、その時点のコンテナをダンプ済みクラスに焼き込みます。本番ブート時はダンプを require するだけで AppConfigurator は再実行されません。

%env(VAR)% プレースホルダはダンプ時に getEnv('VAR') 呼び出しとして書き出されるのでリクエスト時の env をそのまま反映しますが、AppConfigurator が $_ENV['API_TOKEN'] のように env を直読みして文字列として setParameter() に渡した値は別の扱いになります。文字列のままダンプに焼き込まれ、本番では env を再評価しません。

したがって、Fly secrets / Cloud Run env / サイドカー injector のように ランタイムにしか secret が来ない構成では、ビルド時に env が空ならその空文字がダンプにベイクされ、本番でも空文字が返ります — "なぜか空文字" の静かな障害になります。回避策は env の直読みをやめ、%env(VAR)% プレースホルダを渡すこと。

// アンチパターン — ビルド時に空ならベイクされて空のまま
$container->setParameter('app.api_token', $_ENV['API_TOKEN'] ?? '');

// 推奨 — ダンプは getEnv('API_TOKEN') を発行し、リクエスト時に解決
$container->setParameter('app.api_token', '%env(API_TOKEN)%');

routes:compile はこの問題と無関係です(src/Pages/ を走査するだけで env を読まない)。container:compile だけが env を巻き込みます。どうしてもランタイム secret を扱えない事情があるなら、container:compile を外して routes:compile のみ採用し、リクエストごとの compile() コストを受け入れるのも妥当な選択です(dev は元から live ビルドなのでこの問題に当たりません)。

フレームワーク自身が読む DATABASE_* / APP_LOCALE などはこの仕組みとは別で、ContainerFactory が PHP レベルで直接 $_ENV を読んで素のパラメータ/コンストラクタ引数に変換します。container:compile を使う場合は同じくビルド時の値がベイクされるので、ランタイムにしか来ない値(Fly secrets の DATABASE_PASSWORD 等)を渡す構成では container:compile を外す運用が必要です(環境変数と .env カスケード 参照)。

オートワイヤ#

ページ/API ハンドラの引数は 型でオートワイヤされます。 PageContextRequestIdentity、そして登録済みサービス。

PSX コンポーネントからのサービス取得#

src/Components/ の PSX コンポーネントはプレーンなクロージャなので、コンストラクタ/引数インジェクションが使えません。コンポーネントの中では Relayer::container() で DI コンテナからサービスを直接取得します。

<?php
// src/Components/TodoList.psx
namespace App\Components;

use Polidog\Relayer\Db\Database;
use Polidog\Relayer\Relayer;

use function Polidog\UsePhp\Runtime\fc;

return fc(function (array $props) {
    $db = Relayer::container()->get(Database::class);
    $todos = $db->fetchAll('SELECT id, title, done FROM todos ORDER BY id');

    return <ul>{array_map(fn ($t) => <li>{$t['title']}</li>, $todos)}</ul>;
});

Relayer::container()boot() が構築した PSR-11 コンテナを返します(boot() より前に呼ぶと LogicException)。これはコンポーネント専用の逃げ道です。ページ・API ハンドラ・ルートクラスなど通常のサービスは、上のオートワイヤ/AppConfigurator でのコンストラクタインジェクションを使うのが正道——コンポーネントに値を渡したいときも、まずは props を検討してください。

最終更新: 2026-05-23
変更履歴 (5)
  • v0.24.0: PSX コンポーネントからの Relayer::container() サービス取得を追記
  • v0.23.0: env別 convention-config(when@<env> / services.{env}.*)の『環境別の設定』を追記
  • v0.20.0 の %env() dev 解決と container:compile ベイク警告を反映
  • Document %env() placeholders in services.yaml + container:compile caveat (dev/live ContainerBuilder leaks env_<hash>_VAR_<hash>; production dumped container resolves via getEnv()). Add AppConfigurator
  • 新規作成