Modules
A module is how you add a feature to Tiger. If it has controllers, routes, an API, views, or ACL β it's a module. If it's shared, route-less plumbing β it's the app library instead. This page is the module reference; for a hands-on first run see Your first module.
A module is a folder
Modules live in application/modules/<name>/ and are auto-discovered β Tiger scans the modules dir, includes each Bootstrap.php, and runs it. There's no central registry to edit, no "register your module here" step.
application/modules/billing/
βββ Bootstrap.php # runs on boot; hook route overrides, nav, settings here
βββ controllers/ # Billing_*Controller β the thin MVC surface (SSR shell only)
βββ services/ # Billing_Service_* β the /api surface (fat: logic lives here)
βββ models/ # Billing_Model_* β extend Tiger_Model_Table
βββ forms/ # Billing_Form_* β extend Tiger_Form
βββ views/scripts/ # .phtml view scripts
βββ configs/
β βββ module.ini # module settings
β βββ acl.ini # permissions (deny-by-default)
β βββ navigation.ini # admin sidebar entries (ACL-filtered)
βββ migrations/ # NNNN_name.php, additive-only
βββ languages/en/billing.php # ['billing.key' => 'text']
βββ assets/ # symlinked to public/_modules/billing on activate
Purely additive β it touches no core file
This is the contract that makes modules safe:
- Config β the module's
configs/*.inimerge into the global config. It doesn't edit anyone's config; it adds to the cascade. - Behavior β it exposes
Module_Service_*classes; the core/apigateway resolves them by convention. Thin controllers, fat services. - Permissions β resources are class names, gated by the ACL; the module ships its own
acl.ini. - Views / i18n / routes β same additive pattern, all namespaced to the module.
Because nothing in a module reaches into Tiger, and nothing Tiger ships reaches into a module, composer update can replace the framework under you without disturbing your feature. Arrows point one way: modules β core, never the reverse.
Activation is zero-infrastructure
vendor/bin/tiger module:activate billing # on
vendor/bin/tiger module:deactivate billing # off
vendor/bin/tiger module:list # what's installed / active
On activate, a module's assets/ (if present) are symlinked to public/_modules/<slug> β reference them at /_modules/billing/β¦ (and route them through $this->asset() for cache-busting). A module never touches Apache/nginx, DNS, or anything outside its own dir. That's a hard rule: infra-touching activation would break 1-click install. A pretty public URL is a PHP-layer route override, not a rewrite rule.
Modules can also declare lightweight dependency alerts in configs/dependency.ini β activate warns if a required module is off; deactivate warns what depends on this one. They're convenience alerts, never hard blocks.
Scaffold, don't hand-write
Don't build the folder by hand β generate it:
vendor/bin/tiger make:module billing
You get a live controller + /api service + ACL + views + config to build from. Then it's the loop from Your first module: add a method to the service, and it's callable.
Where things go
| You're adding⦠| It belongs in⦠|
|---|---|
| a feature with a URL / API / views | a module (application/modules/*) |
| shared, route-less code (base classes, helpers, integrations) | the app library (library/App/*) |
| a base class every module should extend | the app library (App_Service_Base extends Tiger_Service_Service) |
Rule of thumb, any time you're unsure: does it have a route? Route β module. No route β library.