data types and standard library

Environment and Config Files

Good config code keeps secrets out of source control, parses values into useful types, validates required settings early, and makes defaults explicit. Bad config code spreads getenv() calls everywhere and fails later with vague errors.

Read environment variables at the edge

Environment variables arrive as strings or false. Convert them before the rest of the application sees them.

PHP example
<?php

declare(strict_types=1);

putenv('APP_ENV=local');

$appEnv = getenv('APP_ENV');

if (!is_string($appEnv) || $appEnv === '') {
    throw new RuntimeException('APP_ENV is required.');
}

echo $appEnv . PHP_EOL;

// Prints:
// local

In a real project, read environment variables during application bootstrapping and pass typed config into services.

Parse INI with typed values

PHP can parse INI strings and files. INI_SCANNER_TYPED converts values such as true, false, and numbers where possible.

PHP example
<?php

declare(strict_types=1);

$ini = <<<'INI'
app_env=local
debug=true
max_upload_mb=10
INI;

$config = parse_ini_string($ini, false, INI_SCANNER_TYPED);

if (!is_array($config)) {
    throw new RuntimeException('Config could not be parsed.');
}

echo $config['app_env'] . PHP_EOL;
echo $config['debug'] ? 'debug on' : 'debug off';
echo PHP_EOL;
echo $config['max_upload_mb'] . PHP_EOL;

// Prints:
// local
// debug on
// 10

Typed parsing helps, but you still need validation. A misspelled key can still be missing.

Validate required settings

Turn loose config arrays into a known application shape.

PHP example
<?php

declare(strict_types=1);

function requireStringConfig(array $config, string $key): string
{
    if (!isset($config[$key]) || !is_string($config[$key]) || trim($config[$key]) === '') {
        throw new InvalidArgumentException($key . ' must be a non-empty string.');
    }

    return $config[$key];
}

$config = ['database_dsn' => 'mysql:host=db;dbname=app'];

echo requireStringConfig($config, 'database_dsn') . PHP_EOL;

// Prints:
// mysql:host=db;dbname=app

Failing fast during startup is better than discovering missing config halfway through a customer request.

Convert booleans deliberately

Do not treat every non-empty string as true. The string 'false' is truthy in PHP.

PHP example
<?php

declare(strict_types=1);

function envBool(string $value): bool
{
    $parsed = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);

    if ($parsed === null) {
        throw new InvalidArgumentException('Expected a boolean value.');
    }

    return $parsed;
}

echo envBool('false') ? 'on' : 'off';
echo PHP_EOL;

// Prints:
// off

This matters for settings such as debug mode, maintenance mode, feature flags, and queue workers.

Keep secrets out of committed config

Commit examples such as .env.example or config/example.ini, but do not commit real passwords, API keys, tokens, or private keys.

PHP example
<?php

declare(strict_types=1);

$requiredSecrets = ['DATABASE_PASSWORD', 'PAYMENT_API_KEY'];

foreach ($requiredSecrets as $name) {
    echo $name . ' must be provided by the environment' . PHP_EOL;
}

// Prints:
// DATABASE_PASSWORD must be provided by the environment
// PAYMENT_API_KEY must be provided by the environment

The application should document required secrets without exposing their values.

YAML and TOML need parsers

PHP has built-in support for INI, but YAML and TOML usually come from Composer packages or framework config loaders. The rule is the same: parse once, validate the shape, and pass typed values into the application.

PHP example
<?php

declare(strict_types=1);

$parsedYamlLikeConfig = [
    'cache' => [
        'enabled' => true,
        'ttl_seconds' => 300,
    ],
];

echo $parsedYamlLikeConfig['cache']['enabled'] ? 'cache on' : 'cache off';
echo PHP_EOL;

// Prints:
// cache on

Do not invent a YAML or TOML parser with string splitting. Use a maintained parser and validate the result.

What to remember

Read raw environment and config files at the application boundary. Convert strings to real types, validate required keys, document secrets without committing them, and keep config access centralised so the rest of the code can depend on clear values.

Practice

Task: Load typed application config

Write a small config loader from an INI string.

Requirements

  • Use declare(strict_types=1);.
  • Parse an INI string with INI_SCANNER_TYPED.
  • Require app_env to be a non-empty string.
  • Require debug to be a boolean.
  • Require max_upload_mb to be a positive integer.
  • Return a typed config array with clearer key names.
  • Print the loaded values.
  • Show one invalid config case by catching the exception.
  • Include the expected output as comments in the same PHP code block.

The loader should fail early when config is missing or has the wrong type.

Show solution
PHP example
<?php

declare(strict_types=1);

function loadAppConfig(string $ini): array
{
    $config = parse_ini_string($ini, false, INI_SCANNER_TYPED);

    if (!is_array($config)) {
        throw new InvalidArgumentException('Config could not be parsed.');
    }

    if (!isset($config['app_env']) || !is_string($config['app_env']) || trim($config['app_env']) === '') {
        throw new InvalidArgumentException('app_env must be a non-empty string.');
    }

    if (!isset($config['debug']) || !is_bool($config['debug'])) {
        throw new InvalidArgumentException('debug must be a boolean.');
    }

    if (!isset($config['max_upload_mb']) || !is_int($config['max_upload_mb']) || $config['max_upload_mb'] < 1) {
        throw new InvalidArgumentException('max_upload_mb must be a positive integer.');
    }

    return [
        'environment' => $config['app_env'],
        'debug' => $config['debug'],
        'maxUploadMegabytes' => $config['max_upload_mb'],
    ];
}

$validIni = <<<'INI'
app_env=local
debug=true
max_upload_mb=10
INI;

$config = loadAppConfig($validIni);

echo $config['environment'] . PHP_EOL;
echo $config['debug'] ? 'debug on' : 'debug off';
echo PHP_EOL;
echo $config['maxUploadMegabytes'] . ' MB' . PHP_EOL;

try {
    loadAppConfig("app_env=local\ndebug=maybe\nmax_upload_mb=10");
} catch (InvalidArgumentException $exception) {
    echo $exception->getMessage() . PHP_EOL;
}

// Prints:
// local
// debug on
// 10 MB
// debug must be a boolean.

The loader turns a loose config source into a predictable application shape. That keeps validation near the boundary and avoids scattered type checks throughout the codebase.