Skip to main content

ECMS9 architecture

Overall layout

app/
├── Config/ # core configuration (Routes, Filters, Events, Autoload,
│ │ # ModuleExtensions, Toolbar, Boot/production.php)
├── Commands/ # core spark commands (cms9:*)
├── Controllers/ # base core controllers (BaseFront, ...)
├── Database/ # core migrations (baseline + incremental)
├── Filters/ # core filters (CSRF, LsCache, AdminDebugToolbar, ...)
├── Helpers/ # helpers (modules, cms, shop_render, widget)
├── Language/ # core language files
├── Libraries/ # core libraries (Template, RenderPoint, Scheduler, JobQueue, updaters)
├── Models/ # CI4 models of the core
├── Modules/ # ← modules: Shop, Admin, NovaPoshta, Search, payments, ...
├── Propel/ # generated legacy ORM models (Base/, Map/, Query)
└── ThirdParty/ # vendored legacy libraries
themes/
├── administrator/ # admin theme (admin panel Twig/JS/CSS)
└── <storefront>/ # storefront themes (default, goodlook, fauna, ...)
public/ # front controller index.php + static assets
writable/ # caches, logs, uploads, update backups,
# store_modules.json, schedule.json

Core

The core is CodeIgniter 4 with extensions:

  • Template pipeline (App\Libraries\Template) — renders storefront pages through Twig themes, with insertion points for modules (analytics, image optimization, etc.).
  • Events — key business actions fire events that modules subscribe to:
    • shopMakeOrder — an order was created (storefront or admin);
    • shopAdminOrderEdit — an order was saved in the admin panel;
    • shopAdminOrderUserCreate — a new customer was created in the admin panel;
    • storefront_render_point:<name> — a theme asked for module markup at a render point;
    • post_controller — request-scoped hook (global HTML injection, scheduler tick).
  • Filters — CSRF, page cache (LsCacheFilter), locale, module-enabled gate, the admin-only debug toolbar (Profiler).
  • Module registries (Config\ModuleExtensions) — the one place modules plug into core lists without editing core files. Each module ships a Config/Registrar.php whose static ModuleExtensions() returns any of: csrfExempt, lsCacheNever, moduleEnabledAlways, adminMenu, jobHandlers, scheduledTasks, adminTranslations. CI4 registrar auto-discovery merges them at config build.
  • Internal scheduler (App\Libraries\Scheduler) — a post_controller listener ticks at most once a minute and runs due scheduledTasks as separate spark subprocesses; state in writable/schedule.json, admin screen at /admin/scheduler. A site needs no hosting crontab (quiet sites may still point a real cron at spark cms9:schedule-run).
  • Job queue (App\Libraries\JobQueue) — persistent jobs resolved by module:type keys registered in jobHandlers.

Modules

Every module lives in app/Modules/<Name>/ and is registered in the database (the components table: enabled, active). Its PSR-4 namespace comes either from a line in app/Config/Autoload.php (first-party modules) or from writable/store_modules.json (modules installed from the store, merged at boot). See Module structure.

Data

Two layers coexist:

  • CI4 Query Builder / CodeIgniter\Model (\Config\Database::connect()) — the rule for all new code: new modules, new files in existing modules.
  • Propel (legacy)App\Propel\* models with generated Query classes (SOrdersQuery::create()->filterById(...)->findOne()); booted on every request and still dense in the Shop admin and exports. Edits inside existing Propel code stay in its style; do not mix both in one method. The direction is to retire Propel step by step, so new code must not add to it.

Schema: the core ships migrations in app/Database/Migrations/ (the baseline migration is guarded — it skips when the schema already exists and refuses to roll back a populated shop), so php spark migrate is safe on live sites. Modules do not ship migrations; they create their own tables idempotently in an install()/ensureSchema() method (CREATE TABLE IF NOT EXISTS), and store packages may add Resources/install*.sql which the installer applies statement by statement, skipping failures.

Themes

Storefront themes are Twig. A theme must not contain hardcoded references to a specific site; external resources — local only, or official CDNs. Themes expose module widgets through render_point() calls (see Render points); spark shop:theme-coverage reports what a theme surfaces. Themes are not part of the core package, so a theme fix is deployed per site. The admin theme is shared across installations.