league/config

repository·main·Indexed 20 days ago

https://github.com/thephpleague/config

A PHP library for defining nested configuration arrays with strict schemas and accessing values using dot or slash notation. It utilizes nette/schema for defining types, required fields, and defaults, and dflydev/dot-access-data for data access. Key features include schema validation, a read-only configuration reader via the reader() method, and support for merging multiple schemas into a single Configuration instance.

Tokens
8.7K
Snippets
27
Records
33
Agent score
68%

What's inside league/config

  1. Identify the dependencies of league/config

    main

    To maintain a minimal footprint and facilitate its specific approach to schema definition and data access, league/config relies on two primary open-source libraries:

    1. nette/schema: Used for defining and processing configuration schemas.
    2. dflydev/dot-access-data: Used to simplify reading and writing nested configuration values.

    These dependencies are chosen to be lightweight and to minimize potential conflicts with other packages in your project.

  2. Understand the design philosophy of league/config

    main

    The league/config library follows a simple, opinionated approach focused on strict, code-driven configuration. It is designed for scenarios where configuration is managed via PHP code rather than external files.

    Core Principles:

    • Array-based structure: Configuration operates on nested arrays that are easily accessible.
    • Strict Schemas: The structure, allowed types, and allowed values are strictly defined.
    • Fluent Interface: Schemas are defined using a simple, fluent API.
    • Immutability: Schemas are immutable and cannot be modified once set. You can add or combine schemas, but you must never modify an existing one.
    • Code-driven: Both configuration values and schemas are defined and managed using PHP code.
    • Separation of Concerns: Configuration values must never define or influence the schemas.

    What this library does NOT do:

    • It does not support loading or exporting configuration via YAML, XML, or other file formats (though you can implement this yourself).
    • It does not parse configuration from command lines or user interfaces.
    • It does not allow dynamic schema changes (e.g., changing allowed values based on other configuration values).
  3. Understand Lazy Processing in league/config

    main

    By default, league/config uses lazy processing. This means that user-provided values are not processed or validated against the schema immediately upon instantiation or when using merge() or set(). Instead, validation is deferred until the first time you attempt to read or check a value.

    This behavior provides flexibility, allowing you to:

    1. Set values from multiple different sources sequentially.
    2. Add missing required values later in the application lifecycle before they are accessed.

    Note: Because validation is deferred, errors (such as missing required keys or type mismatches) will not be thrown during the configuration setup phase, but rather when you call $config->get() or similar retrieval methods.

    use League\Config\Configuration;
    use Nette\Schema\Expect;
    
    $config = new Configuration([
        'debug_mode' => Expect::bool()->required(),
    ]);
    
    // No validation happens here, even if 'debug_mode' is missing
    $config->merge(['other_key' => 'value']);
    
    // Validation occurs HERE. If 'debug_mode' was never set, an exception is thrown.
    if ($config->get('debug_mode')) {
        // ...
    }
  4. How league/config handles configuration and schemas

    main

    The library follows a specific philosophy regarding how configuration should be managed:

    • Strict Schemas: Configuration is defined by strict schemas that dictate structure, types, and allowed values.
    • Immutability: Schemas are immutable. Once defined, they should not be modified. You can add or combine schemas, but you cannot change existing ones.
    • Code-First: Both schemas and configuration values are managed directly in PHP code. The library does not support loading/exporting from YAML, XML, or other file formats, nor does it parse CLI arguments.
    • Separation of Concerns: Configuration values should never influence or change the schema itself.
    • Access Patterns: Values are accessed via nested arrays or dot/slash notation (e.g., database.host or database/host).
  5. Define configuration structures using schemas

    main

    A schema defines the structure of your configuration, including allowed keys, value types, and validation rules. league/config uses the nette/schema package for this purpose. You define schemas using the Nette\Schema\Expect class and pass them into the League\Config\Configuration constructor.

    With schemas, you can:

    • Mark options as required.
    • Provide default values.
    • Validate types (int, string, bool, etc.), ranges (min/max), regular expressions, or enums.
    • Mark options as deprecated.
    • Use custom assertions via assert().
    • Cast values to specific types.

    Note on nested structures: While nette/schema typically casts nested structures (Expect::structure) to stdClass objects, league/config automatically casts them to array for you.

    use League\Config\Configuration;
    use Nette\Schema\Expect;
    
    $config = new Configuration([
        'debug_mode' => Expect::bool(),
        'database' => Expect::structure([
            'driver' => Expect::anyOf('mysql', 'postgresql', 'sqlite')->required(),
            'host' => Expect::string()->default('localhost'),
            'port' => Expect::int()->min(1)->max(65535),
            'ssl' => Expect::bool(),
            'database' => Expect::string()->required(),
            'username' => Expect::string()->required(),
            'password' => Expect::string()->nullable(),
        ]),
        'logging' => Expect::structure([
            'enabled' => Expect::bool()->default($_ENV['DEBUG'] == true),
            'file' => Expect::string()->deprecated("use logging.path instead"),
            'path' => Expect::string()->assert(function ($path) { return \is_writeable($path); })->required(),
        ]),
    ]);
  6. Manage league/config versions with Composer caret operator

    main

    The project follows Semantic Versioning (SemVer). To ensure compatibility and receive non-breaking updates, it is highly recommended to use Composer's caret operator (^) when requiring the package. For example, using ^1.1 allows any version from 1.1.0 up to (but not including) 2.0.0.

    composer require "league/config:^1.1"
  7. Initialize Configuration with a Schema

    main

    You can instantiate a Configuration object by passing an initial array of configuration values. To ensure data integrity, it is recommended to use Nette\Schema\Expect to define a schema that validates the structure, types, and requirements of your configuration data.

    use League\Config\Configuration;
    use Nette\Schema\Expect;
    
    $config = new Configuration([
        'database' => Expect::structure([
            'driver' => Expect::anyOf('mysql', 'postgresql', 'sqlite')->required(),
            'host' => Expect::string()->default('localhost'),
            'port' => Expect::int()->min(1)->max(65535),
            'database' => Expect::string()->required(),
            'username' => Expect::string()->required(),
            'password' => Expect::string()->nullable(),
        ]),
    ]);
  8. Basic usage of league/config

    main

    Using league/config involves a three-step workflow to manage application settings with validation:

    1. Define the configuration schema: Specify the overall structure, required options, validation constraints (using Nette\Schema\Expect), and default values.
    2. Apply user-provided values: Merge external data (like arrays from config files or environment variables) into the configuration instance.
    3. Read validated options: Retrieve the processed and validated values using dot-notation keys to drive application logic.

    Note: This guide refers to version 1.1 patterns.

    use League\Config\Configuration;
    use Nette\Schema\Expect;
    
    // 1. Define schema
    $config = new Configuration([
        'database' => Expect::structure([
            'driver' => Expect::anyOf('mysql', 'postgresql', 'sqlite')->required(),
            'host' => Expect::string()->default('localhost'),
        ]),
    ]);
    
    // 2. Set values
    $config->merge([
        'database' => [
            'driver' => 'mysql',
        ],
    ]);
    
    // 3. Read values
    $driver = $config->get('database.driver');
  9. Merge multiple schemas into a single Configuration

    main

    If different components of your application define their own schemas (e.g., a DatabaseConnection class and a Logger class), you can combine them into a single League\Config\Configuration instance.

    You can achieve this in three ways:

    1. Passing an array of schemas to the Configuration constructor.
    2. Passing some schemas to the constructor and using addSchema() for others.
    3. Initializing an empty Configuration and calling addSchema() multiple times.

    Each call to addSchema(string $name, Schema $schema) registers a specific sub-section of your configuration.

    use League\Config\Configuration;
    
    // Option 1: All in constructor
    $config = new Configuration([
        'database' => DatabaseConnection::getSchema(),
        'logging' => Logger::getSchema(),
    ]);
    
    // Option 2: Mix constructor and addSchema()
    $config = new Configuration([
        'database' => DatabaseConnection::getSchema(),
    ]);
    $config->addSchema('logging', Logger::getSchema());
    
    // Option 3: All via addSchema()
    $config = new Configuration();
    $config->addSchema('database', DatabaseConnection::getSchema());
    $config->addSchema('logging', Logger::getSchema());
  10. Define and use configuration with the Configuration class

    main

    The League\Config\Configuration class is the primary entry point. It allows you to define a strict schema using Nette\Schema\Expect and manage configuration values.

    Key features:

    • Schema Definition: Use Expect to define types, required fields, defaults, and custom assertions.
    • Value Setting: Use merge() to set multiple values at once or set() to set a single value using dot or slash notation.
    • Value Retrieval: Use get() to fetch values. Validation and defaults are applied automatically.
    • Existence Checking: Use exists() to check for a key without triggering an exception.
    • Error Handling: If validation fails or a non-existent key is requested via get(), an InvalidConfigurationException is thrown.
    use League\Config\Configuration;
    use Nette\Schema\Expect;
    
    // 1. Define your configuration schema
    $config = new Configuration([
        'database' => Expect::structure([
            'driver' => Expect::anyOf('mysql', 'postgresql', 'sqlite')->required(),
            'host' => Expect::string()->default('localhost'),
            'port' => Expect::int()->min(1)->max(65535),
            'ssl' => Expect::bool(),
            'database' => Expect::string()->required(),
            'username' => Expect::string()->required(),
            'password' => Expect::string()->nullable(),
        ]),
        'logging' => Expect::structure([
            'enabled' => Expect::bool()->default($_ENV['DEBUG'] == true),
            'file' => Expect::string()->deprecated("use logging.path instead"),
            'path' => Expect::string()->assert(function ($path) { return \is_writeable($path); })->required(),
        ]),
    ]);
    
    // 2. Set values
    $config->merge([
        'database' => [
            'driver' => 'mysql',
            'port' => 3306,
            'database' => 'mydb',
            'username' => 'user',
            'password' => 'secret',
        ],
    ]);
    $config->set('logging.path', '/var/log/myapp.log');
    
    // 3. Retrieve values
    $config->get('database');        // Returns entire section as array
    $config->get('database.driver'); // Returns specific value via dot notation
    $config->get('database/driver'); // Returns specific value via slash notation
    $config->get('database.host');   // Returns default value "localhost"
    
    // 4. Check existence
    if ($config->exists('foo.bar')) {
        // ...
    }
  11. Create a read-only configuration reader

    main

    If you need to pass a configuration object to another part of your application but want to ensure that the recipient cannot modify the configuration values or schemas, use the reader() method.

    Calling $config->reader() returns a read-only version of the configuration that only exposes the get() and exists() methods. This prevents any further calls to methods that modify the state (like set()).

    Since both the mutable Configuration object and the read-only reader implement ConfigurationInterface, you should type-hint against ConfigurationInterface in your methods to allow both mutable and read-only instances to be passed in.

    use League\Config\Configuration;
    
    $config = new Configuration([/* ... */]);
    
    // Pass the read-only reader to prevent modification
    $someOtherObject->setConfig($config->reader());