Configuration
Gustav separates framework bootstrap settings from application settings. The
starter uses conventional project paths and reads MODE.
app/bootstrap.php returns the configuration shared by the HTTP worker and
project CLI:
use GustavPHP\Gustav\Configuration;
return Configuration::forProject(
namespace: 'App',
root: dirname(__DIR__),
);
Configuration::forProject() selects development when MODE is absent and
accepts MODE=development or MODE=production. It configures these paths
relative to the supplied project root:
| Setting | Conventional path |
|---|---|
| Static files | public/ |
| Views | views/ |
| Session storage | storage/sessions/ |
Typed application configuration
Put immutable configuration classes under your application's Config
namespace. Mark the class with #[Config] and map every constructor parameter
to an environment variable with #[Env]:
namespace App\Config;
use GustavPHP\Gustav\Attribute\{Config, Env, Validate};
use GustavPHP\Gustav\Validation\Common\Integer;
enum DatabaseRole: string
{
case Primary = 'primary';
case Replica = 'replica';
}
#[Config]
final readonly class DatabaseConfig
{
/** @param list<string> $replicas */
public function __construct(
#[Env('DATABASE_URL')]
public string $url,
#[Env('DATABASE_POOL_SIZE'), Validate(new Integer(min: 1, max: 100))]
public int $poolSize = 10,
#[Env('DATABASE_SSL')]
public bool $ssl = true,
#[Env('DATABASE_REPLICAS')]
public array $replicas = [],
#[Env('DATABASE_ROLE')]
public DatabaseRole $role = DatabaseRole::Primary,
) {
}
}
Gustav discovers and hydrates every #[Config] class before request handling
starts. Each object is registered as an application singleton, so controllers,
services, middleware, and service factories use ordinary constructor
injection:
namespace App\Services;
use App\Config\DatabaseConfig;
use GustavPHP\Gustav\Attribute\Factory;
use GustavPHP\Gustav\Service\Lifetime;
use PDO;
#[Factory(lifetime: Lifetime::Singleton)]
final readonly class DatabaseFactory
{
public function __construct(private DatabaseConfig $configuration)
{
}
public function __invoke(): PDO
{
return new PDO($this->configuration->url);
}
}
See Third-party objects with factories for the complete factory contract and lifetime rules.
Conversion and optional values
Environment values are strings. Gustav converts them from the declared constructor type:
| PHP type | Accepted environment value |
|---|---|
string | The value unchanged |
int | A valid whole number, including 0 and negative values |
float | A finite decimal number |
bool | true, false, 1, or 0, case-insensitive |
array | JSON that decodes to a PHP array |
| backed enum | An exact string backing value or valid integer backing value |
Nullable forms of these types are supported. Missing values follow PHP constructor semantics:
- A parameter without a default is required, even when its type is nullable.
- An omitted parameter with a default keeps that default.
- Use
?string $value = nullwhen absence should resolve tonull. - An empty environment string is still a string; it is not treated as
null.
Ambiguous unions and unsupported object types are rejected during startup instead of being guessed at runtime. Arrays do not infer element types; validate their contents inside application code when needed.
Repeatable #[Validate(...)] attributes use the same built-in validation rules
as request input. All type and rule failures across discovered configuration
classes are collected before Gustav throws one configuration exception.
Environment files and secrets
Configuration::forProject() loads optional files from the project root in
this order:
.envprovides safe, committed development defaults..env.localoverrides those defaults for one machine and should be ignored by Git.- Real process environment variables override both files.
For example:
MODE=development
DATABASE_POOL_SIZE=10
DATABASE_SSL=true
DATABASE_REPLICAS='["postgres-replica.internal"]'
Use deployment environment variables for production secrets. Gustav never includes the rejected raw value in its own startup diagnostics. Errors identify the variable and target field so multiple problems can be fixed together:
Application configuration is invalid:
- DATABASE_POOL_SIZE (App\Config\DatabaseConfig::$poolSize): Value must be integer
- DATABASE_URL (App\Config\DatabaseConfig::$url): Value is required
Configuration objects contain the resolved values by design. Do not dump or log an entire configuration object when it contains credentials.
Testing configuration
Use an isolated Environment instead of changing process-global variables in
tests:
use GustavPHP\Gustav\Config\Environment;
use GustavPHP\Gustav\Configuration;
$configuration = Configuration::forProject(
namespace: 'App',
root: dirname(__DIR__),
environment: Environment::fromArray([
'MODE' => 'production',
'DATABASE_URL' => 'sqlite::memory:',
]),
);
The supplied map is the complete test environment, preventing process variables from leaking between tests.
Custom framework layout
Construct Configuration directly when the project does not use the
conventional directories:
use GustavPHP\Gustav\{Configuration, Mode};
use GustavPHP\Gustav\Session\SessionOptions;
$configuration = new Configuration(
mode: Mode::Production,
namespace: 'App',
files: '/srv/example/web/',
views: '/srv/example/templates/',
routeNamespaces: ['Module\Billing\Routes'],
eventNamespaces: ['Module\Billing\Events'],
serializerNamespaces: ['Module\Billing\Serializers'],
serviceNamespaces: ['Module\Billing\Services'],
middlewareNamespaces: ['Module\Billing\Middlewares'],
configurationNamespaces: ['Module\Billing\Config'],
commandNamespaces: ['Module\Billing\Commands'],
exceptionHandlerNamespaces: ['Module\Billing\ExceptionHandlers'],
session: new SessionOptions(directory: '/srv/example/var/sessions/'),
);
Direct construction reads real process variables for typed application
configuration. Pass an explicit Environment when another source is required.
The framework Configuration object itself remains injectable as a singleton.
Direct construction disables sessions unless session is supplied. See
Sessions and CSRF for cookie options and custom shared stores.