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

FilePurpose
index.phpSite front — all public requests.
admin.phpAdmin panel (thin loader, hands control to Kernel).
install.phpSelf-deleting install wizard (after install an install.lock appears).
bin/celenaCLI: 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:

LayerFolderDepends on
Core (domain-agnostic)core/only core/*
Built-in CMS modulesmodules/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 QueryBuilder or PDO::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.