Configuration
Config files return arrays of data — routes, credentials, services. Your application class's load()
turns that data into wiring. Values in one, behavior in the other.
The Config Array#
The front controller reads one config file and hands the array to the constructor:
<?php
$autoloader = include __DIR__ . '/../vendor/autoload.php';
$dotEnv = Dotenv\Dotenv::createImmutable(__DIR__ . '/../');
$dotEnv->safeLoad();
$app = new App\Application($autoloader, include __DIR__ . '/../app/config/app.http.php');
$app->load();
$app->run();
Bootstrap runs in the constructor and picks up a fixed set of top-level keys:
| Key | What bootstrap does with it |
|---|---|
routes |
registers every entry with the router |
services |
defines each entry in the service locator |
events |
attaches each listener to the event manager |
middleware |
registers each handler with the middleware manager |
name, version |
sets the application's identity; name falls back to APP_NAME |
prefix, src, psr-0 |
registers an autoloader prefix for the app or module |
helper_functions |
set to false to skip loading the pop-utils helper functions |
So a config array can wire an application on its own:
<?php
return [
'routes' => [
'[/]' => [
'controller' => 'App\Http\Controller\IndexController',
'action' => 'index'
],
'*' => [
'controller' => 'App\Http\Controller\IndexController',
'action' => 'error'
]
],
'services' => [
'session' => ['call' => 'Pop\Session\Session::getInstance']
]
];
Every other key is yours to use. pop:init adds http_options_headers for CORS and, with a database
configured, a database key. Reach them with $app->config['http_options_headers'] or
Pop\App::config('database').
Wiring in load()#
Opening connections, registering services and branching between HTTP and CLI go in load() on your
application class. Call it before run() — the generated front controller does:
<?php
namespace App;
class Application extends \Pop\Application
{
public function load(): Application
{
if (isset($this->config['database'])) {
$this->initDb($this->config['database']);
}
if ($this->router() !== null) {
if ($this->router()->isHttp()) {
$this->on('app.dispatch.pre', 'App\Http\Event\Options::send', 1);
} else if ($this->router()->isCli()) {
$this->on('app.dispatch.post', function() { echo PHP_EOL; });
}
}
return $this;
}
}
initDb() is an app-level convention that pop:init scaffolds, shown in full in
Records & the ORM. The credentials stay data in app/config/database.php; the
connection opens here.
Adding a database after your initial pop:init? Run ./kettle db:config, then add
'database' => include __DIR__ . '/database.php', to app.http.php so load() can find it.
Environment Variables#
Per-machine values live in .env, which vlucas/phpdotenv loads into $_ENV before anything else —
public/index.php for web requests, kettle for commands. pop:init writes it, .gitignore excludes
it, and .env.example is the copy you commit.
| Variable | Used for |
|---|---|
APP_NAME |
the application name, when the config sets no name key |
APP_ENV |
local, dev, testing, staging or production |
APP_URL |
the application's base URL |
MAINTENANCE_MODE |
true or false |
MAINTENANCE_MODE_SECRET |
lets specific clients through while maintenance mode is on |
DB_ADAPTER, DB_DATABASE, DB_USERNAME, DB_PASSWORD, DB_HOST, DB_TYPE |
read by app/config/database.php |
QUEUE_ADAPTER, QUEUE_PRIORITY, QUEUE_LEASE |
read by the queue configuration |
Read them through Pop\App::env(), which coerces the literals dotenv leaves as strings — true and
(true) come back as true, false as false, null as null, empty as '':
use Pop\App;
$name = App::env('APP_NAME');
$mode = App::env('MAINTENANCE_MODE'); // bool, not the string 'true'
$tz = App::env('APP_TIMEZONE', 'UTC'); // second argument is the default
The default applies when the key is absent. A key written with no value — DB_HOST= on its own line —
is an empty string, so remove the line entirely for the default to take effect.
Per-Environment Branching#
One config array per runtime, with the environment as a value you branch on:
use Pop\App;
if (App::isLocal()) {
// verbose errors, no cache
} else if (App::isProduction()) {
// real mail transport, real payment gateway
}
isProduction() matches any APP_ENV starting with prod, and
App::environment(['staging', 'production']) tests membership in a set. Every predicate is also an
instance method — $this->isProduction() inside load(), which is usually where the branch belongs.
./kettle pop:env prints the current environment and ./kettle pop:env --set changes it. In
production, kettle confirms before running anything destructive.
For maintenance mode, ./kettle pop:down --secret=letmein sets the flag and the secret, ./kettle pop:up
clears it. Gate on App::isDown(), and App::isSecretRequest() lets a client holding the secret through.
Config Objects#
The constructor takes a Pop\Config\Config anywhere it takes an array, which adds dot-notation access
and immutability:
use Pop\Config\Config;
$config = new Config(['database' => ['host' => 'localhost', 'port' => 5432]]);
$host = $config['database.host']; // 'localhost'
$config->changesAllowed(); // false
Pass true as the second constructor argument for a writable one. Config::createFromData() also reads
INI, JSON, XML and YAML — see Config Objects.
See Also#
- Application Structure — where each config file lives in the skeleton
- Applications & Bootstrap — the full bootstrap sequence and
load() - Records & the ORM —
initDb()in full, and thedatabaseconfig shape - Config Objects —
Pop\Config\Configon its own - popphp README — the full config key surface