arcanum-org / framework
A robust CQRS framework for building powerful applications.
Requires
- php: >=8.4
- monolog/monolog: ^3.4
- psr/clock: ^1.0
- psr/container: ^2.0
- psr/event-dispatcher: ^1.0
- psr/http-message: ^2.0
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
- psr/simple-cache: ^3.0
- symfony/finder: ^6.3 || ^7.0
- vlucas/phpdotenv: ^5.5
- voku/portable-ascii: ^2.0
Requires (Dev)
- php-coveralls/php-coveralls: ^2.9
- phpmetrics/phpmetrics: ^2.9
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^13.0
- squizlabs/php_codesniffer: ^3.7
This package is auto-updated.
Last update: 2026-04-13 02:04:10 UTC
README
Arcanum
A CQRS PHP framework that stays readable and manageable at scale.
Why Arcanum
MVC Doesn't Scale
MVC groups by what things are: all models go in one folder, all views in another, and all controllers in a third. At 20 endpoints that's manageable. At 200, your Models/ directory is a junk drawer and your controllers are 400-line god classes doing six different things.
CQRS groups by what things do. The handler, template, SQL, validation, and specialized middleware for "place an order" all live together in app/Domain/Shop/Command/PlaceOrder/. The directory is the architecture diagram. Things that change together live together.
SQL Is Code
Every framework I've ever worked with does two things: 1. Abstracts away SQL with an ORM or query builder. 2. Forces you to write SQL in strings.
In Arcanum, SQL files are first-class citizens. There is no ORM. There is no query builder. That's because SQL is already a perfectly good language. SQL is code. You can read it. You can EXPLAIN it. The framework generates type-safe methods from your SQL files, so you get autocompletion and static analysis without giving up the power and readability of raw SQL.
It's hard to explain this important point, because we've been trained to think of SQL as a string, and we must write model methods to build those strings, send them off to the database, and parse the results. But when SQL is a first-class citizen, you write it once, in its own file, and the framework generates a method that is that SQL. The method signature is generated from the SQL parameters and result columns. The mental model is one-to-one with reality.
Stop Building Two Applications at the Same Time
Most modern web apps are really two apps: a backend API and a JavaScript frontend, maintained in parallel, sometimes deployed separately, and constantly drifting apart.
Arcanum takes a different path. Hypermedia as the Engine of Application State, HATEOAS. The server renders the HTML. htmx handles the dynamic bits. Arcanum ships with first-class support for htmx 4, so you can build a dynamic, interactive app without writing a line of JavaScript. The server is the single source of truth for your app's behavior and state. The DOM is just a view layer that reflects that state.
The framework's Htmx package handles the backend ceremony so you don't have to:
- You write normal query handlers. The framework inspects htmx headers, decides how much of the page to render, and returns the right fragment. Your handler code doesn't change.
- Domain events propagate to the DOM. A command handler dispatches an event. The framework projects it as an
HX-Triggerresponse header. Listening elements refresh themselves. Zero JavaScript coordination. - CSRF, auth redirects, cache safety — all wired up by middleware.
Don't want to use htmx? Sure, but you're missing out on the most seamless way to build a dynamic app. The framework doesn't force you to use it, but it's the path of least resistance for a reason.
Humans Don't Read Documentation, but AI Agents Do
The AI genie is out of the bottle. Most developers use coding assistants, and AI agents are quickly becoming both the authors and the consumers of your code. Arcanum is designed for this reality.
AI writes better Arcanum code because there's one idiomatic way to do it. When an AI agent scaffolds a new feature, the conventions guide it toward the right structure, the right naming, the right location — automatically.
AI consumes Arcanum apps natively. Humans want to see HTML. Agents want to see markdown, JSON, CSV, or plain text. When you build an endpoint in Arcanum, it can server all of those formats without extra work on your part. The same handler serves .html, .json, .md, .csv, and .txt — just change the URL extension. An agent doesn't need to parse HTML or reverse-engineer an API; it asks for /products.md instead of /products.html and the framework renders Markdown with zero effort on your part.
Your Handler Is Five Lines
Arcanum absorbs the ceremony. Validation, authorization, CSRF, content negotiation, error rendering, response formatting — all handled by attributes, middleware, and conventions. What's left is the thing that matters:
final class PlaceOrderHandler { public function __construct(private readonly Database $db) {} public function __invoke(PlaceOrder $command): void { $this->db->model->insertOrder(product: $command->product, quantity: $command->quantity); } }
First Principles
These are the foundations that everything in Arcanum is built upon.
-
Don't abstract away the thing you're actually doing. SQL, HTTP status codes, URL structure — let the real thing be the thing. Abstractions that hide the underlying reality eventually leak, and then you have two mental models instead of one.
-
Colocation by concern, not by layer. The DTO, handler, template, SQL, validation, and middleware for an operation all live in the same directory. When you need to change how something works, you open one folder, not five.
-
Strong defaults, not walls. Conventions make the common case effortless — one class per operation, handler discovered by name, template co-located with the DTO. But when you need to break the pattern, the framework gets out of your way. Opinionated doesn't mean inflexible.
-
Build for two audiences. Every design decision should work for developers writing code and agents consuming the deployed app. Opinionated conventions make AI write better code. Multi-format responses, structured errors, and semantic HTTP make your app machine-readable without a separate integration layer.
What It Looks Like
A query that serves GET /shop/products.html (and .json, .csv, .md, .txt):
// app/Domain/Shop/Query/Products.php — the DTO final class Products { public function __construct( public readonly string $category = '', ) {} } // app/Domain/Shop/Query/ProductsHandler.php — the handler final class ProductsHandler { public function __construct(private readonly Database $db) {} public function __invoke(Products $query): array { return ['products' => $this->db->model->productsByCategory(category: $query->category)]; } }
-- app/Domain/Shop/Model/ProductsByCategory.sql -- @param category string -- @cast price float SELECT id, name, price FROM products WHERE (:category = '' OR category = :category) ORDER BY name
A dev doesn't need to write any route definitions, controller methods, or model classes. An AI agent doesn't need to be trained on how to structure the code or where to put things. The namespace is the URL. The SQL is the method.
Quick Start
git clone https://github.com/arcanum-org/arcanum.git myapp
cd myapp
composer install
php bin/arcanum migrate
php -S localhost:8000 -t public
Visit localhost:8000 — you'll see the welcome page with a live guestbook demo, diagnostics, and a CQRS walkthrough.
See the starter app README for the full setup guide.
What's in the Box
23 packages, each with its own README.
Foundation
| Package | What it does |
|---|---|
| Cabinet | PSR-11 dependency injection container |
| Codex | Reflection-based auto-wiring and class resolution |
| Toolkit | String utilities, cryptography primitives, random generation |
| Parchment | Filesystem layer |
| Gather | Collections for storing and retrieving key-value data |
Data Flow
| Package | What it does |
|---|---|
| Pipeline | Send data through a pipeline where the output of one stage becomes input of the next |
| Continuum | Middleware pattern — each stage calls $next to continue |
| Conveyor | Command bus — dispatches DTOs to handlers with before/after middleware |
| River | PSR-7 stream implementations |
| Sequence | Lazy and eager ordered iterable objects |
HTTP and CLI
| Package | What it does |
|---|---|
| Hyper | PSR-7 messages, PSR-15 handling, response renderers, etc. |
| Rune | CLI tool with built-in commands and support for custom commands. |
| Atlas | Convention-based CQRS router for HTTP and CLI |
| Ignition | Bootstrap kernels: HyperKernel, RuneKernel. This is what makes the framework work. |
| Shodo | Template engine, formatters (HTML, JSON, CSV, Markdown, plain text), compiler directives |
| Htmx | First-class htmx 4 support: rendering modes, event projection, CSRF, auth redirects |
Persistence and State
| Package | What it does |
|---|---|
| Forge | SQL-as-methods, connection management, model generation, migrations |
| Vault | PSR-16 caching: File, Array, Null, APCu, Redis drivers |
| Session | HTTP sessions, CSRF middleware, configurable drivers |
Identity and Access
| Package | What it does |
|---|---|
| Auth | Authentication guards, authorization attributes, CLI auth |
Validation, Observability, Throttling
| Package | What it does |
|---|---|
| Validation | Attribute-based DTO validation with 11 built-in rules |
| Glitch | Error handling, HttpException, StatusCode enum, ArcanumException |
| Quill | Multi-channel PSR-3 logging over Monolog |
| Echo | PSR-14 event dispatcher |
| Throttle | Rate limiting: token bucket and sliding window strategies |
| Hourglass | Clock interface (PSR-20), FrozenClock, Stopwatch, Interval |
| Testing | Test harness: TestKernel, Factory, HTTP and CLI test surfaces |
Documentation
| Resource | What it covers |
|---|---|
| COMPENDIUM.md | The guided tour — what Arcanum is, how the pieces fit, conventions, CLI surface |
| Starter App | Getting started, directory structure, examples |
| PLAN.md | What's been built, what's coming, design decisions |
| Package READMEs | Deep dives — linked in the table above |
Status
Arcanum is under active development, approaching a 1.0-alpha release.
- PHP 8.4+ required
- PHPStan level 9 — strict static analysis across the entire codebase
- 2700+ tests with strict coverage — every test class declares exactly what it covers
- AI-assisted, not vibe-coded. AI agents have contributed significantly to this codebase, but every line passes the same static analysis, test coverage, and code review standards. Structure and rigor first.
composer check # cs-fix, cs-check, phpstan, phpunit — all at once