Config: .ini inheritance + DB overrides
Tiger's config is a cascade — four tiers, each owned by the right party, merged later-wins:
core.ini (vendor) Tiger framework plumbing (you never touch this)
← application.ini (your app) you app settings + overrides
← local.ini (your app) you secrets / per-deploy (gitignored)
← DB (runtime) you per-org overrides, no deploy
Read it top to bottom: each tier can override the one above it. The last word wins. That's the entire mental model — everything below is just applying it.
Environment inheritance inside a file
Every .ini declares four sections, and the environments inherit with ZF1's : syntax:
[production]
tiger.session.ttl.authed = 604800 ; 7 days — the base
[staging : production] ; inherits everything from [production]…
tiger.session.ttl.authed = 3600 ; …and overrides just this one key
[testing : production]
[development : production]
tiger.debug = 1
APPLICATION_ENV selects the section; : production means you only write what differs. Set the base once in [production], tweak per-environment.
Overriding a Tiger default — without touching Tiger
Core ships a default theme:
; core.ini — Tiger's. You never edit this.
tiger.theme.name = "puma"
Your app wants a skin. Don't fork core — just declare it later in the cascade:
; application/configs/application.ini — yours
[production]
tiger.theme.name = "puma"
tiger.theme.skin = "jaguar"
Done. application.ini merges after core.ini, so your value wins. composer update can't clobber it — it's not in vendor/.
Secrets go in local.ini (gitignored, uncommitted):
; application/configs/local.ini — yours, never committed
[production]
tiger.db.host = "127.0.0.1"
tiger.db.dbname = "myapp"
tiger.db.username = "myapp"
tiger.db.password = "…"
tiger.crypto.key = "…" ; generated by: bin/tiger install:secrets
The DB tier: live overrides, no deploy 🧞
Here's a cosmic power: the bottom of the cascade is a database table — so you can change config at runtime, per tenant, with no deploy. Same merge, the DB just merges last:
$cfg = new Tiger_Model_Config();
// Flip a value globally — live next request, no file edit, no restart:
$cfg->set(Tiger_Model_Config::SCOPE_GLOBAL, '', 'tiger.theme.skin', 'cheetah');
// …or scope it to ONE tenant — that's how per-org theming works:
$cfg->set(Tiger_Model_Config::SCOPE_ORG, $orgId, 'tiger.theme.skin', 'jaguar');
This is the Tiger pattern — files are the base, a DB table is the runtime override tier, last wins. Per-org theming, feature toggles, and every settings screen are the same mechanism. When you build your own settings surface, mirror it: a key/value table with a scope (global|org), loaded on top of the base in a bootstrap _init*. (See Tiger_Model_Config and Tiger_Model_Translation — translations work the exact same way.)
Reading config
Anywhere in the app, the merged config is a Zend_Config in the registry:
$cfg = Zend_Registry::get('Zend_Config');
$skin = $cfg->tiger->theme->skin; // resolved through all four tiers
By the time your code runs, the cascade is already collapsed into one object. You just read it.