Skip to content

Configuration

Configuration is one flat array of values, StaticPHP\Core\Models\Config::$items, filled by plain php files that assign into $config.

The bootstrap loads two files from the application before anything else happens:

<?php
Config::load(['Config', 'Routing']);

With no module or project named, that resolves to APP_PATH/Config/Config.php and APP_PATH/Config/Routing.php. Both are ordinary php files. Load::config() binds Config::$items and the local $config to the same array, so assigning to $config inside a config file is assigning to Config::$items.

A minimal Config/Config.php that boots and serves a request:

<?php
$config['base_url'] = 'http://localhost/';
$config['disable_twig'] = false;
$config['environment'] = 'prod';
$config['debug'] = false;
$config['debug_check'] = null;
$config['allowed_hosts'] = [];
$config['trust_proxy_headers'] = false;
$config['trusted_proxy_hops'] = 1;
$config['client_ip'] = null;
$config['view_env_keys'] = [];
$config['url_prefixes'] = [];
$config['module_paths'] = [];
$config['autoload_configs'] = [];
$config['autoload_helpers'] = [];
$config['before_controller'] = [];
$config['error_pages'] = ['status' => null, 'debug' => null];
$config['request_uri'] = & $_SERVER['REQUEST_URI'];
$config['query_string'] = & $_SERVER['QUERY_STRING'];
$config['script_name'] = & $_SERVER['SCRIPT_NAME'];

The last three are bound by reference on purpose: the cli entry points rewrite $_SERVER to describe the url they were asked to serve, and the configuration has to follow.

client_ip used to be bound the same way. Leave it null instead - the bootstrap fills it from Router::clientIp(), which is the only thing that knows about proxies. Setting it explicitly still works and still wins, but on a proxied deployment a binding to REMOTE_ADDR pins every request to the proxy. See proxy headers.

Routing.php is covered in your first route.

Everything on Config is static, and Config::$items is public. get(), set(), getViewData(), setViewData(), merge() and load() are the core of it, alongside resolveDebug(), viewEngine() and the four typed accessors getString(), getInt(), getBool() and getArray(). The behaviour that catches people out - get() deciding with isset() and returning by reference, merge() branching on the stored value’s type, the $owerwrite spelling, and the absence of any dot-path support - is on the Config API.

Config::load() takes file names without the .php extension, and optionally a module and a project:

APP_PATH/Config/Mail.php
<?php
Config::load(['Mail']);
// APP_MODULES_PATH/Billing/Config/Rates.php
Config::load(['Rates'], 'Billing');
// SP_PATH/Utils/Config/Db.php - "staticphp" is the reserved name for the package itself
Config::load(['Db'], 'Utils', 'staticphp');

Config::load() is a thin wrapper over Load::config(), so the rules for resolving a module and a project to a directory - including what any name other than staticphp has to be, and the per-file ['Db' => 'staticphp'] form - are Load’s.

Instead of calling load() by hand, list the files in $config['autoload_configs'] and the bootstrap loads them for you, after debug has been worked out and before the error handlers are registered. Entries are slash separated and read right to left:

Entry Loads
Mail APP_PATH/Config/Mail.php
Billing/Rates APP_MODULES_PATH/Billing/Config/Rates.php
staticphp/Utils/Db SP_PATH/Utils/Config/Db.php

Six subsystems come with a default configuration file in src/Utils/Config/. They are ordinary config files, not classes: each one assigns the keys its subsystem reads, with values that make sense as a starting point. Load one with $config['autoload_configs'][] = 'staticphp/Utils/<name>', or copy it into the application’s own Config/ directory and edit it there.

Populates $config['db']['pdo'], keyed by connection name, with default as the shipped entry. Each entry holds string - the PDO connection string - along with username, password, charset, persistent, wrap_column, fetch_mode_objects, emulate_prepares and debug. The shipped file sets debug from $config['debug'], which is why it has to be loaded after the application’s own Config.php - the bootstrap’s ordering already guarantees that. See the database layer.

Populates $config['cache'] with one section per backend: redis, memcached, apcu and files. Every section takes a prefix; the rest is backend specific - hostname, port, database and timeout for redis, a server list for memcached, and a path, extension and directory nesting for the file backend. See caching.

Populates $config['i18n']: the countries and languages the site serves, the url format that identifies them, and the switches for redirecting, negotiating, auto-registering missing keys, falling back, caching warmed language tables and naming the database tables. It also derives the language prefixes from the country list and prepends them to $config['url_prefixes'], so the router recognises /lv-en/... as a prefix rather than as the first controller segment. See internationalisation.

Populates $config['migrations'] with three keys: dir, the directory the .sql files live in, kept outside Public/ deliberately; table, the tracking table; and connection, naming which entry of $config['db']['pdo'] to migrate.

Populates $config['audit']: connection and table, saying which entry of $config['db']['pdo'] the trail is written to and where the rows land; strict and max_rows, the two safety switches; id_key, the column read from the affected row; actor and context, the callables an application supplies to name who did it and where from; and exclude, the columns whose values must never reach the trail. The same list is also a constant inside Audit, so the trail works before this file is loaded at all - load it when you want to change something. See the audit trail.

Populates $config['queue']: driver, database or redis; connection, which entry of $config['db']['pdo'] jobs are written to on the database driver; table and failed_table; the redis section, only read on the redis driver; queue, the name a push lands on when it does not name one; tries and backoff, how many attempts a job gets and how long it waits between them; timeout, how long a worker’s claim on a job lasts; sleep, how long a worker waits before looking again when there is nothing to do; and handlers, for job names that are not class names or that have since moved. See the queue overview.

Beyond the subsystem sections above, these are the keys the package itself looks at:

Key Read by
base_url, request_uri, script_name Router::splitSegments(), to work out the url and segments
routing Router, for the rewrite rules and the default route
url_prefixes Router::splitSegments(), stripped off before routing
allowed_hosts Router::validateHost(); empty means syntax check only
debug, debug_check the bootstrap, to decide whether debug is on
client_ip the bootstrap fills it when null; read by the application
trust_proxy_headers, trusted_proxy_hops Router, before it reads any X-Forwarded-* header
autoload_configs, autoload_helpers the bootstrap
before_controller Router::loadController(), called before the controller
module_paths Load, to resolve a project name to a directory
disable_twig the bootstrap, to skip building the view engine
error_pages ErrorPage, to override the status and debug templates
view_env_keys Load, the only $_ENV keys exposed to templates
view_data Load::view(), merged into every view’s data
logging.display_level, logging.log_level, logging.report_level the error handlers, to decide what is shown, logged and emailed
environment the debug error page, shown in the runtime summary

The bootstrap writes client_ip, now, date_time, debug, and - when twig is in use - view_engine and view_loader back into the same array.

query_string in the sample above is bound for the application’s convenience; the router derives Router::$query_string from the request uri itself rather than reading it.