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.


How routing works

All requests enter through public/index.php, which loads humblee/init.php. The framework reads the URI, splits it into segments, and routes based on the first segment:

First URI segment Controller
admin/ Humblee\Controller\Admin
user/ Humblee\Controller\User
media/ Humblee\Controller\Media
core-request/ Humblee\Controller\Request (AJAX, framework-level)
request/ App\Controller\Request (AJAX, application-level)
(anything else) Humblee\Controller\Template (public-facing pages)

The second URI segment maps to the method called on that controller. So /admin/pages instantiates Humblee\Controller\Admin and calls its pages() method.

For public-facing pages (anything that does not match a reserved prefix), Humblee\Controller\Template looks up the slug in the database and renders the matching page content. This is how CMS-managed pages work — you create them in the admin interface and they are automatically routable without touching any PHP.

AJAX requests

The framework provides two AJAX endpoints:

  • core-request/ — reserved for framework-level actions (media uploads, admin UI operations, etc.). Handled by Humblee\Controller\Request.
  • request/ — your application's AJAX endpoint. Handled by App\Controller\Request, which you create in application/Controller/Request.php. This class extends Humblee\Controller\Xhr, which provides require_hmac() and require_role() helpers for securing individual actions.

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.

CSRF protection

Every POST request must include an HMAC token pair. Generate the fields with Crypto::get_hmac_pair() in your view and validate with Crypto::check_hmac_pair() in your controller's __construct() before processing any POST data. Controllers that handle forms should do this check unconditionally.

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.


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. Reads the URI and determines the controller
  5. Instantiates the controller and calls the appropriate method
  6. Renders the response

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.