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 update replaces 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.