A humble PHP framework & CMS
Every HTTP request passes through a fixed middleware pipeline before reaching a controller. Understanding this pipeline is essential for adding custom behavior, securing endpoints, and debugging unexpected responses.
Browser → Apache → public/index.php
→ humblee/init.php
Config, autoload, database, session_start, timezone
→ Kernel::boot()
1. Package::build() — Normalize all request input
2. Auth::handle() — Restore session from remember-me cookie if needed
3. App\Middleware\*::handle() — Auto-discovered files in application/middleware/
4. Router::handle() — Dispatch to the correct controller and method
humblee/init.php bootstraps the application in a fixed order. There is no service container — each step runs synchronously and deterministically.
Humblee\Middleware\Package consolidates all incoming request data into a single object available everywhere as Package::current(). It reads $_GET, $_POST, PUT, DELETE, and PATCH bodies — including JSON-encoded bodies sent via php://input — and presents them through a consistent interface.
use Humblee\Middleware\Package;
$package = Package::current();
$id = $package->get('id'); // null if missing
$id = $package->get('id', 0); // with a default value
$all = $package->all(); // all parsed data as a plain array
$method = $package->method(); // 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
$present = $package->has('field'); // bool
Do not read $_POST, $_GET, or php://input directly. Package handles all HTTP verbs and content types uniformly, including REST endpoints that send JSON request bodies instead of form data.
Humblee\Middleware\Auth checks for a remember-me cookie. If one is present and valid, it re-populates the user's session before any controller or middleware runs. This means Core::auth() correctly reflects the logged-in state throughout the rest of the pipeline — even if the PHP session itself has expired.
After the framework's own setup, the Kernel auto-discovers every PHP file in application/middleware/. Each file is loaded with require_once. Any class that implements Humblee\Middleware\Contract is instantiated and its handle() method called, in filesystem order.
<?php
declare(strict_types=1);
namespace App\Middleware;
use Humblee\Middleware\Contract;
use Humblee\Middleware\Package;
class RateLimit implements Contract
{
public function handle(Package $package): void
{
// Runs on every request, before routing.
// Call exit or header('Location: ...') to short-circuit the pipeline.
}
}
Drop the file into application/middleware/ — no registration step is needed. The Kernel finds it by glob. One class per file; the class and file name should match by convention.
Middleware is the right place for cross-cutting concerns that apply to every request: logging, rate limiting, maintenance mode, A/B test cookie assignment, geolocation, and so on. Anything that should run before routing and without knowledge of which controller will handle the request.
The router reads the first URI segment and dispatches to the matching controller:
| URI prefix | Controller | Namespace | Purpose |
|---|---|---|---|
request/ |
Request |
App\Controller |
Custom application AJAX endpoints |
admin/ |
Admin |
Humblee\Controller |
CMS admin panel pages |
core-request/ |
Request |
Humblee\Controller |
Core CMS AJAX (framework-internal) |
user/ |
User |
Humblee\Controller |
Login, logout, registration, profile |
media/ |
Media |
Humblee\Controller |
File serving and on-the-fly decryption |
| (any other) | Template |
Humblee\Controller |
Public-facing CMS pages |
The second URI segment maps to the method called on the matched controller. /admin/pages instantiates Humblee\Controller\Admin and calls its pages() method. /user/login calls User::login().
For public pages (the Template catch-all), there is no method dispatch. The entire URI is treated as a page slug and resolved against the database. This is how CMS-managed pages work — you create them in the admin interface and they become routable without touching PHP.
core-request/)The framework's internal AJAX endpoint (core-request/) uses the third URI segment to dispatch to a group of static handler methods in humblee/src/Controller/Requests/:
| Third segment | Handler class | Example |
|---|---|---|
content |
Requests\Content |
POST /core-request/content/save |
media_files |
Requests\MediaFiles |
POST /core-request/media_files/upload |
pages |
Requests\Pages |
POST /core-request/pages/update |
templates |
Requests\Templates |
POST /core-request/templates/save |
users |
Requests\Users |
POST /core-request/users/create |
blocks |
Requests\Blocks |
POST /core-request/blocks/save |
personalization |
Requests\Personalization |
POST /core-request/personalization/save |
These handlers are framework-internal. For your own AJAX endpoints, use the request/ prefix — handled by App\Controller\Request, which you own. See Controllers.
When i18n_segments is configured, the router strips the language prefix before matching a page slug, so /fr/about and /about resolve to the same database record but can serve different content. See Personalization & i18n for the full details.