Developer overview
This section is for people extending the system. The end-user manual is under user guide.
Stack
| Layer | Technology |
|---|---|
| Language | Plain PHP 8.0+ |
| Database | Eloquent ORM (illuminate/database) |
| Templates | Smarty 3.1 plus native PHP rendering |
| Sessions | PHP sessions |
| Routing | Single 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 logsThree ways to extend
| Approach | Good for | Docs |
|---|---|---|
| Hooks | Slotting into an existing flow (notify after an order, screen a registration) | Hook reference |
| Plugins | A complete feature with its own pages, settings and routes | Plugin development |
| Payment plugins | Integrating a new payment provider | Payment 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.
hook(P, new X()); // fatal 500
hook(P, $this->getUser()); // fatal 500
hook(P, ['a' => 1]); // fatal 500
$user = $this->getUser();
hook(P, $user); // correctWorse, 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.
#[Plugin(state: Plugin::INSTALL)] // correct
#[Plugin(Plugin::INSTALL)] // silently never firesWith 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.
