PHPackages                             polidog/relayer - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. polidog/relayer

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

polidog/relayer
===============

Relayer — file-based router + Symfony DependencyInjection + dotenv on top of polidog/use-php.

v0.25.0(2mo ago)188MITPHP &gt;=8.5

Since May 15Compare

[ Source](https://github.com/polidog/relayer)[ Packagist](https://packagist.org/packages/polidog/relayer)[ RSS](/packages/polidog-relayer/feed)WikiDiscussions Synced 3w ago

READMEChangelog (10)Dependencies (11)Versions (40)Used By (0)

Relayer
=======

[](#relayer)

[English](README.md) · [日本語](README.ja.md)

Opinionated, batteries-included framework on top of [polidog/use-php](https://github.com/polidog/usePHP). Bundles:

- A Next.js App Router-style file-based router (`src/Pages/page.psx`, `layout.psx`, dynamic segments, error pages)
- File-based JSON API routes (`src/Pages/.../route.php`) — a method-keyed map of autowired handlers, each returning a `Response`(`Response::json()` / `text()` / `noContent()` / `redirect()`); `OPTIONS` / `HEAD` synthesized like Next.js Route Handlers
- Per-page / per-layout external scripts (`$ctx->js()` / `PageComponent::addJs()` / `LayoutComponent::addJs()`) emitted at the end of `` after the bundle, in declaration order
- React islands (`Island::mount()`) — a rich-UI escape hatch: server-rendered shell, client React component, props from PHP, your own bundle
- Optional root middleware (`src/Pages/middleware.php`) wrapping every dispatch, plus a ready-made `Cors` middleware
- CSRF-protected server actions (`$ctx->action()` / `PageComponent::action()` dispatch form posts to in-page handlers)
- [Symfony DependencyInjection](https://symfony.com/doc/current/components/dependency_injection.html)for service wiring (autowire, YAML/PHP config auto-load)
- [symfony/dotenv](https://github.com/symfony/dotenv) for `.env` loading with the standard `.env` / `.env.local` / `.env.{APP_ENV}` cascade
- `#[Cache]` attribute for HTTP cache headers + `If-None-Match` 304 handling with pluggable `EtagStore` (file-based default, Redis-ready)
- Session-based authentication: `#[Auth]` attribute / `$ctx->requireAuth()`, role checks, password hashing, pluggable `UserProvider` and `SessionStorage`
- Bearer **token** auth: server-side Firebase / Cognito ID-token verification (JWKS, cached + rotation-aware) — stateless API or verify-then-session, same `#[Auth]` guard
- [Zod](https://zod.dev/)-style schema validation (`Validator::object()`, `safeParse` / `parse`, form-input coercion + per-field errors)
- A dev-only request profiler (`/_profiler` view, no-op in production)

Exposes a single `Relayer::boot()` entrypoint so app code stays small.

Requirements
------------

[](#requirements)

- PHP &gt;= 8.5
- [polidog/use-php](https://github.com/polidog/usePHP) ^0.1.0
- [symfony/dependency-injection](https://github.com/symfony/dependency-injection) ^7.1
- [symfony/config](https://github.com/symfony/config) ^7.1
- [symfony/yaml](https://github.com/symfony/yaml) ^7.1
- [symfony/dotenv](https://github.com/symfony/dotenv) ^7.1

Installation
------------

[](#installation)

```
composer require polidog/relayer
```

Scaffold a new project
----------------------

[](#scaffold-a-new-project)

`relayer init` lays the project structure into the **current directory**. Run it from your project root after requiring the framework:

```
composer require polidog/relayer
vendor/bin/relayer init
composer install
php -S 127.0.0.1:8000 -t public
```

`composer install` (rather than `dump-autoload`) so the `App\` autoload *and* the publish scripts `init` just added both apply — the latter emits `public/usephp.js`, which the default document references.

It is idempotent and non-destructive:

- existing files are never overwritten (they are reported as skipped), so it is safe to re-run;
- your existing `composer.json` is patched **additively** — it adds the `App\`PSR-4 autoload, the usePHP asset-publish scripts, and an `extra.relayer.structure_version` marker, and leaves everything else untouched.

The `structure_version` marker records which skeleton shape the project was generated against, so `relayer upgrade` (below) can migrate it forward.

`init` also scaffolds **`RELAYER.md`** — concise, authoritative coding conventions for agents/LLMs working in the project (file conventions, the `route.php` / `middleware.php` / `Island` contracts, the minimal-design philosophy, a "do not" list) — plus 2-line **`AGENTS.md`** and **`CLAUDE.md`** pointers to it (the filenames agent tools / Claude Code auto-read). All ship inside `polidog/relayer`, so they are **co-versioned with the framework and cannot drift**, and all are skip-if-exists, so a project's own `AGENTS.md` / `CLAUDE.md` is never overwritten. It additionally scaffolds Claude Code tooling under **`.claude/`** — a `relayer-routing` **skill** (the routing / `Response` / CSRF contracts, trigger-scoped) and a `relayer-reviewer` **subagent** that reviews changes against `RELAYER.md`. Both defer to `RELAYER.md` as the single source of truth and are co-versioned + skip-if-exists for the same reason. Run `vendor/bin/relayer routes` for the project's actual route map.

### Upgrading the project structure

[](#upgrading-the-project-structure)

When you bump `polidog/relayer`, newer framework versions may add files to the generated skeleton. `relayer upgrade` brings an existing project up to the installed framework's structure:

```
composer update polidog/relayer
vendor/bin/relayer upgrade
composer install
```

It reads the `extra.relayer.structure_version` marker, writes only the files added in the versions between it and the current one, then advances the marker (the one mutation `init` deliberately never makes). Every step is **skip-if-exists**, so files you have edited are kept and reported as skipped; the scope is exactly the structure deltas plus the marker — it does not touch composer scripts or autoload (re-run `relayer init` for those — it is additive and safe). It is idempotent: once at the current version it reports nothing to do. If the project has no marker it was not created by `relayer init`; run `init` first to stamp the current shape.

Project Layout
--------------

[](#project-layout)

```
your-app/
  .env                 # loaded automatically if present
  composer.json
  config/
    services.yaml      # auto-loaded if present (also services.php / .yml)
  public/
    index.php
  src/
    Pages/             # AppRouter file-based routes live here
      layout.psx
      page.psx
      about/
        page.psx
    AppConfigurator.php # your service registrations (extends Polidog\Relayer\AppConfigurator)

```

Quick Start
-----------

[](#quick-start)

`public/index.php`:

```
