Relayer
DocsOperationscli

CLI Commands#

Installing composer require polidog/relayer puts these executables in vendor/bin/.

BinaryRole
vendor/bin/relayerProject scaffolding/inspection (the framework)
vendor/bin/rosanna-deferPrecompiling .psx and placing rosanna-defer.js (the PSX engine, Rosanna)

vendor/bin/rosanna is installed too, but do not use its compile: it does not write the deferred manifest Relayer uses.

relayer#

With no arguments, -h, --help, or help it prints usage (no arguments exits with code 2; an explicit help exits 0). An unknown command prints usage and 2.

vendor/bin/relayer            # usage (exit 2)
vendor/bin/relayer --help     # usage (exit 0)

-h / --help after a subcommand prints usage and exits 0; an unknown option (an argument starting with -) prints usage and exits 2. Both stop before any file is written.

relayer init#

Scaffolds the project structure into the current directory. Run it in a directory where composer require polidog/relayer has already been done.

  • It does not touch existing files. composer.json is patched additively, and extra.relayer.structure_version is stamped (do not edit it by hand). init never advances this marker, so the shape a project was generated against is always identifiable; advancing it is relayer upgrade's job (below).
  • The generated artifacts include a FrankenPHP development container (Dockerfile, php.ini, compose.yaml, .dockerignore). It starts with docker compose up --build without a host PHP (→ Getting Started).
  • It also generates convention files for AI agents. RELAYER.md (the single source of truth for routing / the Response contract / CSRF / design policy), plus a two-line AGENTS.md / CLAUDE.md that merely point to it (the file names that agent tooling and Claude Code each read automatically). They are bundled with polidog/relayer, co-versioned with the framework, and skip-if-exists (they do not overwrite the user's existing files).
  • It also generates Claude Code tooling under .claude/ — a relayer-routing skill (.claude/skills/relayer-routing/SKILL.md) that passes the routing / Response / CSRF contract on trigger, and a relayer-reviewer subagent (.claude/agents/relayer-reviewer.md) that reviews changes against RELAYER.md. Both treat RELAYER.md as the single source of truth and are likewise co-versioned and skip-if-exists.
  • It writes .env.local (APP_ENV=dev) only into a new project: one with no .env, no marker, and none of src/Pages, src/AppConfigurator.php, public/index.php, or public/worker.php. Before writing, it makes sure .gitignore excludes .env.local (adding /.env.local if missing) and fails if it cannot. It does not create the file when Git tracks .env.local. In an existing project or a fresh clone, run echo APP_ENV=dev >> .env.local (→ Environment Variables).
  • It rejects the case where the PSR-4 mapping of App\ points somewhere other than src/ (because public/index.php is App\AppConfigurator, which assumes src/).
  • Exit codes: 0 success / 1 I/O failure / 2 misuse (no composer.json, not JSON, etc.).

relayer upgrade#

Upgrading polidog/relayer can make a newer framework version add files to the generated scaffold. upgrade advances an existing project's structure up to the installed framework's structure. It is the counterpart to relayer init.

composer update polidog/relayer
vendor/bin/relayer upgrade
composer install
  • It reads the extra.relayer.structure_version marker, applies the deltas between that value and the current version, then advances the marker. It is the only command that moves it.
  • New files are skip-if-exists. Edited files are kept as-is and reported as skipped (=, non-destructive).
  • A version can also change the content of a file it shipped earlier, such as the Dockerfile gaining a production target. That replacement is gated on the file being untouched: upgrade replaces it only when it is byte-for-byte what some framework version wrote (~). If you edited it at all, it is left exactly as-is and reported as kept (!). To take the new template, save your changes elsewhere, delete the file, and run relayer init.
  • When it replaces an untouched .env, it moves the APP_ENV=dev there into the gitignored .env.local: it creates the file if missing, appends the line if the file does not set APP_ENV, and leaves it alone if it does. An edited .env is kept and .env.local is not written, so check APP_ENV yourself. Before writing, it makes sure .gitignore excludes .env.local, and writes nothing if it cannot. If Git tracks .env.local, it fails and leaves .env and .env.local as they are; run git rm --cached .env.local, commit, then re-run.
  • Scope is the structure deltas and the marker only. In composer scripts and autoload, it only replaces an asset publish script that points at a class that no longer exists with Polidog\Rosanna\Defer\Installer\AssetInstaller::publish. Anything else missing is additive and safe, so re-run relayer init for it if needed.
  • Idempotent. If the project is already at the current version it reports that there is nothing to do and exits.
  • A project with no marker was not generated by relayer init, so stamp the current structure with init first.
  • Exit codes: 0 success / already current / 1 I/O or state error / 2 misuse (no composer.json, no marker, etc.).

relayer routes#

Lists the routes detected under src/Pages. They are sorted by path, with method, path, kind (page / api / native), and file lined up. A root middleware is shown at the end, marked (php) or (native).

vendor/bin/relayer routes
  • route.php (API routes) shows the declared HTTP methods. Pages show GET,POST. An unreadable route.php is not hidden — it is shown with ? plus a warning line.
  • If src/Pages does not exist, it says so and exits.

relayer compile#

Builds every deploy artifact at once: .psx (src/Pages and src/Components, skipping a missing directory), routes:compile, then container:compile, stopping at the first failure.

vendor/bin/relayer compile

relayer native:build#

Compiles every route.go and middleware.go under src/Pages to var/cache/native/bin. It needs Go, so relayer compile does not include it (→ native routes).

relayer routes:compile#

Precompiles the route table and the dispatch pipeline. By default the router walks src/Pages/ on every request, and the dispatch listener chain is assembled from container tags on every boot. Running this writes two snapshots:

  • <projectRoot>/var/cache/routes/routes.php — the discovered route table.
  • <projectRoot>/var/cache/routes/dispatcher.php — a final class CompiledDispatcher implements DispatchListener. It receives the registered listeners in the constructor and forwards each hook in the same order. Open the file and the listener order is statically visible.

Run it at deploy time (→ Deployment).

vendor/bin/relayer routes:compile
  • Only their presence is the gate (the two files are independent). If routes.php exists, route scanning is skipped; if dispatcher.php exists, the dump is loaded. Missing files fall back to the matching live path. dev does not compile, so it always reflects the live tree and listener set and never goes stale. Without dispatcher.php, the same relayer.dispatch_listener tag is fanned out through RuntimeDispatcher in the same order, so the observable behavior is identical.
  • Scan-time ambiguities (a page vs route.php collision, route-group URL collisions) fail in this command rather than on the first production request.
  • dispatcher.php bakes only the listener service IDs (class names). Constructor arguments are not baked. Listeners with env-derived constructor arguments are still resolved through the live container at request time (independent of the container:compile caveat).
  • The outputs are always the two paths above (auto-generated; do not edit by hand — regenerate with the same command). It is a separate step from rosanna-defer compile (.psx); run both at build time.
  • Exit codes: 0 success / 1 missing or unscannable src/Pages, or a write failure.

relayer container:compile#

Precompiles the DI container. By default Relayer::boot() builds a Symfony ContainerBuilder and runs compile() on every request — usually the largest avoidable per-request cost. Running this dumps it to a plain PHP class at <projectRoot>/var/cache/container/CompiledContainer.php (Polidog\Relayer\Generated\CompiledContainer), and production requires that file instead of rebuilding. Run it at deploy time (→ Deployment).

vendor/bin/relayer container:compile
  • Only its presence is the gate. If the file exists production uses it; if not, it falls back to a live build. dev does not compile, so it always reflects the latest service configuration and never goes stale.
  • Bad service definitions (errors in config/services.yaml or App\AppConfigurator) fail in this command rather than on the first production request — same deploy-time failure property as relayer routes:compile.
  • Regenerate after changing service configuration (re-run the same command). The output is always the path above (auto-generated; do not edit by hand). It is a separate step from rosanna-defer compile (.psx) and routes:compile (the route table); run all three at build time.
  • Exit codes: 0 success / 1 build, dump, or write failure.

relayer profiler:clear#

Deletes the dev profiler records (var/cache/profiler/*.json). See Profiler for details.

vendor/bin/relayer profiler:clear

Idempotent, deleting only *.json (directories and other files remain). Empty still succeeds with 0; on a delete failure it reports and returns 1.

rosanna-defer compile#

A build step that precompiles .psx so there is no compilation at request time. Run it in production deployment (Deployment).

vendor/bin/rosanna-defer compile [options] [paths...]
  • A missing path exits 1 (a new project has no src/Components, so use relayer compile to build everything).
  • The manifest is rewritten on every run. Pass src/Components and src/Pages in one command.
  • It scans each path (file/directory) and compiles .psx into the cache directory (named by the sha1 of the source's absolute path, so the source tree is not polluted).
  • It writes out manifest.php (PascalCase component FQCN → generated path) and deferred-manifest.php, which collects the Defer config (Defer Components auto-register via this). Lowercase-named page.psx / layout.psx are compiled but do not appear in the manifest and are loaded directly by path.
  • If the source hash matches *.meta, recompilation is skipped (incremental). When paths is omitted, the default is <cwd>/components, so in a setup like this site you specify src explicitly.
OptionEffect
--checkDoes not write; exits non-zero if anything is stale (good as a CI/build gate)
--cleanDeletes the contents of the cache directory
--watchRecompiles on .psx change (stop with Ctrl+C)
--cache=PATHCache destination (default <cwd>/var/cache/psx)

An unknown option exits 1. --check and --watch cannot be combined.

vendor/bin/rosanna-defer compile src                 # precompile under src
vendor/bin/rosanna-defer compile --check src         # CI: fail if not compiled

vendor/bin/rosanna-defer publish places public/rosanna-defer.js. relayer init registers Polidog\Rosanna\Defer\Installer\AssetInstaller::publish in composer's post-install-cmd / post-update-cmd, so composer install normally does it for you.

This site's article-management CLI bin/docs (migrate/list/new/edit/show/ rm/export/import) is this site's own tool, not relayer — it edits the Turso store directly. It is not a framework command, so treat it as something separate (→ Deployment).

Last revised — 2026-11-04
Revisions (10)
  • v1.1.0: usePHP→Rosanna(rosanna-defer)に合わせて更新
  • v0.31.0: relayer compile、native:build、usephp compile の失敗条件を追記
  • Reflect v0.26.0 relayer upgrade and routes:compile behavior
  • v0.21.0 routes:compile dumps dispatcher.php; RuntimeDispatcher fallback
  • v0.20.0 — add runtime secret bullet for container:compile
  • Document the relayer container:compile command (v0.19.0)
  • Sidebar reorg: order shift for the new Development category
  • Document the relayer routes:compile command
  • Add the relayer upgrade command (v0.18.0)
  • Created