symfony/config
Symfony Config component helps you find, load, merge, auto-fill, and validate configuration from many sources (YAML, XML, INI, databases, etc.). Provides tools for building robust, consistent configuration handling in PHP apps and libraries.
Installation:
composer require symfony/config
Add to composer.json if using standalone:
"require": {
"symfony/config": "^8.0"
}
First Use Case: Define a configuration tree for a Laravel package or app module. Example:
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class AppConfig implements ConfigurationInterface
{
public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('app');
$rootNode = $treeBuilder->getRootNode();
$rootNode
->children()
->scalarNode('name')
->isRequired()
->cannotBeEmpty()
->end()
->arrayNode('features')
->useAttributeAsKey('name')
->prototype('array')
->children()
->booleanNode('enabled')->defaultTrue()->end()
->end()
->end()
->end()
->end();
return $treeBuilder;
}
}
Loading Configuration:
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Loader\LoaderResolver;
use Symfony\Component\Config\Loader\DelegatingLoader;
use Symfony\Component\Config\Loader\YamlFileLoader;
$locator = new FileLocator(__DIR__.'/config');
$resolver = new LoaderResolver([
new YamlFileLoader($locator, 'yaml'),
]);
$loader = new DelegatingLoader($resolver);
$config = $loader->load('app.yaml');
TreeBuilder and ConfigurationInterface for defining schemas.Loader classes (YamlFileLoader, XmlFileLoader, etc.) for parsing files.Definition nodes (scalarNode, arrayNode, booleanNode) for structuring config.Use TreeBuilder to define hierarchical config structures with validation rules.
Example: Laravel Package Config
$treeBuilder = new TreeBuilder('my_package');
$rootNode = $treeBuilder->getRootNode();
$rootNode
->children()
->arrayNode('database')
->addDefaultsIfNotSet()
->children()
->scalarNode('connection')->defaultValue('mysql')->end()
->scalarNode('host')->defaultValue('%env(DATABASE_HOST)%')->end()
->integerNode('port')->min(1024)->max(65535)->defaultValue(3306)->end()
->end()
->end()
->arrayNode('cache')
->prototype('scalar')->end()
->end()
->end();
Combine YAML, XML, PHP arrays, or environment variables:
$loader = new DelegatingLoader([
new YamlFileLoader($locator, 'yaml'),
new XmlFileLoader($locator, 'xml'),
new PhpFileLoader($locator, 'php'),
]);
$config = $loader->load([
'yaml' => 'config/packages.yaml',
'env' => ['DATABASE_URL' => getenv('DATABASE_URL')],
]);
Use ParameterBag or Dotenv to merge env vars into config:
use Symfony\Component\Dotenv\Dotenv;
$dotenv = new Dotenv();
$dotenv->load(__DIR__.'/../.env');
$configurator = new \Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator();
$configurator->rootNode()
->children()
->scalarNode('api_key')
->defaultValue('%env(API_KEY)%')
->end()
->end();
Leverage built-in validators for types, ranges, and patterns:
$rootNode
->scalarNode('timeout')
->validate()
->ifTrue(function ($value) { return $value < 1; })
->thenInvalid('Timeout must be at least 1 second.')
->end()
->end();
Use beforeNormalization/afterNormalization for runtime logic:
$rootNode->beforeNormalization()->ifTrue(function ($v) {
return isset($v['features']['debug']) && $v['features']['debug'];
})->then(function ($v) {
$v['features']['debug']['enabled'] = true;
return $v;
});
Integrate with Laravel’s config() helper:
// In a Service Provider
public function boot()
{
$config = (new AppConfig())->getConfigTreeBuilder()->buildTree();
$config->setRootNode(new \Symfony\Component\Config\Definition\Node(
$config->getRootNode()->normalize($this->app['config']->all())
));
$this->app->singleton('my_package.config', function () use ($config) {
return $config->getRootNode()->getValue();
});
}
Circular References:
Avoid recursive config definitions (e.g., nested arrayNode with same key).
Fix: Use ->ignoreExtraKeys() or flatten structures.
Case Sensitivity:
YAML keys are case-sensitive. Use ->normalizeKey(function ($key) { return strtolower($key); }) if needed.
Default Values vs. Required Fields:
A node cannot have both isRequired() and defaultValue(). Choose one.
Environment Placeholders:
Use %env(KEY)% syntax, but ensure the env var exists or provide a fallback:
->defaultValue('%env(APP_DEBUG)%')->defaultFalse()
ArrayNode Prototype Issues:
If using ->prototype(), ensure keys are unique (e.g., with useAttributeAsKey('name')).
Deprecated Features (Symfony 8+):
getXsdValidationBasePath() and getNamespace() in ExtensionInterface are deprecated.Dump Raw Config:
$config = $loader->load('config.yaml');
dump($config->getRootNode()->getValue());
Validate Schema:
Use ->validate() with custom callbacks to catch issues early:
$rootNode->validate()
->ifTrue(function ($v) { return empty($v['database']['host']); })
->thenInvalid('Database host is required.');
Check for Unreachable Paths: Symfony 7.3+ auto-detects unreachable config paths. Enable strict mode:
$treeBuilder->setStrictMode(true);
Environment-Specific Configs:
Use LoaderResolver to load environment-specific files:
$resolver = new LoaderResolver([
new YamlFileLoader($locator, 'yaml'),
]);
$loader = new DelegatingLoader($resolver, 'app.'.app()->environment().'.yaml');
Custom Loaders:
Extend FileLoader for proprietary formats (e.g., JSON5, TOML):
class Json5FileLoader extends FileLoader
{
protected function load($resource, $type = null)
{
return json_decode(file_get_contents($resource), true, 512, JSON5_THROW_ON_ERROR);
}
}
Dynamic Node Factories:
Use NodeFactory to create nodes programmatically:
$factory = new NodeFactory();
$node = $factory->createNode(
new ArrayNode('dynamic'),
['key' => 'value']
);
Post-Processing: Attach listeners to modify config after loading:
$configurator = new DefinitionConfigurator();
$configurator->rootNode()
->afterNormalization()
->then(function ($v) {
$v['processed_at'] = now()->toDateTimeString();
return $v;
});
Laravel Service Provider Integration: Cache compiled config to avoid reprocessing:
$this->app->singing('config.cache', function () {
return Cache::remember('config.my_package', now()->addHours(1), function () {
return $configurator->getRootNode()->getValue();
});
});
Compile Config Schemas:
Generate PHP classes from TreeBuilder at build time (e.g., using symfony/flex recipes).
Memoize Loaders: Cache loaded
How can I help you explore Laravel packages today?