The drop-in architecture
Tiger has exactly one prime directive, and everything else falls out of it:
Every file is owned by exactly one party, and the boundary is enforced by tooling.
vendor/is Tiger's.composer updatereplaces it in place.- Everything else is yours. Composer physically cannot write outside
vendor/.
From that one rule comes the golden rule: extend, don't edit. You never hack a framework file (it'd vanish on the next update). You add. We don't forbid editing vendor/ β we just make the consequence honest and predictable.
The four layers
Zend_* β the ZF1 engine (TigerZF) vendor/β¦/tigerzf Tiger-owned
Tiger_* β the platform (kernel + substrate) vendor/β¦/tiger-core Tiger-owned
App_* β your shared code (no routes) library/ yours
Modules β your features (routes + UI) application/modules/* yours
Rule of thumb: *shared plumbing with no routes β library/ (`App_`); a feature with controllers/routes/views β a module.**
Modules: features that plug in by convention
A module is purely additive. Drop it in application/modules/<name>/ and it's live β no wiring, no registration:
application/modules/billing/
βββ Bootstrap.php # auto-discovered by the module scan
βββ controllers/ # Billing_InvoiceController β /billing/invoice/*
βββ services/ # Billing_Service_Invoice β POST /api {module:'billing',β¦}
βββ configs/acl.ini # who may reach it (deny-by-default)
βββ views/ models/ migrations/
ZF1's module scan finds the Bootstrap, the /api gateway resolves your services by convention, the ACL gates every access, and its config/i18n merge into the global config. You touched zero core files. Scaffold a fresh one with:
vendor/bin/tiger make:module billing
Want a pretty public URL (/pricing instead of /billing/pricing)? You declare it β the platform owns the ordering β you never hand-edit routes or (heaven forbid) a web-server rewrite. A module works the moment it's activated, with zero infra.
Consume, don't fork
Core is a dependency in vendor/, never copied into your app. You depend on its stable surface β marked @api (semver-guaranteed) vs @internal (may change) β and it updates like any Composer package. The arrows point one way β modules β core β never the reverse. Core needs no app; every app needs core.
The mental model, any time you're unsure where something goes: "Who owns this, and what happens to it on composer update?" The answer is usually the design.