Skip to content

Developer overview

This section is for people extending the system. The end-user manual is under user guide.

Stack

LayerTechnology
LanguagePlain PHP 8.0+
DatabaseEloquent ORM (illuminate/database)
TemplatesSmarty 3.1 plus native PHP rendering
SessionsPHP sessions
RoutingSingle entry point index.php, dispatched via ?s=/path

Directory layout

app/
  Controller/     Controllers: Admin, User, Shared (integration), Install
  Service/        Business logic — ordering, top-ups and delivery live in Service/Bind/
  Model/          Eloquent models
  View/           Templates; storefront themes are under User/Theme/
  Plugin/         Plugins
  Pay/            Payment plugins
  Consts/Hook.php Hook point constants
kernel/           Core: routing, annotations, plugin loading, utilities
config/           Configuration files (database connection, etc.)
runtime/          Runtime cache and logs

Three ways to extend

ApproachGood forDocs
HooksSlotting into an existing flow (notify after an order, screen a registration)Hook reference
PluginsA complete feature with its own pages, settings and routesPlugin development
Payment pluginsIntegrating a new payment providerPayment plugin development

For external systems that need to pull products and place orders, see the API reference.

Things to know before you start

Everyone who has been bitten by these remembers them:

1. hook() arguments are passed by reference, so they must be variables.

php
hook(P, new X());              // fatal 500
hook(P, $this->getUser());     // fatal 500
hook(P, ['a' => 1]);           // fatal 500
$user = $this->getUser();
hook(P, $user);                // correct

Worse, it only blows up when that line actually executes, so it is easy to miss in testing.

2. The lifecycle attribute must use a named argument.

php
#[Plugin(state: Plugin::INSTALL)]   // correct
#[Plugin(Plugin::INSTALL)]          // silently never fires

With the positional form the plugin reports "enabled successfully" but your table creation never ran.

3. update.php runs before the new code is copied in.

When an upgrade script executes, the new version's files are not in place yet. Referencing classes, constants or methods that only exist in the new version throws, and the exception is swallowed silently. Use only old-version APIs and literals.

4. Disable and re-enable the plugin after adding hook files.

runtime/plugin/hook is a compiled registry cache, rebuilt only when a plugin is enabled. Refreshing the page will not do it.

5. To clear the template cache, delete only runtime/view/compile.

Never delete runtime/config — that is the configuration cache and removing it breaks the whole site.

Debugging

When something goes wrong, read runtime.log in the site root (not runtime/log/).

With DEBUG=false, any uncaught exception is rendered as the 404 page and returned with HTTP 500. The real cause exists only in runtime.log.

Released under the MIT License