High-performance, opinionated PHP 8 web framework — O(1) routing, parallel async MySQL, type-safe asset bridge, Twig templating, React islands.
- Quickstart
- Why Garnet
- Feature highlights
- Installation
- What you get in an app
- Architecture
- Documentation
- Known limitations
- Development
- Security
- License
# 1. Get the framework. `composer install` also wires the frontend
# toolchain (npm + node_modules) via the bundled `garnet setup` hook.
git clone https://github.com/PHPCraftdream/garnet-framework
cd garnet-framework && composer install
# 2. Scaffold an app from the bundled template. It's born ready —
# vendor, node deps, Playwright and a working build, zero manual config.
php bin/garnet app:create MyApp
cd MyApp
# 3. Build assets and run it (port 8001).
php garnet build
php garnet serveThat's the whole quickstart — no separate frontend install step. Re-run the
installer any time with php bin/garnet setup. The rest of this README explains
what you just installed and where to go next.
Garnet started as the engine of a production booking platform and was extracted into a standalone framework. It optimises for three things, in this order:
-
Performance — every architectural decision is weighed against its cost. The router is O(1) (path used as a hash-map key, no regex on dispatch). MySQL queries can fan out in parallel (
mysqli_poll, total latency =max(query_time), not the sum). Assets are pre-hashed and referenced through codegen-typed PHP classes. -
Developer experience — repetitive work is automated.
php garnet preparegenerates type-safe*Gen.phpaccessors for every asset.IEntityConfigdrives generic grid/form controllers via the Template Method pattern. The CLI is a singlephp garnet <command>surface for everything: build, serve, migrate, deploy, cache. -
Explicit control — no auto-discovery magic. Routes are PHP arrays. Bundle bootstrap is a method call. Dependencies are listed in
composer.json, not divined at runtime.
If you're building a CRUD-heavy app with custom business logic and you
want fewer dependencies, fewer mystery files, and a tight php garnet
loop, this might fit.
- O(1) router — direct hash-map dispatch with
/path/~methodsyntax. - Parallel async MySQL — multiple queries via
mysqli_poll; total wait time bounded by the slowest query. - Type-safe asset bridge —
*Gen.phpclasses give IDE-autocompletable, cache-busted URLs for every JS/CSS chunk. - Bundle architecture — self-contained modules (auth, CRUD, i18n, uploads, comments, support, IM, notifications) that auto-register templates, assets and services.
IEntityConfigCRUD — declarative entity specs drive generic grid/form controllers with validation, field types, role gates.- Passwordless auth — built-in email magic-link state machine with CSRF, brute-force mitigation, single-page UX.
- Twig templating — strict separation of markup (Twig) from logic (PHP); auto-escaping by default.
- React island frontend — lazy-loaded React components in server-rendered pages; ErrorBoundary-wrapped; no full SPA required.
- CLI tooling — single
php garnetentry point for build, serve, deploy, migrations, codegen.
composer require phpcraftdream/garnet-frameworkThis is how real production apps consume Garnet — a normal versioned
Composer dependency in vendor/. php bin/garnet app:create (from within
your app) scaffolds this wiring for you.
The quickstart above (git clone + app:create run inside the framework
checkout) is a different, dev-only path: it's for contributing to the
framework itself, or trying Garnet without setting up a real project. See
"Which install path do I want?"
for the full comparison, including which CI job covers each path.
- PHP 8.1+
- Extensions:
mbstring,json,pdo,mysqli,intl - MySQL 8.0+ or MariaDB 10.6+ (optional — only if you use the DB)
- Node.js 18+ (for the frontend build)
- Composer 2.x
Once app:create finishes you have a working app that:
- Boots
php garnet serveonhttp://localhost:8001. - Has passwordless email auth and a wired admin console.
- Has CRUD scaffolding ready to plug into via
IEntityConfig. - Has a
php garnet buildfrontend pipeline (rspack) that watches and rebuilds on save.
MyApp/
├── garnet # local CLI wrapper
├── composer.json
├── package.json
├── Application.php # main app class
├── Common/ # shared services
├── Foreground/ # public-facing controllers + Twig
├── Dashboard/ # admin panel (optional)
├── Front/ # business-specific React + CSS
├── Migrations/ # DB schema migrations
└── WorkDir/ # runtime: config, caches, logs (gitignored)
See docs/quickstart.md for the step-by-step.
A request hits public/index.php → IoRunWeb::run builds the global
request state → middlewares run in order → the O(1) router dispatches
to a controller method (get__foo, post__bar) → the controller
prepares a typed array and hands it to Twig → Twig renders the
response, which is emitted by Emitter. CLI commands take the same
shape via IoRunConsole and php garnet. DbPool wraps mysqli with
async support, so any controller can fan out reads concurrently. Bundles
register themselves at boot, contributing routes, services, templates
and asset roots without auto-discovery.
For the full story see docs/architecture.md.
docs/quickstart.md— scaffold a new appdocs/dev-workflow.md— develop framework + app togetherdocs/architecture.md— layers, request lifecycle, async DBdocs/bundle.md— writing your own bundledocs/cli.md— every CLI commanddocs/database.md— DbPool, DbTable, async patternsdocs/frontend.md— React islands, codegen, asset bridgedocs/i18n.md— translation pipelinedocs/deploy.md— production deploy viagarnet bundle/deploy:diffdocs/testing.md— kahlan specs, writing testsdocs/e2e-testing.md— Playwright end-to-end testsdocs/core.md— kernel-level primitivesdocs/io.md— HTTP, CLI dispatch, Twig, config, caching, mailerdocs/ssh.md— SSH connection and remote commandsdocs/known-issues.md— sharp edgesdocs/cookbook/— short copy-friendly recipes (routes, islands, CRUD, parallel queries, emails, uploads, i18n, validation, CLI commands, bundles)AGENTS.md— onboarding for AI agents / new devs
Garnet evolved out of a booking platform, and a few framework primitives still carry domain names from that lineage. They work fine, but they look booking-shaped from the outside:
FwBalanceLedgerentry types are stored as string valuesbooking_invoice,booking_payment,booking_refund. v1.0 will rename them to generic equivalents (tx_invoice,tx_payment,tx_refund) with a migration path.FwAppSettings::cancellationPenaltyPercentis a booking-domain setting in the framework's settings module; v1.0 will move it to the application layer.- A handful of CRUD-action i18n keys reference
booking_*operations (e.g.AdminAction_booking_cancel). They'll be generalised the same way.
Until v1.0 the framework is the most natural fit for booking / scheduling / appointment-style apps, but it works for plain CRUD or content apps too — these booking-shaped pieces are isolated to a few classes you can ignore if you don't need them.
git clone https://github.com/PHPCraftdream/garnet-framework
cd garnet-framework
composer install
composer ci # phpstan + cs:check + kahlanSee CONTRIBUTING.md for the contribution flow.
If you discover a security issue, please do not open a public
issue. See SECURITY.md for the responsible-disclosure
contact and policy.
Garnet Framework is dual-licensed under MIT or Apache-2.0, at your option. Pick whichever fits your project — both texts ship with the repo:
Unless you explicitly state otherwise, any contribution submitted for inclusion shall be dual-licensed as above, without any additional terms or conditions.
© PHPCraftdream and contributors.