Library / SDK
thephpleague/config avatar
thephpleague/config

league/config, schema-backed configuration arrays for PHP

Simple yet expressive schema-based configuration library for PHP apps

567 stars18 forksPHPBSD-3-Clause

At a glance

What is it?
league/config lets a PHP application declare its configuration as a strict schema built with Nette Schema's fluent Expect API, then set and read values with dot notation, with validation and defaults applied on every read. It deliberately refuses to read YAML files, which is the boundary that decides whether it fits your project.
Who is it for?
league/config suits a PHP application whose configuration is written in PHP code, that wants the shape of its configuration checked in one place rather than discovered at runtime, and that is comfortable with the library refusing to load configuration from files. It does not suit a project that keeps configuration in YAML or JSON files, or that needs the schema to depend on the values.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Configuration as a schema, not as a bag of values

league/config is a PHP library for defining nested configuration arrays with strict schemas and reading them back with dot notation. It was created by Colin O'Dell and is part of the PHP League.

The word strict is doing the work here. In most PHP applications, configuration is an array that arrives from somewhere, and the shape of that array is a matter of convention that breaks at runtime, in production, when a key was misspelled or a nested value was left as a string where an integer was expected. This library flips that. You declare the structure once, and every value that goes in is checked against it.

The philosophy section spells out the goals, and they are unusually specific. The configuration operates on arrays with nested values that are easily accessible. The structure is defined with strict schemas that define the overall structure, allowed types and allowed values. Schemas are defined using a simple, fluent interface. You can add and combine schemas but never modify existing ones. Both the configuration values and the schema are defined and managed with PHP code. Schemas are immutable and should never change once they are set. And configuration values should never define or influence the schemas.

That last rule is the one that shapes everything, and it is the reason for the list of features the library will likely never support.

The library requires PHP 7.4 or higher, is licensed BSD-3, and its documentation lives at config.thephpleague.com. The recorded releases are v1.2.0 from 2022-12-11, v1.1.1 from 2021-08-14 and v1.1.0 from 2021-06-19, and the last push to the repository was on 2026-10-20.

The Expect API is Nette Schema, and that is the whole story

The basic usage example makes the dependency structure obvious. The schema is built with `Nette\Schema\Expect`, so league/config is not implementing its own schema language, it is a configuration layer over Nette Schema's fluent expectation API.

php
use League\Config\Configuration;
use Nette\Schema\Expect;

// 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(),
    ]),
]);

Each entry in the schema is an expectation. `Expect::string()` fixes the type, `->required()` marks it as mandatory, `->default('localhost')` supplies a fallback, `Expect::int()->min(1)->max(65535)` constrains the range, `Expect::anyOf('mysql', 'postgresql', 'sqlite')` restricts to a set of values, and `->nullable()` permits null. `Expect::structure([...])` nests a further schema under a key.

The type constraint and the value constraint are separate concerns, which is a useful distinction. `Expect::string()` tells you the value is a string, and `Expect::anyOf(...)` tells you it is one of three specific strings. Composing them gives you a closed set without needing a custom assertion.

Assertions are available too, and the logging section of the example uses one to check writability at validation time rather than hoping the path is right.

php
    '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(),
    ]),

That block shows three features at once. A default computed from an environment variable at schema definition time, which is a consequence of schemas living in PHP code. A deprecated key, which lets you keep an old name working while telling callers to move. And a custom assertion, which is where you can express a rule that types cannot.

Setting values, then reading them with dot notation

Values are set in two ways, and the difference between them matters for how you structure an application.

The bulk form merges a whole array at once.

php
$config->merge([
    'database' => [
        'driver' => 'mysql',
        'port' => 3306,
        'database' => 'mydb',
        'username' => 'user',
        'password' => 'secret',
    ],
]);

The single-value form sets one path.

php
$config->set('logging.path', '/var/log/myapp.log');

Both are validated against the schema, so a merge with a misspelled key is rejected in the same way a bad single value is. That is the practical payoff of declaring the structure: the failure happens where you set the value, with a message naming the schema, rather than later when a service reads a null it did not expect.

Reading works with two path syntaxes, and both are documented.

php
$config->get('database');        // Fetches the entire "database" section as an array
$config->get('database.driver'); // Fetch a specific nested value with dot notation
$config->get('database/driver'); // Fetch a specific nested value with slash notation
$config->get('database.host');   // Returns the default value "localhost"
$config->get('logging.path');    // Guaranteed to be writeable thanks to the assertion in the schema

Defaults are applied on read, not at merge time, which is why `database.host` returns `localhost` even though the merge never mentioned it. The same is true of the assertion, which is why the documentation says the logging path is guaranteed to be writeable at that point.

Two failure modes are documented explicitly. Setting a value that violates the schema raises an `InvalidConfigurationException`, so `set('database.driver', 'mongodb')` throws because the driver set does not include it. And fetching a key that does not exist also raises `InvalidConfigurationException`, which the library offers a way around.

php
$config->exists('foo.bar'); // Returns `false`

So there is an `exists()` check for the case where a key is genuinely optional, and a hard exception for the case where the developer assumed a key was there.

The features the library will never have

The most useful part of the README is the list of things this library will likely never support, because it tells you exactly where the boundary is before you commit to it.

Loading and exporting configuration values or schemas using YAML, XML or other files is out. Parsing configuration values from a command line or any other user interface is out. And dynamically changing the schema, the allowed values or the default values based on other configuration values is out.

That third one is the interesting exclusion, because it is the direct consequence of the rule that configuration values must never influence schemas. If a schema cannot depend on the values, then the whole schema has to be known before any value is set, which means the file-based workflow where you load a YAML file and then interpret it against a shape chosen by that file's content is structurally impossible here.

The practical effect is that league/config is a library for applications whose configuration is code. You write a PHP file that declares the schema and sets the values, and that file is version-controlled, reviewed and type-checked by your IDE like any other source. The alternative is that your configuration lives in a YAML file that no tool checks against anything, and a typo in it is discovered by a user rather than by a test.

The library points at five alternatives for the cases it does not cover: symfony/config, symfony/options-resolver, hassankhan/config, consolidation/config and laminas/laminas-config. Those are the tools to look at if you need file loading, command line parsing or dynamic schemas, and choosing one of them is not a failure of this library, it is a case where the philosophy does not match your requirement.

The counter-argument is fair, though. Configuration in files has real advantages. It can be changed by an operator without a deploy, it can be mounted from a secret store, and it can be templated per environment. A schema-based PHP library cannot offer that, and if your deployment model depends on it, league/config is the wrong shape for you no matter how good the validation is.

Versioning, maintenance policy and who uses it

The support policy is written down in specific terms, which is worth more than a general statement that the project is maintained.

When a new minor version is released, for example 1.0 to 1.1, the previous one continues to receive security and critical bug fixes for at least 3 months. When a new major version is released, the previous one receives critical bug fixes for at least 3 months and security updates for 6 months after that new release comes out. The README notes this policy may change in the future and exceptions may be made case by case.

The versioning section follows SemVer closely, and states that minor and patch releases should not introduce breaking changes to the codebase. That is a promise about the public API, with one carve-out: any classes or methods marked `@internal` are not intended for use outside the library and are subject to breaking changes at any time, so you should avoid using them.

The release history is where you need to look carefully. The recorded releases are v1.2.0 from 2022-12-11, v1.1.1 from 2021-08-14 and v1.1.0 from 2021-06-19. The last push to the repository was on 2026-10-20, so there is recent activity on the branch, but the last tagged release is from the end of 2022. A project on a 1.2 line that has not cut a release in nearly four years is stable but dormant, and that is a different risk profile from one shipping monthly.

The README names one user: league/commonmark, which is the CommonMark parser for PHP. That is a useful signal about the design constraints, because CommonMark is a library that other libraries embed, so its configuration surface has to be small and predictable.

The tooling in the repository reflects a serious PHP project. There is `phpcs.xml.dist` for coding standards, `phpstan.neon.dist` for static analysis, `psalm.xml` for a second analyser, `phpunit.xml.dist` for the test suite, and a `.scrutinizer.yml` for continuous inspection. Tests run with a Composer script.

bash
composer test

The BSD-3 licence is the permissive one, so using it in a commercial application carries no copyleft obligation, and the LICENSE.md file at the root holds the terms.

Editorial conclusion

league/config suits a PHP application whose configuration is written in PHP code, that wants the shape of its configuration checked in one place rather than discovered at runtime, and that is comfortable with the library refusing to load configuration from files. It does not suit a project that keeps configuration in YAML or JSON files, or that needs the schema to depend on the values. Verify first that the values you need to express are covered by the schema features you use, that your PHP version is 7.4 or higher, and that the deprecation mechanism you rely on, such as the `logging.file` key pointing at `logging.path`, behaves the way your deployment expects.

Frequently asked questions

What does league/config do in a PHP application?

It lets you define the structure of your configuration as a strict schema, then set and retrieve nested values with dot notation, with validation, defaults and assertions applied on every read. Schemas are built with the fluent Expect API from Nette Schema.

How do I install league/config?

Run composer require league/config. The library requires PHP 7.4 or higher, and its documentation is at config.thephpleague.com.

Can league/config load configuration from a YAML or JSON file?

No, and the project says so directly: loading or exporting values and schemas using YAML, XML or other files is a feature the library will likely never support, along with parsing values from a command line and dynamically changing the schema based on other values.

What happens if I set a value that does not match the schema?

An InvalidConfigurationException is thrown, so setting database.driver to a value outside the allowed set of mysql, postgresql and sqlite fails immediately. Fetching a key that does not exist raises the same exception, unless you check first with exists, which returns false for a missing path.

Which library should I use if I need configuration files?

The project points at symfony/config, symfony/options-resolver, hassankhan/config, consolidation/config and laminas/laminas-config as the options for file loading, command line parsing and dynamic schemas, which are the features league/config deliberately leaves out.

Official sources

  1. License: BSD-3-Clause
  2. Project website
  3. README
  4. Releases
  5. thephpleague/config on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/thephpleague-config.svg)](https://hysenlabs.com/projects/thephpleague-config)

Was this useful?