No Composer. No vendor folder. No supply-chain anxiety. Every line of code is yours to read, understand, and trust.
Built exclusively for PostgreSQL. Schema introspection, Active Record ORM, and auto-generated models straight from your database.
Strict types, named arguments, match expressions — clean, modern PHP without legacy baggage or abstraction soup.
A complete MVC framework in ~3,200 lines. Every feature is built-in, battle-tested, and dependency-free.
Map URLs to Controller@method with a clean array syntax. No YAML, no annotations.
Capture URL segments with {param} patterns, auto-injected as method arguments.
Nest GET, POST, PUT, PATCH, DELETE handlers under the same URL. Automatic 405 responses.
Stack auth, roles, and custom middleware on routes or entire controllers. Chainable and composable.
Unified access to POST, GET, JSON body, headers, and method detection in one clean object.
Simple set/get/destroy API with built-in flash message support and auth user helpers.
Purpose-built for PostgreSQL. Schema support, information_schema introspection, and PDO prepared statements.
Models are table rows. Find, modify, save, delete — with zero configuration and auto-detected primary keys.
Chain where(), join(), orderBy(), limit(), groupBy(), having() — returns hydrated model instances.
Join models with INNER, LEFT, or RIGHT joins. Columns auto-prefixed, nested objects hydrated automatically.
Built-in Count, Sum, Avg, Max, Min — all parameterized and SQL-injection safe.
Run php gen.php all to generate models from your schema. Type-mapped, annotated, ready to use.
Master layout with {{content}} placeholder. Render wrapped or standalone with one method call.
All view data auto-escaped via htmlspecialchars. Use rawData only when you explicitly need unescaped HTML.
One-time session messages rendered automatically via placeholder. Show once, then gone.
Return JSON from any controller with renderJson(). Perfect for building APIs alongside your views.
Foreach over view data safely. Each item auto-wrapped with XSS protection. Works with empty checks and null coalescing.
No custom syntax to learn. Templates are .leap.php files with native PHP. Your IDE just works.
Controllers auto-receive db, request, session, and view via constructor. No service containers needed.
Custom autoloader maps namespaces to directories. No Composer, yet fully organized by namespace.
Interactive error pages with stack traces, source code, request data, and a live debug console.
Execute PHP in context of the error. Inspect variables, test fixes, iterate fast — right in the browser.
Exceptions logged to storage/logs/error.log with full context. Syntax errors caught and highlighted.
.env file support for database credentials and app settings. No framework-specific config formats.
Built-in RFC 6455 WebSocket server. Real-time features without any external dependencies or services.
Alpine + nginx + PHP-FPM container included. One command to spin up a full development environment.
Hit the URL, fill in database credentials, and the framework bootstraps itself. Models auto-generated on setup.
Built-in authentication with login, logout, user ID helpers, and middleware guards for protected routes.
~20 files, ~3,200 lines. Boots in milliseconds. No abstraction layers to wade through.
declare(strict_types=1) everywhere. Type safety baked in from day one. Catch bugs at compile time.
Routes map to controllers. Controllers talk to models. That's it.
// conf/router.config.php — the entire routing file $routes = [ "/" => "HomeController@welcome", "/users" => ["UsersController@index", "auth"], "/users/edit/{id}" => [ "GET" => ["UsersController@edit", "auth"], "POST" => ["UsersController@update", "auth"], ], ];
// app/UsersController.php — a complete controller class UsersController extends LeapController { public function index() { $this->view->data = Users::Query()->get(); $this->view->render('users/index'); } public function edit($id) { $user = Users::Query()->where("id = :id", [":id" => $id])->first(); $this->view->data = $user; $this->view->render('users/edit'); } }
// Find, modify, save — that's the whole ORM $user = Users::Query()->where("username = :u", [":u" => "admin"])->first(); $user->name = "New Name"; $user->save(); // Create a new record $user = new Users(); $user->loadFromRequest($this->request); $user->save(); // Delete Users::delete($id);
<!-- views/users/index.leap.php — auto-escaped by default --> <table class="table"> <?php foreach ($this->data as $user) : ?> <tr> <td><?= $user->username ?></td> <td><?= $user->name ?></td> <td><a href="/users/edit/<?= $user->id ?>">Edit</a></td> </tr> <?php endforeach; ?> </table>
# Models are generated from your database. Not hand-written. $ php gen.php all Generated: Users.model.php Generated: Orders.model.php Generated: Products.model.php # Schema changes? Regenerate. Done. $ php gen.php users