Humblee

A humble PHP framework & CMS

System Architecture

Directory overview

Humblee separates concerns across four top-level directories. Understanding which directory does what is the key to working with the framework effectively.

/
├── humblee/        # Framework core — do not modify
├── application/    # Your application — extend everything here
├── public/         # Web root — the only directory the browser can reach
└── storage/        # Uploaded media files — not web-accessible

/humblee/ — the framework core

This is the CMS engine. It handles routing, the admin interface, user authentication, content management, the media library, and all the infrastructure that makes the rest of the application work. You should not need to modify files here during normal development.

The PHP source lives in humblee/src/ and is autoloaded under the Humblee\ namespace (PSR-4). The framework's view templates live in humblee/views/.

/application/ — your application layer

This is where you build. The application/ directory is autoloaded under the App\ namespace and its structure mirrors the framework:

application/
├── Controller/     # App\Controller\ — your custom controllers
├── Model/          # App\Model\ — your custom models
└── views/          # Your view templates (PHP includes — not autoloaded)

When the framework needs a controller for a given route, it checks the application namespace first. This means you can override framework behavior by creating a class with the same name in App\Controller\. For routes that are entirely your own, you simply add a new controller here.

/public/ — the web root

Apache (or NGINX) points to this directory. It is the only directory the browser can reach directly. Everything else — the framework, your application code, the database configuration, uploaded files — sits above the web root and is not directly accessible.

Static assets from both the framework and your application are published here:

public/
├── index.php               # Entry point — bootstraps humblee/init.php
├── humblee/                # CMS admin assets (CSS, JS, images)
│   ├── css/admin/
│   └── js/admin/
├── application/            # Your app's static assets
└── node_modules/           # Bulma CSS (served directly, not built)

The .htaccess file at the project root redirects all traffic to public/, making it behave as the effective site root even though the actual application files live one level up.

/storage/ — media files

Uploaded files are stored here. This directory is not inside public/, so files cannot be accessed by URL directly. When a file is requested through the CMS, it is streamed through a PHP controller (/media/{id}/) which enforces access control and handles decryption if the file is encrypted at rest.


Routing

All requests are dispatched by Humblee\Middleware\Router after passing through the middleware pipeline. The first URI segment determines which controller handles the request; the second segment maps to the method called on it.

For a complete walkthrough of the middleware pipeline and the full routing table, see Routing & Middleware.


How pages are displayed

When a public URL is requested, the flow is:

  1. Humblee\Controller\Template matches the URI slug to a page record in the database
  2. It loads the page's content blocks (each page can have multiple named content areas)
  3. It renders the page using your application's view template in application/views/
  4. The view template calls Draw::content($content, 'block_name') to output each content area

Content blocks can contain plain text, HTML, or Markdown. The Draw::content() helper handles rendering. If a block contains Markdown and the Parsedown library is installed, it is automatically converted to HTML before output.


Autoloading

Composer PSR-4 maps two namespaces:

Namespace Directory
Humblee\ humblee/src/
App\ application/

Classes in both namespaces are available everywhere without manual require statements. The autoloader is bootstrapped in humblee/init.php.


Key conventions

Database access

All queries use Idiorm, a lightweight ORM. Always call it with the \ORM:: prefix. Table names are defined as constants in the environment configuration (e.g. _table_pages, _table_users, _table_content) — never hardcode table names in your code.

Authentication and roles

Sessions are managed by the framework. Call Core::auth($role) to check whether the current user has a given role before executing protected logic. Roles are stored as integers and cached in the session on first check. See Users & Roles for the full role list and patterns.

CSRF protection

Every state-changing POST must include a signed HMAC token pair. Generate the fields with Crypto::get_hmac_pair() in your view and validate with $this->require_hmac() in your XHR controller before reading any POST data. See Security for the complete pattern and check ordering.

Frontend tools

The admin interface embeds modern JavaScript SPAs (built with Svelte and Vite) for complex interactions like the media manager. These live in frontend/apps/, build to public/humblee/js/admin/, and are loaded by the relevant controller action via $this->extra_head_code. The framework passes runtime configuration to the JS layer through a window.__CONFIG__ global injected in the view template. See Frontend for the full build pipeline and injection pattern.


The init.php bootstrap

humblee/init.php is the single entry point for every request. In order, it:

  1. Loads the Composer autoloader
  2. Loads the environment configuration
  3. Establishes the database connection
  4. Starts the PHP session and sets the timezone
  5. Calls Humblee\Middleware\Kernel::boot(), which runs the middleware pipeline:
    • Normalizes request input (Package::build())
    • Restores sessions from remember-me cookies (Auth::handle())
    • Runs any application middleware in application/middleware/
    • Dispatches to the matched controller (Router::handle())

Nothing in the framework uses a service container or dependency injection. Dependencies are resolved directly within each class. This is a deliberate trade-off in favor of simplicity over abstraction.

See Routing & Middleware for the full pipeline detail.