CLI Commands#
Installing composer require polidog/relayer puts two executables in vendor/bin/.
| Binary | Role |
|---|---|
vendor/bin/relayer | Project scaffolding/inspection (the framework) |
vendor/bin/usephp | Precompiling .psx (the PSX engine, from use-php) |
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)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.jsonis 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 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:
0success /1I/O failure /2misuse (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_versionmarker and writes
only the files added between that value and the current version, then advances the marker. It is the only command that moves it.
- Every step is skip-if-exists. Edited files are kept as-is and
reported as skipped (non-destructive).
- Scope is the structure deltas and the marker only. It does not
touch composer scripts or autoload; those are additive and safe, so re-run relayer init for them 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:
0success / already current /1I/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), and file lined up.
vendor/bin/relayer routesroute.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/Pagesdoes not exist, it says so and exits.
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 rebuilt 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` whose constructor takes the registered listeners and whose interface methods forward to them in the same order. A statically visible dispatch chain: open the file and the order in which each listener is called for each hook is right there.
Run it at deploy time (→ Deployment).
vendor/bin/relayer routes:compile- Only their presence is the gate (the two files are independent).
routes.php present skips route scanning, dispatcher.php present switches to loading the dump; absent, each falls back to its 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 polymorphically through a RuntimeDispatcher, so the two paths are observationally identical.
- Scan-time ambiguities (a
pagevsroute.phpcollision, route-group
URL collisions) fail in this command rather than on the first production request.
dispatcher.phpbakes only the listener service IDs (class
names) — constructor arguments are not baked. Listeners whose constructors take env-derived parameters still get those resolved through the live container at request time (independent of the container:compile env-bake caveat).
- The outputs are always those two paths (auto-generated; do not edit
by hand — regenerate with the same command). They are a separate step from usephp compile (.psx); run both at build time.
- Exit codes:
0success /1missing or unscannablesrc/Pages, or
a write failure.
For registering custom listeners, see the "DispatchListener — dispatch extension" section in Profiler.
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.yamlor
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 usephp compile (.psx) and routes:compile (the route table); run all three at build time.
- Runtime secret note.
AppConfigurator::configure()runs exactly
once at deploy time and the result is baked into the dump. Reading env directly and passing it as a plain string to setParameter() freezes the build-time value (often an empty string) for every production request — for values that only arrive at runtime (Fly secrets, Cloud Run env, etc.) hand the %env(VAR)% placeholder instead (see Services & DI).
- Exit codes:
0success /1build, dump, or write failure.
relayer profiler:clear#
Deletes the dev profiler records (var/cache/profiler/*.json). See Profiler for details.
vendor/bin/relayer profiler:clearIdempotent, deleting only *.json (directories and other files remain). Empty still succeeds with 0; on a delete failure it reports and returns 1.
usephp compile#
A build step that precompiles .psx so there is no compilation at request time. Run it in production deployment (Deployment).
vendor/bin/usephp compile [options] [paths...]- It scans each path (file/directory) and compiles
.psxinto 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.
| Option | Effect |
|---|---|
--check | Does not write; exits non-zero if anything is stale (good as a CI/build gate) |
--clean | Deletes the contents of the cache directory |
--watch | Recompiles on .psx change (stop with Ctrl+C) |
--cache=PATH | Cache destination (default <cwd>/var/cache/psx) |
vendor/bin/usephp compile src # precompile under src
vendor/bin/usephp compile --check src # CI: fail if not compiledThis 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).
Change history (7)
- 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