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.