Humblee

A humble PHP framework & CMS

Routing & Middleware

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.


Request Lifecycle

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.


Stage 1: Package — Normalizing Request Input

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.


Stage 2: Auth — Session Restoration

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.


Stage 3: Application Middleware

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.

Adding custom middleware

<?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.


Stage 4: Router — Dispatching to Controllers

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.


AJAX Sub-routing (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.


i18n URL Handling

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.