A humble PHP framework & CMS
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 coreThis 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 layerThis 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 rootApache (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 filesUploaded 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.
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.
When a public URL is requested, the flow is:
Humblee\Controller\Template matches the URI slug to a page record in the databaseapplication/views/Draw::content($content, 'block_name') to output each content areaContent 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.
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.
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.
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.
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.
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.
humblee/init.php is the single entry point for every request. In order, it:
Humblee\Middleware\Kernel::boot(), which runs the middleware pipeline:Package::build())Auth::handle())application/middleware/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.