slick/configuration
Configuration management for Laravel: load, merge, validate, and access app settings through a clean, typed API. Cache config values, support environment-specific overrides, and keep settings organized in one place.
1. Install and read in this order
composer require slick/configuration
README.md — the only real spec; read it fully before writing code.src/Configuration.php — the facade you will use 95% of the time.src/Driver/ — one class per file format (PhpDriver, IniDriver, YamlDriver, JsonDriver, etc.). This directory tells you what is actually supported and how merge/namespace behaviour works.tests/ — when the README is ambiguous, the tests are the real contract for dot-notation, defaults, and load order.2. First config file
Create a PHP file that returns an array — this is the lowest-friction driver and needs no extra parser dependency:
// config/app.php
return [
'debug' => true,
'database' => [
'host' => '127.0.0.1',
'name' => 'app',
],
];
3. First use case — load a group and read with dot notation
use Slick\Configuration\Configuration;
$config = new Configuration();
$config->load(__DIR__ . '/config/app.php', 'app');
$debug = $config->get('app.debug', false);
$host = $config->get('app.database.host', 'localhost');
That is the whole day-one workflow: load a file under a namespace, read values by dotted path, always pass a default. Confirm exact method signatures against the README — the package is small and the API surface is narrow, so there is no reason to guess.
4. Programmatic overrides
$config->set('app.database.host', 'db.internal');
Use this for tests and for environment-specific overrides loaded after the base file.
Namespace per file, one file per concern. Load app.php as app, db.php as db, mail.php as mail. Flat, prefixed keys keep get() calls readable and make it obvious where a value comes from. Avoid one giant config.php.
Environment layering by load order. Keep a base file and a local override, and load the override second (assuming last-loaded wins — verify this in the driver tests before relying on it):
$config->load('config/app.php', 'app');
if (is_file('config/app.local.php')) {
$config->load('config/app.local.php', 'app'); // overrides
}
$config->set('app.debug', getenv('APP_DEBUG') === '1');
Explicit ordering beats implicit merging. If merge semantics are shallow, re-load the whole group rather than relying on per-key merging.
Wrap it in your own interface. This is the single most valuable pattern for a 0-star dependency:
interface AppConfig
{
public function debug(): bool;
public function dbHost(): string;
}
Bind the Slick-backed implementation in your container. Your application code never imports Slick\Configuration\Configuration, so swapping the library later is a one-file change. It also gives you one place to cast, validate, and fail fast on missing required keys.
Consume via constructor injection, not statics. If the package exposes a singleton/initialize() helper, avoid it in application code. Pass a ConfigurationInterface into services so tests can supply a hand-built instance or a mock. Keep a single factory that builds the fully-loaded config object at bootstrap and injects it.
Hydrate typed DTOs at the edge. Read raw values in a factory and construct value objects:
final class DatabaseConfig
{
public static function fromConfiguration(ConfigurationInterface $c): self
{
return new self(
host: (string) $c->get('app.database.host', 'localhost'),
port: (int) $c->get('app.database.port', 3306),
);
}
}
Config arrays are untyped; convert once, then work with real types.
Custom drivers are the extension point. If you need .env or .toml, implement the package's DriverInterface (see src/Driver/ for the contract) and register/map it for your extension. Keep such drivers in your own codebase, not in a fork.
Testing. Build a Configuration in the test (or a small fake implementing the interface), set() the keys the subject needs, assert behaviour. Never load real config files in unit tests; do that in one integration test for the loader itself.
foo.bar is indistinguishable from nested foo => bar. Enforce "no dots in key names" in your config files, or the lookup will silently return the wrong thing.load() on a .php file includes it. Never pass a user-controlled or writable path; treat config files as trusted source code and keep them out of the web root.null, throws, or is a bug. Pass explicit defaults everywhere and use has() for optional sections. A typo in a dotted path looks identical to an intentionally absent value.${VAR} inside values. Do the substitution yourself in the factory, or keep secrets in getenv() and inject them with set()..YML) and what happens for an unknown extension. Prefer explicit driver selection over magic when it is offered.Configuration in your bootstrap/container or precompile to a PHP array in your deploy step.initialize() API, reset it in tearDown() or you will chase ghost state. Constructor-based instances avoid this entirely.composer.json, keep the wrapper interface described above, and add one small test asserting the behaviours you rely on (dot access, defaults, override order). That test is your upgrade safety net if the package is abandoned.is_file before load), and the order. Most "broken config" reports are a wrong path or a later load clobbering an earlier one.How can I help you explore Laravel packages today?