Architecture
Architecture
Celena is a hand-written PHP CMS with no third-party frameworks (no Laravel/Symfony) and no Composer dependencies in the core. All code is plain PHP 8.3+ with mandatory declare(strict_types=1) and strict typing.
If you're just starting out, read this page, then Project structure and Request lifecycle. To build extensions, move on to What is a plugin.
Entry points
| File | Purpose |
|---|---|
index.php | Site front — all public requests. |
admin.php | Admin panel (thin loader, hands control to Kernel). |
install.php | Self-deleting install wizard (after install an install.lock appears). |
bin/celena | CLI: migrations, plugins, queue, cache. |
Every request goes through the core Celena\Core\Kernel:
HTTP request → index.php → core/bootstrap.php → Kernel::boot()
→ Autoloader → Container → Config → services
→ Router::dispatch(Request) → Middleware → Controller → Response → send()
Layers
The code is split into four layers with a strict dependency direction:
| Layer | Folder | Depends on |
|---|---|---|
| Core (domain-agnostic) | core/ | only core/* |
| Built-in CMS modules | modules/ | core/* |
| Plugins (extensions) | plugins/ | core/*, optionally other plugins via manifest |
| Themes (appearance) | templates/ | template engine tags only |
The core knows nothing about news, shops or forms — it provides the tools (router, DB, template engine, hooks), while concrete functionality comes from modules and plugins.
Namespaces and autoloading
PSR-4 via core/Autoloader.php. Mappings:
Celena\Core\→core/Celena\Module\<Name>\→modules/<Name>/Celena\Plugin\<Slug>\→plugins/<slug>/src/Celena\Theme\<Slug>\→templates/<slug>/
Rule: one class — one file. The file name matches the class name.
Contracts
declare(strict_types=1)everywhere; argument and return types are mandatory.- Any user input goes through
Celena\Core\Security\Filter. - Any HTML output goes through
e()(the escaping helper) or the template engine's auto-escaping. - Database access only via
QueryBuilderorPDO::prepare. Concatenating values into SQL is forbidden.
Hooks (events)
Extensions talk to the core through a WordPress-style hook system — actions (side effects) and filters (value transformation):
use Celena\Core\Plugin\Hook;
Hook::action('news.published', $news); // fire an action
Hook::on('news.published', fn($n) => /* ... */); // subscribe
$title = Hook::filter('news.title', $news['title']); // apply filters
Hook::addFilter('news.title', fn($t) => trim($t)); // subscribe to a filter
More on the Hooks & events page.
Cache
- Data cache is file-based by default (
storage/cache/data); APCu/Redis optionally. - Templates are compiled to PHP and cached in
storage/cache/tpl/{hash}.php. Invalidation is by source modification time (mtime) and manual.