{"id":16904422,"url":"https://github.com/crell/config","last_synced_at":"2025-03-22T10:31:05.967Z","repository":{"id":147152379,"uuid":"618170695","full_name":"Crell/Config","owner":"Crell","description":"A simple but powerful configuration loader, based on classed objects.","archived":false,"fork":false,"pushed_at":"2024-09-25T14:25:21.000Z","size":86,"stargazers_count":7,"open_issues_count":2,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-18T10:21:29.281Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Crell.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["Crell"]}},"created_at":"2023-03-23T22:35:43.000Z","updated_at":"2024-09-25T14:25:22.000Z","dependencies_parsed_at":null,"dependency_job_id":"450ff5f3-2a00-44c2-a5a7-4b951072854c","html_url":"https://github.com/Crell/Config","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Crell%2FConfig","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Crell%2FConfig/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Crell%2FConfig/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Crell%2FConfig/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Crell","download_url":"https://codeload.github.com/Crell/Config/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244943724,"owners_count":20536290,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-13T18:33:17.950Z","updated_at":"2025-03-22T10:31:05.641Z","avatar_url":"https://github.com/Crell.png","language":"PHP","funding_links":["https://github.com/sponsors/Crell"],"categories":[],"sub_categories":[],"readme":"# Config Loader\n\n[![Latest Version on Packagist][ico-version]][link-packagist]\n[![Software License][ico-license]](LICENSE.md)\n[![Total Downloads][ico-downloads]][link-downloads]\n\nConfig Loader is what it says it is: A simple, fast, but powerful configuration loading system\nsuitable for any framework.\n\n## How it works\n\nConfig 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`.\n\nConfig 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.\n\nBecause each config object is its own class, it integrates seamlessly with a Dependency Injection Container and testing.  (See the section on DI below.)\n\nLet's see an example.\n\n\u003cdetails open\u003e\n\u003csummary\u003eYAML config example\u003c/summary\u003e\n\n_Note: To use YAML config files, you need to have [`symfony/yaml`](https://packagist.org/packages/symfony/yaml) installed._\n\n```php\nuse Crell\\Config\\LayeredLoader;\nuse Crell\\Config\\YamlFileSource;\n\nclass EditorSettings\n{\n    public function __construct(\n        public readonly string $color,\n        public readonly string $bgcolor,\n        public readonly int $fontSize = 14,\n    ) {}\n}\n\n$loader = new LayeredLoader([\n  new YamlFileSource('./config/common'),\n  new YamlFileSource('./config/' . APP_ENV),\n]);\n\n$editorConfig = $loader-\u003eload(EditorSettings::class);\n```\n\nGiven these files on disk:\n\n```yaml\n# config/common/editorsettings.yaml\ncolor: \"#ccddee\"\nbgcolor: \"#ffffff\"\n```\n\n```yaml\n# config/dev/editorsettings.yaml\nbgcolor: '#eeff00'\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eJSON config example\u003c/summary\u003e\n\n```php\nuse Crell\\Config\\LayeredLoader;\nuse Crell\\Config\\JsonFileSource;\n\nclass EditorSettings\n{\n    public function __construct(\n        public readonly string $color,\n        public readonly string $bgcolor,\n        public readonly int $fontSize = 14,\n    ) {}\n}\n\n$loader = new LayeredLoader([\n  new JsonFileSource('./config/common'),\n  new JsonFileSource('./config/' . APP_ENV),\n]);\n\n$editorConfig = $loader-\u003eload(EditorSettings::class);\n```\n\nGiven these files on disk:\n\n`config/common/editorsettings.json`:\n```json\n{\n    \"color\": \"#ccddee\",\n    \"bgcolor\": \"#ffffff\"\n}\n```\n\n`config/dev/editorsettings.json`:\n```json\n{\n  \"bgcolor\": \"#eeff00\"\n}\n```\n\n\u003c/details\u003e\n\nNow, 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`.\n\nThe 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.)\n\nYou 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.\n\n## Source types\n\nThere are several file formats supported out of the box, including `JsonFileSource`, `YamlFileSource`, `PhpFileSource`, and even `IniFileSource` (because why not?).  \nNote: In order to use the `YamlFileSource` you need to have the [`symfony/yaml`](https://packagist.org/packages/symfony/yaml) package installed. \n\nWriting other sources is simple, as the `ConfigSource`interface has only a single method.  \n\nYou 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.\n\n## Custom file keys\n\nBy 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).\n\nThat is frequently not a nice filename.  However, you may customize the key via an attribute, like so:\n\n```php\nuse Crell\\Config\\Config;\n\n#[Config(key: 'editor_settings')]\nclass EditorSettings\n{\n    public function __construct(\n        public readonly string $color,\n        public readonly string $bgcolor,\n        public readonly int $fontSize = 14,\n    ) {}\n}\n```\n\nNow, this class's filename will be `editor_settings.yaml` (and similar).\n\n## Complex objects\n\nConfig 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.\n\nIf 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.\n\n## Caching\n\nWhile Serde is reasonably fast, it still has a cost.  If you're loading many configuration objects then the time could add up.\n\nConfig Loader ships with two cache wrappers that can be easily wrapped around `LayeredLoader`.\n\n* `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.\n* `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.\n\nNote: 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.\n\nExample:\n\n\u003cdetails open\u003e\n\u003csummary\u003eYAML config example\u003c/summary\u003e\n\n_Note: To use YAML config files, you need to have [`symfony/yaml`](https://packagist.org/packages/symfony/yaml) installed._\n\n```php\nuse Crell\\Config\\LayeredLoader;\nuse Crell\\Config\\YamlFileSource;\nuse Crell\\Config\\SerializedFilesystemCache;\n\n$loader = new LayeredLoader([\n  new YamlFileSource('./config/common'),\n  new YamlFileSource('./config/' . APP_ENV),\n]);\n\n$cachedLoader = new SerializedFilesytemCache($loader, '/path/to/cache/dir');\n\n$cachedLoader-\u003eload(EditorSettings::class);\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eJSON config example\u003c/summary\u003e\n\n```php\nuse Crell\\Config\\LayeredLoader;\nuse Crell\\Config\\JsonFileSource;\nuse Crell\\Config\\SerializedFilesystemCache;\n\n$loader = new LayeredLoader([\n  new JsonFileSource('./config/common'),\n  new JsonFileSource('./config/' . APP_ENV),\n]);\n\n$cachedLoader = new SerializedFilesytemCache($loader, '/path/to/cache/dir');\n\n$cachedLoader-\u003eload(EditorSettings::class);\n```\n\u003c/details\u003e\n\n\n## Dependency Injection\n\nWhat 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.\n\nConsider the following service class:\n\n```php\nclass EditorForm\n{\n    public function __construct(\n        private EditorSettings $settings,\n    ) {}\n    \n    public function renderForm(): string\n    {\n        // Do stuff here.\n        $this-\u003esettings-\u003ecolor;\n        \n        ...\n    }\n}\n```\n\nIt 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.\n\nYou can now trivially test that service in a test by making your own `EditorSettings` instance and passing it in:\n\n```php\nclass EditorFormTest extends TestCase\n{\n    #[Test]\n    public function some_test(): void\n    {\n        $settings = new EditorSettings(color: '#fff', bgcolor: '#000');\n        \n        $subject = new EditorForm($settings);\n        \n        // Make various assertions.\n    }\n}\n```\n\nFor 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.\n\nFor example, in Laravel you could do something like this:\n\n```php\nnamespace App\\Providers;\n \nuse Crell\\Config\\ConfigLoader;\nuse Crell\\Config\\LayeredLoader;\nuse Crell\\Config\\PhpFileSource;\nuse Crell\\Config\\SerializedFilesystemCache;\nuse Crell\\Serde\\Serde;\nuse Crell\\Serde\\SerdeCommon;\nuse Illuminate\\Contracts\\Foundation\\Application;\nuse Illuminate\\Support\\ServiceProvider;\n \nclass ConfigServiceProvider extends ServiceProvider\n{\n    public $singletons = [\n        // Wire up Serde first.  See its documentation for more\n        // robust ways to configure it.\n        Serde::class =\u003e SerdeCommon::class,\n    ];\n    \n    public function register(): void\n    {\n        // Set up some sources.\n        $this-\u003eapp-\u003esingleton('base_config', fn(Application $app) \n            =\u003e new PhpFileSource('config/base')\n        );\n        $this-\u003eapp-\u003esingleton('env_config', fn(Application $app) \n            =\u003e new PhpFileSource('config/'. APP_ENV)\n        );\n        \n        // Register the loader, and wrap it in a cache.\n        $this-\u003eapp-\u003esingleton(LayeredLoader::class, fn(Application $app)\n            =\u003e new LayeredLoader(\n                [$app['base_config'], $app['env_config']],\n                $app[Serde::class],\n            )\n        );\n        $this-\u003eapp-\u003esingleton(ConfigLoader::class, fn(Application $app)\n            =\u003e new SerializedFilesystemCache($app[LayeredLoader::class], 'cache/config')\n        );\n        \n        // Now register the config objects.\n        // You could also use a compiler pass to discover these from disk and\n        // auto-register them, if your framework has that ability.\n        $this-\u003eapp-\u003esingleton(EditorSettings::class, fn(Application $app)\n            =\u003e $app[ConfigLoader::class]-\u003eload(EditorSettings::class);\n    }\n}\n```\n\nNow, 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.\n\n\n\n## Contributing\n\nPlease see [CONTRIBUTING](CONTRIBUTING.md) and [CODE_OF_CONDUCT](CODE_OF_CONDUCT.md) for details.\n\n## Security\n\nIf you discover any security related issues, please email larry at garfieldtech dot com instead of using the issue tracker.\n\n## Credits\n\n- [Larry Garfield][link-author]\n- [All Contributors][link-contributors]\n\n## License\n\nThe Lesser GPL version 3 or later. Please see [License File](LICENSE.md) for more information.\n\n[ico-version]: https://img.shields.io/packagist/v/Crell/Config.svg?style=flat-square\n[ico-license]: https://img.shields.io/badge/License-LGPLv3-green.svg?style=flat-square\n[ico-downloads]: https://img.shields.io/packagist/dt/Crell/Config.svg?style=flat-square\n\n[link-packagist]: https://packagist.org/packages/Crell/Config\n[link-scrutinizer]: https://scrutinizer-ci.com/g/Crell/Config/code-structure\n[link-code-quality]: https://scrutinizer-ci.com/g/Crell/Config\n[link-downloads]: https://packagist.org/packages/Crell/Config\n[link-author]: https://github.com/Crell\n[link-contributors]: ../../contributors\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrell%2Fconfig","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcrell%2Fconfig","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrell%2Fconfig/lists"}