{"id":25952123,"url":"https://github.com/davidwyly/rxn","last_synced_at":"2026-07-09T03:31:34.091Z","repository":{"id":56963536,"uuid":"53752544","full_name":"davidwyly/rxn","owner":"davidwyly","description":"Rxn (Reaction) - A fast, simple, and powerful API framework for PHP","archived":false,"fork":false,"pushed_at":"2026-05-11T17:52:19.000Z","size":1631,"stargazers_count":3,"open_issues_count":2,"forks_count":0,"subscribers_count":5,"default_branch":"master","last_synced_at":"2026-05-22T18:59:52.875Z","etag":null,"topics":["api-rest","api-versioning","autoloading","autowiring","backend","crud-api","dependency-injection","frontend-decoupling","json-api","lightweight","method-injection","orm-library","php-framework","php7","rxn","scaffolding","schema-migrations","work-in-progress"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/davidwyly.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2016-03-12T20:44:58.000Z","updated_at":"2026-05-04T22:30:56.000Z","dependencies_parsed_at":"2022-08-21T08:20:55.348Z","dependency_job_id":null,"html_url":"https://github.com/davidwyly/rxn","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/davidwyly/rxn","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidwyly%2Frxn","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidwyly%2Frxn/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidwyly%2Frxn/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidwyly%2Frxn/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/davidwyly","download_url":"https://codeload.github.com/davidwyly/rxn/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidwyly%2Frxn/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35286002,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-09T02:00:07.329Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-rest","api-versioning","autoloading","autowiring","backend","crud-api","dependency-injection","frontend-decoupling","json-api","lightweight","method-injection","orm-library","php-framework","php7","rxn","scaffolding","schema-migrations","work-in-progress"],"created_at":"2025-03-04T14:42:53.747Z","updated_at":"2026-07-09T03:31:34.080Z","avatar_url":"https://github.com/davidwyly.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"![alt tag](http://i.imgur.com/nu63B1J.png?1)\n\n#### An opinionated JSON micro-framework for PHP.\n\n##### Status: alpha. Targets PHP 8.2+.\n\nRxn (from \"reaction\") tries to land all three of **fast**,\n**readable**, and **small** at the same time. The usual trilemma\n(\"pick two\") is real only when the three are treated as orthogonal\naxes to optimise independently — they aren't. Bad design hurts all\nthree at once; good design helps all three at once. The\n[design philosophy doc](docs/design-philosophy.md) is the working\ntheory.\n\nThe operational consequence is a single opinion: **strict\nbackend/frontend decoupling**. The backend is API-only, responds in\nJSON, and rolls up every uncaught exception into a JSON error\nenvelope (RFC 7807 Problem Details). Frontends — web, mobile,\nwhatever — build against the versioned contracts and stay\ndecoupled. JSON-only is a *narrowing* decision that pays\ndividends down the stack: no content negotiation, no view layer,\none error envelope.\n\nVendor-flavoured concerns ship as separate packages so the core\nstays narrow:\n- [`davidwyly/rxn-orm`](https://github.com/davidwyly/rxn-orm) —\n  query builder + ActiveRecord-shaped layer.\n- [`davidwyly/rxn-observe`](https://github.com/davidwyly/rxn-observe) —\n  OpenTelemetry listener over the framework's PSR-14 event\n  surface; drop in for span trees.\n\nBoth are opt-in via `composer require`; no plumbing in core, no\ncost when not installed.\n\n## At a glance\n\n```mermaid\nflowchart TB\n    Req[\"HTTP request\"] --\u003e Serve[\"App::serve(Router)\"]\n    Serve --\u003e Router[\"Http/Router\u003cbr/\u003e(explicit or attribute-driven)\"]\n    Router --\u003e Pipeline[\"Middleware pipeline\"]\n    Pipeline --\u003e Handler[\"Route handler\u003cbr/\u003e+ DTO bind/validate\"]\n    Handler --\u003e Resp[\"Response\"]\n    Resp --\u003e|success| OK[\"application/json\u003cbr/\u003e{data, meta}\"]\n    Resp -. uncaught exception .-\u003e Fail[\"middleware exception handler\"]\n    Fail --\u003e PD[\"application/problem+json\u003cbr/\u003eRFC 7807\"]\n```\n\n`App::serve(Router)` is the entry point — populate the Router\ndirectly or from `#[Route]` attributes via `Http\\Attribute\\Scanner`.\nThe framework is boot-free: no constructor, no Container plumbing,\nno DB connection during request setup.\n\nSee [`docs/index.md`](docs/index.md) for the full request sequence\nand per-subsystem deep dives.\n\n## Why Rxn\n\nFive motives drive every decision in the framework:\n**novelty, simplicity, interoperability, speed, and strict JSON.**\n\n### Strict JSON\n\nEvery exit point — including uncaught exceptions — is a JSON\nresponse. Slim / Lumen / Mezzio / API Platform all default to\nJSON but still let controllers return HTML, XML, streams; that\nflexibility forces a content-negotiation layer you can't opt out\nof, and every app on top has to remember a surprising exception\ncan leak an HTML stack trace. Rxn removes the choice. Success\nlands on `{data, meta}`; errors land on\n`application/problem+json`. Two shapes, both machine-readable,\nzero negotiation code.\n\n### Interoperability\n\nErrors are **RFC 7807 Problem Details**, not a bespoke envelope.\nAPI gateways, Problem Details-aware client libraries, and error\naggregators already understand the shape; Rxn just emits what the\necosystem expects. **OpenAPI 3 specs generate from reflection**\n(`bin/rxn openapi`), so the contract is always in sync with the\ncode — hand the spec to any OpenAPI consumer (Redocly, client\ngenerators). Drop in `Http\\OpenApi\\SwaggerUi::html($specUrl)`\nfrom a route handler for instant interactive docs.\n\n**PSR-native end-to-end.** PSR-7 ingress (default), PSR-15\nmiddleware (the contract for all eight shipped middlewares),\nPSR-11 container, PSR-3 logger, PSR-14 events — every framework\ninterface satisfies the relevant PSR. Any third-party CORS /\nOAuth / OpenTelemetry / JWT / rate-limit middleware drops into\nthe `Pipeline`; pass the container to a PSR-11-aware library;\nsubscribe a PSR-14 listener to `IdempotencyHit` for replay-rate\ndashboards. No adapters, no escape hatches — the framework's\ncontracts *are* the PSR contracts.\n\n### Novelty\n\nOpinionated pieces worth naming:\n\n- **Typed DTO binding + attribute-driven validation.** Declare\n  `public function create_v1(CreateProduct $input): array`, give\n  `CreateProduct` public typed properties with `#[Required]`,\n  `#[Min(0)]`, `#[Length(min: 1, max: 100)]`, etc., and the\n  framework hydrates, casts, validates, and hands your action a\n  populated instance — or fails the whole request with a 422\n  Problem Details listing *every* field error at once. The same\n  FastAPI-class ergonomic move that almost nothing in the PHP\n  ecosystem ships natively, in ~250 LoC with no DSL.\n- **Attribute routing + middleware** on the controller method:\n  `#[Route('GET', '/products/{id:int}')]` and\n  `#[Middleware(Auth::class)]` *are* the route table. No separate\n  `routes.php` to drift out of sync.\n- **Typed route constraints** (`{id:int}`, `{slug:slug}`,\n  `{id:uuid}`, custom) so `/users/foo` falls through to 404\n  instead of reaching a controller that has to validate and throw.\n- **API versioning as a primitive** — `#[Version('v1')]` on a\n  method (or class) prefixes the route's path; `#[Version('v1',\n  deprecatedAt: '…', sunsetAt: '…')]` auto-attaches a middleware\n  that emits RFC 8594 `Deprecation:` / `Sunset:` headers. Multiple\n  versions of the same logical endpoint coexist as distinct paths;\n  `routes:check` knows the difference between \"intentional\n  cross-version routes\" and \"real conflict.\"\n- **CRUD scaffolding via Resource handlers** — one call\n  (`ResourceRegistrar::register($router, '/products', $handler, …)`)\n  wires the full create/read/update/delete/search route family.\n  Handler is a 5-method interface against any storage; framework\n  does DTO binding, validation-failure → 422 wrapping, missing-row\n  → 404, deleted → 204. The \"extend a class, get five endpoints\"\n  ergonomic the convention router gave us — but with typed wire\n  (DTOs, not arrays), pluggable storage (rxn-orm's\n  `RxnOrmCrudHandler` base or your own ~50-LOC class), and OpenAPI\n  auto-generated from the same DTOs.\n- **Compile-time route conflict detection.** `bin/rxn routes:check`\n  flags ambiguous `#[Route]` patterns before they ship —\n  `/items/{id:int}` vs `/items/{slug:slug}` (slug accepts\n  digits) is a real conflict; the runtime would silently let\n  whichever was registered first win and leave the other as\n  dead code. CI catches it instead.\n- **Reflection-driven OpenAPI + snapshot contract gate** — the\n  framework knows your controllers; why duplicate that in a YAML\n  file? DTO validation attributes map one-to-one to JSON Schema\n  keywords, so the spec *can't* drift from the runtime behaviour\n  — both sides read the same class. `bin/rxn openapi:check`\n  closes the loop in CI: regenerate spec → diff against the\n  committed snapshot → fail the build on breaking changes\n  (operation removed, type changed, constraint tightened, …)\n  unless the PR opts in. Schema-as-truth becomes\n  schema-as-governance.\n- **Production-safe by default** — stack traces never ship outside\n  dev, boundary input sanitisation is one env flag, session\n  cookies auto-flip to `Secure` behind an HTTPS proxy.\n\n### Simplicity\n\nSmall enough to read end to end — **~11K LOC of framework code\nships what a comparably-featured Slim or Mezzio composition\nreaches in 70–100K LOC of vendor packages** (DTO binding +\nattribute-driven validation, OpenAPI from reflection, idempotency\nmiddleware with three storage shapes, RFC 7807 envelope, eight\nproduction middlewares, schema-compiled fast paths — all in one\nrepository). Slim is small because it offloads to the ecosystem;\nSymfony is comprehensive because it doesn't. Rxn is small *and*\nfeature-dense — the schema-as-truth principle (one DTO drives\nbinding, validation, OpenAPI, and the compiled hydrator) is what\nmakes that arithmetic work.\n\nDependency-free, injectable-for-test middlewares for the common\ndefensive layers: **BearerAuth, CORS with preflight, ETag, JSON-\nbody decoding with size caps, Idempotency (Stripe-style replay),\nPagination, RequestId, TraceContext (W3C)**. DI container supports\n**interface-to-implementation binding** (`$c-\u003ebind(UserRepo::class,\nPostgresUserRepo::class)`) and factory closures, so serious apps\naren't stuck with autowire-only. An in-process **TestClient**\n(`Rxn\\Framework\\Testing\\TestClient`) fires requests at your Router\n+ middleware stack and returns a `TestResponse` with PHPUnit-\nintegrated fluent assertions — no web server, no curl, no process\nboundary. The ORM lives in a separate package\n([`davidwyly/rxn-orm`](https://github.com/davidwyly/rxn-orm)) so\nthe framework itself stays narrow.\n\n### Speed\n\nCross-framework HTTP throughput, PHP 8.4, `php -S` per-request\nworker mode (full table + methodology in\n[`bench/ab/CONSOLIDATION.md`](bench/ab/CONSOLIDATION.md)):\n\n| Framework | GET /hello | GET /products/{id} | POST valid | POST 422 |\n|---|---:|---:|---:|---:|\n| **rxn** | **21,530** | **21,080** | **27,160** | **25,690** |\n| symfony micro-kernel | 17,250 | 15,970 | 15,490 | 15,650 |\n| raw PHP (no framework) | 17,140 | 16,930 | 17,080 | 17,120 |\n| slim 4 | 13,800 | 13,780 | 13,460 | 13,480 |\n\n**1.5–2× the throughput of Slim**, **1.25–1.75× Symfony**, and on\nbinder-driven POSTs **1.5–1.6× faster than hand-rolled raw PHP**\ndoing the same `json_decode` + manual validation. The\nschema-compiled fast paths (`Validator::compile`,\n`Binder::compileFor`, container factory cache) earn the gap;\nPSR-7 ingress + `Binder::bindRequest(ServerRequestInterface)`\ncloses another 33–42% on POST cells vs the previous superglobal\npath. p50 latency on the binder-heavy POST cell: 0.71ms (Slim:\n1.47ms; raw PHP: 1.14ms).\n\nHow that's possible (the\n[design philosophy](docs/design-philosophy.md) document is the long\nversion):\n\n- **PSR-4 autoloading; no reflection on the hot path once caches\n  warm.** Container caches reflection / construction plans /\n  parsed-name lookups and compiles per-class factory closures —\n  five stacked optimisations, transparent, ~2.2× cumulative.\n- **Optional schema-compiled fast paths.** For long-lived workers\n  (RoadRunner / Swoole / FrankenPHP), `Validator::compile($rules)`\n  runs **2.45×** faster than the runtime path; `Binder::compileFor($class)`\n  runs **6.4×** faster. Same APIs, two performance profiles.\n- **OPcache preload script** ([`bin/preload.php`](bin/preload.php))\n  for fpm cold-start latency.\n- **File-backed query caching** (`Database::setCache()`) and\n  **object file caching** with atomic writes for reflection-derived\n  data.\n- **ETag middleware** drops 304s for unchanged GETs before your\n  controller serializes a byte of response.\n- **No content-negotiation layer** to walk on every request.\n- **Sync-first, process-per-request, predictable.** We deliberately\n  don't chase async — PHP-FPM's process pool gives you concurrent-\n  requests concurrency without the Fibers + event-loop +\n  non-blocking-driver tax. Stack RoadRunner or Swoole under Rxn if\n  you need in-request concurrency; the framework doesn't change\n  shape for it (and the compile-path opt-ins above start paying\n  for themselves there).\n\n### How it stays this way\n\nEvery shipped optimisation has an A/B run with worktree-based\ncomparison, ranges, and a non-overlapping-range verdict\n(`bench/ab.php`). Negative-result branches stay on origin with\ntheir writeups. Sixteen experiments documented; eleven merged,\nfour documented as negative results, one shipped as\ninfrastructure.\n\nThe principles that produced those eleven wins are written down\nin [`docs/design-philosophy.md`](docs/design-philosophy.md). The\ncumulative scoreboard is in\n[`bench/ab/CONSOLIDATION.md`](bench/ab/CONSOLIDATION.md).\n\n## Quickstart\n\n```bash\ncomposer install\nvendor/bin/phpunit          # 670 tests, 1490 assertions\nbin/rxn help                # CLI subcommands\n```\n\n### Minimal app shape\n\nThe framework's entry point is `App::serve(Router)`. Boot-free —\nno constructor, no Container plumbing, no DB connection during\nrequest setup. A complete app:\n\n```php\n\u003c?php declare(strict_types=1);\nrequire __DIR__ . '/vendor/autoload.php';\n\nuse Rxn\\Framework\\App;\nuse Rxn\\Framework\\Http\\Router;\n\n$router = new Router();\n$router-\u003eget('/products/{id:int}', function (array $params): array {\n    return ['data' =\u003e ['id' =\u003e (int) $params['id']]];\n});\n\nApp::serve($router);\n```\n\nDrop in front of `php -S` for local dev, or `php-fpm` + nginx /\nCaddy / Apache for production. Apps that need DI / config /\nlogging wire those at the composition root and inject them into\nhandlers — the framework itself stays out of that decision.\n\n### Worked example — `examples/quickstart/`\n\n270 LOC across three files exercising the full modern stack:\nDTO binding + attribute validation, BearerAuth, RequestId,\nHealthCheck, typed route constraints (`{id:int}`), RFC 7807\nProblem Details on every failure path. File-backed JSON repo\n(flock-protected) so no database is needed.\n\n```bash\nphp -S 127.0.0.1:9871 -t examples/quickstart/public\n```\n\nSee [`examples/quickstart/README.md`](examples/quickstart/README.md)\nfor the full curl walkthrough.\n\n### CI / cross-framework comparison\n\nCI runs lint + phpunit against PHP 8.2, 8.3, and 8.4\n(`.github/workflows/ci.yml`).\n\nTest counts:\n\n- **Rxn framework:** 670 tests / 1490 assertions (`vendor/bin/phpunit`).\n- **[`davidwyly/rxn-orm`](https://github.com/davidwyly/rxn-orm)**\n  (query builder): 68 tests / 132 assertions, run in that repo.\n- **[`davidwyly/rxn-observe`](https://github.com/davidwyly/rxn-observe)**\n  (OpenTelemetry listener): 9 tests / 26 assertions, run in that repo.\n\nCross-framework comparison harness\n([`bench/compare/`](bench/compare/)) benchmarks Rxn against Slim 4,\na Symfony micro-kernel, and a raw-PHP baseline on identical routes\n— pure PHP, no Docker. The full table is in\n[`bench/ab/CONSOLIDATION.md`](bench/ab/CONSOLIDATION.md); the\nmethodology is in\n[`bench/compare/README.md`](bench/compare/README.md).\n\n## Documentation\n\n| Topic | Where |\n|---|---|\n| Routing (convention + explicit patterns) | [`docs/routing.md`](docs/routing.md) |\n| Dependency injection | [`docs/dependency-injection.md`](docs/dependency-injection.md) |\n| Request binding + validation | [`docs/request-binding.md`](docs/request-binding.md) |\n| Scaffolded CRUD | [`docs/scaffolding.md`](docs/scaffolding.md) |\n| Error handling | [`docs/error-handling.md`](docs/error-handling.md) |\n| Building blocks (Logger, RateLimiter, Scheduler, Auth, Pipeline, Router, Validator, Migration, Chain, query cache, PSR-7 bridge) | [`docs/building-blocks.md`](docs/building-blocks.md) |\n| PSR-7 / PSR-15 interop — bench evidence behind the ingress cost analysis (page predates the PSR-15 native migration; see CHANGELOG) | [`docs/psr-7-interop.md`](docs/psr-7-interop.md) |\n| Plugin architecture — what lives in core vs. as separate Composer packages | [`docs/plugin-architecture.md`](docs/plugin-architecture.md) |\n| Horizons — research directions that could reposition the framework, each sized with cost and ship signal | [`docs/horizons.md`](docs/horizons.md) |\n| CLI (`bin/rxn`) | [`docs/cli.md`](docs/cli.md) |\n| Benchmarks (`bin/bench`) | [`docs/benchmarks.md`](docs/benchmarks.md) |\n| Cross-framework comparison (Slim / Symfony / raw) | [`bench/compare/README.md`](bench/compare/README.md) |\n| Contribution / style guide | [`CONTRIBUTING.md`](CONTRIBUTING.md) |\n\n## Features\n\n`[X]` = implemented and shipped, `[ ]` = on the roadmap.\n\n- [ ] 80%+ unit test code coverage *(currently minimal; see\n      `src/Rxn/**/Tests/` for what's covered)*\n- [X] Gentle learning curve\n   - [X] Installation through Composer\n- [X] Simple workflow with an existing database schema\n   - [X] Code generation\n      - [X] CLI utility to create controllers and models\n            (`bin/rxn make:controller`, `bin/rxn make:record`)\n- [X] Database abstraction\n   - [X] PDO for multiple database support\n   - [X] Support for multiple database connections\n- [X] Security\n   - [X] Prepared statements everywhere — user values flow only\n         through PDO bindings; identifiers come from schema\n         reflection, never from request data\n   - [X] Session cookies set with HttpOnly + SameSite=Lax; Secure\n         flag flips on automatically when the request is HTTPS\n         (including behind a trusted `X-Forwarded-Proto` proxy)\n   - [X] Stack traces never leave the server in production —\n         `Response::getFailure` strips file / line / trace\n         fields when `ENVIRONMENT=production`\n   - [X] Boundary input sanitization — control-character\n         stripping on every GET / POST / header param when\n         `APP_USE_IO_SANITIZATION=true` (JSON is the output\n         format, so HTML-escaping stays in the frontend)\n   - [X] Bearer-token authentication (`Http\\Middleware\\BearerAuth`):\n         the framework extracts + verifies, the app supplies the\n         token → principal resolver — by design, not a gap\n   - [X] Rate limiting (`Utility\\RateLimiter`, file-backed with\n         `flock`)\n- [X] Exception-driven error handling\n   - [X] RFC 7807 Problem Details (`application/problem+json`) is\n         the error shape, period — uncaught exceptions included.\n         Dev-mode file/line/trace carry as `x-rxn-*` extension\n         members\n- [X] URI Routing — **explicit `Http\\Router`**, driven directly\n      or populated from `#[Route]` attributes\n   - [X] Attribute-based routing — `#[Route('GET', '/products/{id:int}')]`\n         + `#[Middleware(Auth::class)]` directly on controller methods\n         via `Rxn\\Framework\\Http\\Attribute\\Scanner`; no separate\n         route table\n   - [X] Explicit pattern routing (`Rxn\\Framework\\Http\\Router`) when\n         attributes don't fit (programmatic groups, route generation\n         from data, etc.)\n   - [X] Typed route constraints (`{id:int}`, `{slug:slug}`,\n         `{id:uuid}`, custom) — a non-matching URL just falls\n         through to 404 instead of bubbling up as a controller-level\n         validation error\n   - [X] Compile-time route conflict detection (`bin/rxn routes:check`;\n         `Http\\Routing\\ConflictDetector`) — flags ambiguous\n         `#[Route]` patterns at CI time using a constraint-type\n         compatibility matrix (e.g. `{id:int}` vs `{slug:slug}`\n         both accept `\"123\"` → conflict; `{id:int}` vs\n         `{name:alpha}` are disjoint). Exit 1 = conflicts found.\n         No PHP framework I know of catches this before\n         first-request resolution\n- [X] Dependency Injection container\n   - [X] Controller method injection\n   - [X] DI autowiring via constructor type hints\n   - [X] Interface → implementation binding\n         (`$container-\u003ebind($abstract, $concrete)`) and factory\n         closures (`$container-\u003ebind($abstract, fn ($c) =\u003e ...)`)\n   - [X] Circular-dependency detection\n- [X] Database / ORM concerns ship as\n      [`davidwyly/rxn-orm`](https://github.com/davidwyly/rxn-orm)\n      (query builder + ActiveRecord). `composer require` to opt in;\n      no plumbing in core.\n- [X] HTTP middleware pipeline — **PSR-15 native end-to-end**\n      (`Http\\Pipeline`; all eight shipped middlewares implement\n      `Psr\\Http\\Server\\MiddlewareInterface`)\n   - [X] Shipped middlewares: BearerAuth, CORS w/ preflight,\n         conditional GET via weak ETags + 304 short-circuit,\n         Idempotency (Stripe-style replay), JSON-body decoding with\n         size caps, Pagination + RFC 8288 Link headers, request-id\n         correlation, **W3C Trace Context** (auto-propagation to\n         outbound `Concurrency\\HttpClient` calls) (`Http\\Middleware\\*`)\n- [X] PSR-7 ingress — `Http\\PsrAdapter::serverRequestFromGlobals()`\n      builds a `ServerRequestInterface` from PHP globals;\n      `App::serve(Router)` is the boot-free one-line front controller\n- [X] **PSR-11** container, **PSR-3** logger, **PSR-14** event\n      dispatcher — every framework interface satisfies the relevant\n      PSR; drops into existing PSR-aware codebases without an\n      adapter\n- [X] Speed and performance\n   - [X] PSR-4 autoloading\n   - [X] File-backed query caching\n   - [X] Object file caching (atomic writes)\n- [X] Event logging (JSON-lines)\n- [X] Scheduler (interval / predicate based)\n- [X] Database migrations (`*.sql` runner)\n- [ ] Mailer *(out of scope; use symfony/mailer or phpmailer)*\n- [X] Request validation *(rule-based `Validator::assert`; see\n      `Rxn\\Framework\\Utility\\Validator`)*\n   - [X] Typed DTO binding with attribute-driven validation —\n         `Http\\Binding\\RequestDto` + `Http\\Binding\\Binder` +\n         `#[Required]`, `#[Min]`, `#[Max]`, `#[Length]`,\n         `#[Pattern]`, `#[InSet]`. All errors surface at once as a\n         7807 `errors` extension member.\n- [X] OpenAPI 3 spec generation from reflected controllers\n      (`bin/rxn openapi`; `Http\\OpenApi\\Generator` + `Discoverer`)\n   - [X] One-line interactive docs via\n         `Http\\OpenApi\\SwaggerUi::html($specUrl)`\n   - [X] DTO parameters emit as `requestBody` with validation\n         attributes mapped to JSON Schema keywords (`#[Min]` →\n         `minimum`, `#[Length]` → `minLength`/`maxLength`,\n         `#[Pattern]` → `pattern`, `#[InSet]` → `enum`) — the same\n         class drives both validation and the spec, so they can't\n         drift\n   - [X] Snapshot-tested contract gate (`bin/rxn openapi:check`;\n         `Codegen\\Snapshot\\OpenApiSnapshot`) — diffs the generated\n         spec against a committed `openapi.snapshot.json` and\n         classifies drift as breaking (operation/parameter/property\n         removals, type changes, tightened constraints, required-\n         toggles) or additive (new operations, new optional fields,\n         loosened constraints). Three exit codes for CI gating:\n         0 clean, 1 additive only or `--allow-breaking` override,\n         2 breaking detected\n   - [X] Cross-language validator twin (`Codegen\\JsValidatorEmitter`)\n         — emits a vanilla ES module that agrees with `Binder::bind`\n         on 0 disagreements / 10K random inputs across four fixture\n         DTOs per CI run\n   - [X] [Polyparity](https://github.com/davidwyly/polyparity)\n         YAML exporter (`Codegen\\PolyparityExporter`) — same DTO\n         drives Rxn's PHP server, the JS twin, AND a polyparity\n         spec consumable by polyparity's TS / Python / future-\n         language siblings\n- [X] In-process HTTP test client + fluent response assertions\n      (`Testing\\TestClient` + `TestResponse`) — no web server, no\n      curl, PHPUnit-integrated failures\n- [ ] Automated API request validation from contracts\n- [ ] Optional, modular plug-ins\n\n## License\n\nRxn is released under the permissive [MIT](https://opensource.org/licenses/MIT) license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidwyly%2Frxn","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavidwyly%2Frxn","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidwyly%2Frxn/lists"}