PHPackages                             crell/config - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. crell/config

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

crell/config
============

A fast, type-driven configuration loading system for any PHP project.

0.2.0(1y ago)8157↓92.9%2[2 issues](https://github.com/Crell/Config/issues)1LGPL-3.0-or-laterPHPPHP ~8.2CI passing

Since Mar 24Pushed 8mo ago2 watchersCompare

[ Source](https://github.com/Crell/Config)[ Packagist](https://packagist.org/packages/crell/config)[ Docs](https://github.com/Crell/Config)[ GitHub Sponsors](https://github.com/Crell)[ RSS](/packages/crell-config/feed)WikiDiscussions master Synced today

READMEChangelogDependencies (11)Versions (4)Used By (1)

Config Loader
=============

[](#config-loader)

[![Latest Version on Packagist](https://camo.githubusercontent.com/76bc373decba07042a212cad3d22ec1ef86d0ac2d9703dc03a8f576212e21ca0/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f4372656c6c2f436f6e6669672e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/Crell/Config)[![Software License](https://camo.githubusercontent.com/bf1c19b4a07c841715e713542bd6e9c2aaf75ffa2ee2aa3987814d99311f799b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4c47504c76332d677265656e2e7376673f7374796c653d666c61742d737175617265)](LICENSE.md)[![Total Downloads](https://camo.githubusercontent.com/d305e41559da33a8d382cff8bc2abb1afb7295a9c290083e0ffb03f855aee10c/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f4372656c6c2f436f6e6669672e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/Crell/Config)

Config Loader is what it says it is: A simple, fast, but powerful configuration loading system suitable for any framework.

How it works
------------

[](#how-it-works)

Config Loader is based on "configuration objects." A configuration object is just a plain PHP class. Every config object is defined entirely by a PHP class, and each property is a config value. That means the name, type, and default value of every configuration value is defined and endorsed by ordinary PHP code. If a value is required, it has no default value set. If it's optional, the default is specified right there in the code. While nothing in the system requires it, it's strongly recommended to make config classes `readonly`.

Config objects can be populated in "layers", from different sources. Typically, that is a file on disk in whatever format you prefer (YAML, PHP, etc.) The multiple layers allows for individual configuration properties to be overriden, say, to have a base configuration and then modifications for `dev` and `prod` environments.

Because each config object is its own class, it integrates seamlessly with a Dependency Injection Container and testing. (See the section on DI below.)

Let's see an example.

YAML config example*Note: To use YAML config files, you need to have [`symfony/yaml`](https://packagist.org/packages/symfony/yaml) installed.*

```
use Crell\Config\LayeredLoader;
use Crell\Config\YamlFileSource;

class EditorSettings
{
    public function __construct(
        public readonly string $color,
        public readonly string $bgcolor,
        public readonly int $fontSize = 14,
    ) {}
}

$loader = new LayeredLoader([
  new YamlFileSource('./config/common'),
  new YamlFileSource('./config/' . APP_ENV),
]);

$editorConfig = $loader->load(EditorSettings::class);
```

Given these files on disk:

```
# config/common/editorsettings.yaml
color: "#ccddee"
bgcolor: "#ffffff"
```

```
# config/dev/editorsettings.yaml
bgcolor: '#eeff00'
```

JSON config example```
use Crell\Config\LayeredLoader;
use Crell\Config\JsonFileSource;

class EditorSettings
{
    public function __construct(
        public readonly string $color,
        public readonly string $bgcolor,
        public readonly int $fontSize = 14,
    ) {}
}

$loader = new LayeredLoader([
  new JsonFileSource('./config/common'),
  new JsonFileSource('./config/' . APP_ENV),
]);

$editorConfig = $loader->load(EditorSettings::class);
```

Given these files on disk:

`config/common/editorsettings.json`:

```
{
    "color": "#ccddee",
    "bgcolor": "#ffffff"
}
```

`config/dev/editorsettings.json`:

```
{
  "bgcolor": "#eeff00"
}
```

Now, when this code is run, `$editorConfig` will have a color of `#ccddee`, a bgcolor of `#ffffff`, and a fontSize of `14` (because a default is provided). If, however, it is run in a `dev` environment (`APP_ENV` is `dev`), then the bgcolor will be `#eeff00`.

The net result is a quick and easy way to load configuration for your application, with full support for per-environment overrides. Of course, you can use layers in any other way you wish as well. (Vary per system language, for instance.)

You can and should define as many different config objects as you'd like. They all load separately. See the section on Dependency Injection below for how that comes in helpful.

Source types
------------

[](#source-types)

There are several file formats supported out of the box, including `JsonFileSource`, `YamlFileSource`, `PhpFileSource`, and even `IniFileSource` (because why not?).
Note: In order to use the `YamlFileSource` you need to have the [`symfony/yaml`](https://packagist.org/packages/symfony/yaml) package installed.

Writing other sources is simple, as the `ConfigSource`interface has only a single method.

You can even stack multiple file types with the same directory to read from into a single list, if you want to support multiple file types.

Custom file keys
----------------

[](#custom-file-keys)

By default, the identifier and thus filename for each config object is its class's full name, lowercased and with `\` replaced by `_`. So `My\App\Config\EditorSettings` would become `my_app_config_editorsettings.yaml` (or whatever file format).

That is frequently not a nice filename. However, you may customize the key via an attribute, like so:

```
use Crell\Config\Config;

#[Config(key: 'editor_settings')]
class EditorSettings
{
    public function __construct(
        public readonly string $color,
        public readonly string $bgcolor,
        public readonly int $fontSize = 14,
    ) {}
}
```

Now, this class's filename will be `editor_settings.yaml` (and similar).

Complex objects
---------------

[](#complex-objects)

Config Loader uses the highly powerful and flexible [`Crell/Serde`](https://github.com/Crell/Serde) library to hydrate the config objects. That means all of Serde's flexibility and power is available via attributes. See Serde's documentation for all of the possible options, but especially its ability to collect properties up into a sub-object, add or remove prefixes, fold the case of different properties between camelCase and snake\_case, and more. You can also use post-load callback methods for validation to enforce rules beyond what the type system can handle.

If you do not pass a Serde instance to LayeredLoader, one will be created automatically. For simple usage that is fine, but if wiring the config loader into a Dependency Injection container it is better to inject a managed Serde instance instead.

Caching
-------

[](#caching)

While Serde is reasonably fast, it still has a cost. If you're loading many configuration objects then the time could add up.

Config Loader ships with two cache wrappers that can be easily wrapped around `LayeredLoader`.

- `Psr6CacheConfigLoader` - Feed it a configured [PSR-6](https://www.php-fig.org/psr/psr-6/) Pool object and an instance of `LayerdLoader` (or, really, any `ConfigLoader` object) and it will transparently cache each config object as it's loaded.
- `SerializedFiesystemCache` - The PSR-6 wrapper has the limitation that you need to have your cache backend already booted up, and Configuration is usually loaded very early in a request. Instead, you can use a file system cache that saves each object as a PHP serialized value on disk. All it requires is a path. This is the fastest possible option, and recommended for most configurations.

Note: Make certain the directory where the file cache is stored is not publicly accessible and secured tightly. Deserializing PHP objects is fast, but also a potential security vulnerability. Never allow anything but the cache wrapper to write to that directory.

Example:

YAML config example*Note: To use YAML config files, you need to have [`symfony/yaml`](https://packagist.org/packages/symfony/yaml) installed.*

```
use Crell\Config\LayeredLoader;
use Crell\Config\YamlFileSource;
use Crell\Config\SerializedFilesystemCache;

$loader = new LayeredLoader([
  new YamlFileSource('./config/common'),
  new YamlFileSource('./config/' . APP_ENV),
]);

$cachedLoader = new SerializedFilesytemCache($loader, '/path/to/cache/dir');

$cachedLoader->load(EditorSettings::class);
```

JSON config example```
use Crell\Config\LayeredLoader;
use Crell\Config\JsonFileSource;
use Crell\Config\SerializedFilesystemCache;

$loader = new LayeredLoader([
  new JsonFileSource('./config/common'),
  new JsonFileSource('./config/' . APP_ENV),
]);

$cachedLoader = new SerializedFilesytemCache($loader, '/path/to/cache/dir');

$cachedLoader->load(EditorSettings::class);
```

Dependency Injection
--------------------

[](#dependency-injection)

What Config Loader is optimized for is wiring into a Dependency Injection Container. Specifically, it can be used as a factory to produce objects of each config type, which can then be exposed to the container's autowiring functionality.

Consider the following service class:

```
class EditorForm
{
    public function __construct(
        private EditorSettings $settings,
    ) {}

    public function renderForm(): string
    {
        // Do stuff here.
        $this->settings->color;

        ...
    }
}
```

It depends on an `EditorSettings` instance. How it gets it, no one cares. But it can rely on all the guarantees that PHP provides around type safety, values being defined, autocomplete in your IDE, etc.

You can now trivially test that service in a test by making your own `EditorSettings` instance and passing it in:

```
class EditorFormTest extends TestCase
{
    #[Test]
    public function some_test(): void
    {
        $settings = new EditorSettings(color: '#fff', bgcolor: '#000');

        $subject = new EditorForm($settings);

        // Make various assertions.
    }
}
```

For the running application, you would register each config object as a service, keyed by its class name, and define it to load as a factory call to the Config Loader service. Now, the container's autowiring will automatically create, and cache, each config object as it's needed and inject it into services that need it, just like any other service.

For example, in Laravel you could do something like this:

```
namespace App\Providers;

use Crell\Config\ConfigLoader;
use Crell\Config\LayeredLoader;
use Crell\Config\PhpFileSource;
use Crell\Config\SerializedFilesystemCache;
use Crell\Serde\Serde;
use Crell\Serde\SerdeCommon;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\ServiceProvider;

class ConfigServiceProvider extends ServiceProvider
{
    public $singletons = [
        // Wire up Serde first.  See its documentation for more
        // robust ways to configure it.
        Serde::class => SerdeCommon::class,
    ];

    public function register(): void
    {
        // Set up some sources.
        $this->app->singleton('base_config', fn(Application $app)
            => new PhpFileSource('config/base')
        );
        $this->app->singleton('env_config', fn(Application $app)
            => new PhpFileSource('config/'. APP_ENV)
        );

        // Register the loader, and wrap it in a cache.
        $this->app->singleton(LayeredLoader::class, fn(Application $app)
            => new LayeredLoader(
                [$app['base_config'], $app['env_config']],
                $app[Serde::class],
            )
        );
        $this->app->singleton(ConfigLoader::class, fn(Application $app)
            => new SerializedFilesystemCache($app[LayeredLoader::class], 'cache/config')
        );

        // Now register the config objects.
        // You could also use a compiler pass to discover these from disk and
        // auto-register them, if your framework has that ability.
        $this->app->singleton(EditorSettings::class, fn(Application $app)
            => $app[ConfigLoader::class]->load(EditorSettings::class);
    }
}
```

Now, the first time a service that wants `EditorSettings` is loaded (such as `EditorForm`), the `EditorSettings` config object service will be created, populated, cached to disk, and cached in memory by the container as a singleton. All transparently! Any service that wants `EditorSettings` can simply declare a constructor dependency, and it's done. On subsequent loads, the cached version will be loaded from disk for even more speed.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](CONTRIBUTING.md) and [CODE\_OF\_CONDUCT](CODE_OF_CONDUCT.md) for details.

Security
--------

[](#security)

If you discover any security related issues, please email larry at garfieldtech dot com instead of using the issue tracker.

Credits
-------

[](#credits)

- [Larry Garfield](https://github.com/Crell)
- [All Contributors](../../contributors)

License
-------

[](#license)

The Lesser GPL version 3 or later. Please see [License File](LICENSE.md) for more information.

###  Health Score

34

—

LowBetter than 75% of packages

Maintenance42

Moderate activity, may be stable

Popularity20

Limited adoption so far

Community15

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 76.9% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~245 days

Total

3

Last Release

706d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/12e28c223b88445f07d697c8805bd856066c947f70b535f6a7e00d2cb311c3c2?d=identicon)[Crell](/maintainers/Crell)

---

Top Contributors

[![Crell](https://avatars.githubusercontent.com/u/254863?v=4)](https://github.com/Crell "Crell (40 commits)")[![JanWennrich](https://avatars.githubusercontent.com/u/42703348?v=4)](https://github.com/JanWennrich "JanWennrich (10 commits)")[![elazar](https://avatars.githubusercontent.com/u/15487?v=4)](https://github.com/elazar "elazar (2 commits)")

---

Tags

configuration

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/crell-config/health.svg)

```
[![Health](https://phpackages.com/badges/crell-config/health.svg)](https://phpackages.com/packages/crell-config)
```

###  Alternatives

[symfony/options-resolver

Provides an improved replacement for the array\_replace PHP function

3.2k525.7M2.0k](/packages/symfony-options-resolver)[league/config

Define configuration arrays with strict schemas and access values with dot notation

565335.0M36](/packages/league-config)[grasmash/expander

Expands internal property references in PHP arrays.

14064.3M10](/packages/grasmash-expander)[josegonzalez/dotenv

dotenv file parsing for PHP

28010.4M164](/packages/josegonzalez-dotenv)[dflydev/dot-access-configuration

Given a deep data structure representing a configuration, access configuration by dot notation.

13414.5M4](/packages/dflydev-dot-access-configuration)[symfony/requirements-checker

Check Symfony requirements and give recommendations

2025.0M43](/packages/symfony-requirements-checker)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
