Download the PHP package ornitophp/framework without Composer
On this page you can find all versions of the php package ornitophp/framework. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download ornitophp/framework
More information about ornitophp/framework
Files in ornitophp/framework
Package framework
Short Description A tiny educational PHP 8.4 framework — front controller, middleware, sessions, query builder. Built to be read.
License MIT
Informations about the package framework
A tiny educational PHP 8.4 framework — built to be read.
What is this?
OrnitoPHP is a zero-magic PHP 8.4 framework built for one purpose: to show you how frameworks work from the inside.
Not a framework that abstracts away the complexity. One that explains it.
Born from sprinf, rebuilt from scratch. Same instincts, modern standards.
Installation
Quick start
That's it. One front controller. No magic bootstrap, no hidden autoloading chains, no framework-specific IDE plugins required.
What's inside
| Component | Description |
|---|---|
| Router | Declarative route table, dynamic segments {name}, middleware per route |
| Middleware Pipeline | Global + route-level, composable onion-style |
| Request / Response | Superglobals quarantined into one value object; controllers return Response, kernel sends once |
| Session | Native PHP sessions, CSRF token generation, flash messages |
| Validation | Rule-string syntax: required\|email\|min:8 |
| Query Builder | Prepared statements, validated identifiers, where/whereInSub/orderBy/limit/offset + toSql()/getBindings() teaching extras + explain() (see how MySQL runs the query) |
| Database Commands | migrate, seed, db:fresh, create:model, create:controller, create:relation, explain |
| Error Renderer | Branded HTML pages + JSON negotiation — same endpoint for browsers and APIs |
| Rate Limiter | File-backed, zero dependencies |
| JWT | Issue, verify, middleware — powered by firebase/php-jwt |
| CSS Presets | Dark theme, Bootstrap CDN, Tailwind CDN — one .env line switches |
See your SQL
The query builder never hides what it runs. Every builder can show the exact SQL it will execute:
toPreviewSql() renders values inline for logs and examples — display only, never executed. This is the "no hidden queries" promise made visible: what you read is exactly what runs. The full builder API (including toCountSql() and whereInSub() for N:M lookups) is in docs/database.md.
Security architecture
Security isn't a feature you bolt on. It's in the bones:
- Prepared statements everywhere — not a single string interpolation in the codebase
- SQL identifiers validated with regex before reaching the database (defense in depth)
- Superglobals quarantined — only
Request::capture()touches$_GET,$_POST,$_COOKIE - CSRF with timing-safe validation (
hash_equals), exempt for/apisegments - Open-redirect prevention —
AuthController::safeTarget()blocks//evil.comand/\\evil.com - Rate limiting — three layered buckets (account|ip, ip, account) so rotating emails or IPs cannot dodge the cap; file-backed, no extra services
- Timing-equalized login —
password_verify()always runs (dummy hash for unknown accounts), so response time cannot reveal registered emails - Registration throttled per IP — the taken-email answer is observable by
necessity; the probe volume is not (
LoginThrottle::fromBuckets())
SQL injection is not "prevented." It's structurally impossible.
The Response contract
Controllers return Response objects. The kernel calls send() exactly once. No echo. No die(). No "headers already sent."
This single decision eliminates an entire class of bugs. It's the kind of architectural choice that separates "I use PHP" from "I understand software design."
Dual error negotiation
The ErrorRenderer decides automatically between HTML and JSON:
Same endpoint. Same logic. Zero duplication. That's content negotiation — not a middleware you configure, but a renderer that reads the request.
Zero-dependency philosophy
Runtime: 2 packages. vlucas/phpdotenv + firebase/php-jwt.
Everything else is written from scratch:
- Router → your code, not nikic/php-parser
- Pipeline → your code, not symfony/http-kernel
- Request/Response → your code, not psr/http-message
- Session → your code, not symfony/session
- Validator → your code, not respect/validation
- RateLimiter → your code, not symfony/rate-limiter
Not because reinventing wheels is noble. Because understanding the blocks matters more than using them.
Console commands
Test suite
208 tests. 630 assertions. The test suite is the living documentation — every edge case is tested, every behavior is documented. Read the tests to understand the framework.
Design rules
- Prepared statements only — values are bound as parameters; identifiers are validated against a strict regex as defense in depth.
- Controllers return
Response— the kernel callssend()exactly once; no controller ever echoes. - Single front controller — every request enters through
public/index.php. - Middleware pipeline — route middleware wraps the handler in an onion-style pipeline.
- Superglobals are quarantined — only
Request::capture()may read them.
Use the starter
If you want a working app with auth, views, migrations, and CSS presets out of the box:
See ornitophp/starter for details.
License
MIT