I have created a Symfony 5.3+ bundle which should be used to add common code to different projects. The bundle contains some services which should be configurable using parameters / options as described in the Symfony docs.
How to provide default values for these options? Defaults set in the bundles Configuration.php do not have any effect.
Details:
I have created a bundle project using the following structure and added it to my Symfony project using composer:
path/to/bundles/XYCommonsBundle/
config/
services.yaml
src/
Service/
SomeService.php
DependencyInjection
Configuration.php
XYCommensExtension.php
XYCommensBundle.php
composer.json
...
// src/DependencyInjection/XYCommensExtension.php
<?php
namespace XY\CommensBundle\DependencyInjection;
use ...
class XYCommensExtension extends Extension {
public function load(array $configs, ContainerBuilder $container) {
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
// make config available as parameters. Necessary?
foreach ($config as $key => $value) {
$container->setParameter('xy_commons.' . $key, $value);
}
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../../config'));
$loader->load('services.yaml');
}
}
// src/DependencyInjection/Configuration.php
class Configuration implements ConfigurationInterface {
public function getConfigTreeBuilder() {
$treeBuilder = new TreeBuilder('xy_commons');
$treeBuilder->getRootNode()
->children()
->arrayNode('params')
->children()
->integerNode('paramA')->defaultValue(100)->end()
->integerNode('ParamB')->defaultValue(200)->end()
->end()
->end()
->end()
;
return $treeBuilder;
}
}
// config/services.yaml
services:
xy_commons.service.some_service:
class: XY\CommonsBundle\Service\SomeService
arguments:
- $paramA: '%xy_commons.params.paramA%'
- $paramB: '%xy_commons.params.paramB%'
// src/Service/SomeService.php
<?php
namespace XY\CommensBundle\Service;
use ...
class SomeService {
public function __construct(LoggerInterface $logger, $paramA, $paramB) {
}
Problem: How to use default values of parameters?
paramA and paramB are defined in the bundles Configuration.php with default values of 100 and 200. I would like to use these defaults in the project without specifying custom values. However, I do not create a config/packages/xy_commons.yaml file in the project and explicitly specify values, I get the following error:
You have requested a non-existent parameter
"xy_commons.params.paramA".
When creating a config/packages/xy_commons.yaml file, I cannot use ~ to use the default value:
xy_commons:
params:
paramA: ~
paramB: ~
Invalid type for path "xy_commons.params.paramA". Expected "int", but
got "null".
Only when explicitly specifying a value it works:
xy_commons:
params:
paramA: 300
paramB: 400
How to use the default values defined in Configuration.php?
There are three problems here. The first deals with what is possibly my least favorite class in Symfony. The dreaded configuration object. In particular, you need to use addDefaultsIfNotSet when dealing with arrays:
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('my');
$rootNode = $treeBuilder->getRootNode();
$rootNode
->children()
->arrayNode('params')->addDefaultsIfNotSet() # ADD THIS
->children()
->integerNode('paramA')->defaultValue(100)->end()
->integerNode('paramB')->defaultValue(200)->end()
->end()
->end()
->end()
;
return $treeBuilder;
}
}
The second problem is that you are not defining your parameters correctly. In particular the parameters will be grouped in an array called params. You almost had it:
class MyExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
foreach($config['params'] as $key => $value) {
$container->setParameter('xy_commons.params.' . $key, $value);
}
# Use bin/console dump:container --parameters to verify
The final thing is the use of tilde to indicate default values. I think this might be a works as designed issue. Technically, tilde on yaml means null and it is up to the processor to get it a meaning. The ArrayNode works as expected but the IntegerNode does not. So this works:
# config/packages/my.yaml
my:
params: ~
# paramA: ~
# paramB: 666
Related
I'm trying to create a default configuration for my bundle. I created the bundle, bundle extension and configuration class. Inside the configuration class, I define a couple of keys with a default value. No yaml files are created. When I try to dump the configuration, I got the error:
"The extension with alias "foxtrot_alpha_users" does not have configuration."
But if I create the matching yaml file, and define at least one of the keys, the other key takes the default value as stated in the configuration class the value can be overridden.
Is it possible to define a default configuration with no yaml file at all?
Bundle class:
class UsersBundle extends Bundle
{
/**
* this is to have a custom alias
*
* #return void
*/
public function getContainerExtension()
{
return( new UsersExtension() );
}
}
Extension class:
class UsersExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
foreach($config as $key => $value)
{
$parameterKey = $this->getAlias().'.'.$key;
$container->setParameter($parameterKey, $value);
}
$container->setParameter('default_password', $config['default_password']);
}
/**
* customized alias
*
* #return string
*/
public function getAlias()
{
return('foxtrot_alpha_users');
}
Configuration class:
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('foxtrot_alpha_users');
$rootNode = $treeBuilder->getRootNode();
$this->addUserParameters( $rootNode );
return $treeBuilder;
}
private function addUserParameters( ArrayNodeDefinition $rootNode )
{
$rootNode
->addDefaultsIfNotSet()
->children()
->scalarNode('default_password')
->defaultValue('248')
->end()
->scalarNode('x')
->defaultValue('1')
->end()
->end()
;
return( $rootNode );
}
I'm running a website under Symfony 3.4.12 and I created my own custom bundle. I have a custom config file in Yaml :
# src/CompanyBundle//Resources/config/config.yml
company_bundle:
phone_number
... and it is launched this way :
<?php
# src/CompanyBundle/DependencyInjection/CompanyExtension.php
namespace CompanyBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
class CompanyExtension extends Extension
{
/**
* {#inheritDoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('config.yml');
}
}
?>
I would like to retrieve my custom parameters in my controller file, what is the best way to do it ? I tried this way, with no success :
$this->getParameter('company_bundle.phone_number')
Thanks.
You have to define your own DependencyInjection/Configuration.php: http://symfony.com/doc/3.4/bundles/configuration.html#processing-the-configs-array
Like that:
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('company_bundle');
$rootNode
->children()
->scalarNode('phone_number')
->end()
->end()
;
}
And then process it into your DependencyInjection/...Extension.php file. If you want to make this option as parameter you have to do it like that:
public function load(array $configs, ContainerBuilder $container)
{
// Some default code
$container->setParameter('company_bundle.phone_number', $config['phone_number']);
}
And then you can get this parameter in your controller like you do.
I'm trying to import a yaml configuration file in my App following the documentation provided here http://symfony.com/doc/current/bundles/extension.html but I always have the error message:
There is no extension able to load the configuration for "app"
My file is located here : config/packages/app.yaml and has the following structure :
app:
list:
model1:
prop1: value1
prop2: value2
model2:
...
As this is a simple App, all the files are in src/. So I have src/DependencyInjection/AppExtension.php
<?php
namespace App\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
class AppExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
}
}
And src/DependencyInjection/Configuration.php
<?php
namespace App\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('app');
// Node definition
$rootNode
->children()
->arrayNode('list')
->useAttributeAsKey('name')
->requiresAtLeastOneElement()
->prototype('array')
->children()
->requiresAtLeastOneElement()
->prototype('scalar')
->end()
->end()
->end()
->end()
->end();
return $treeBuilder;
}
}
I'm not able to access my parameters :(
Any idea?
If you want to load a custom configuration file to process it's parameters using an Extension class (like in Symfony bundle extension but without to create a bundle), to eventually "create" and add one or more of it to the "container" (before it will be compiled) you can register your Extension class manually in the configureContainer method contained in the Kernel.php file:
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader)
{
// to avoid the same error you need to put this line at the top
// if your file is stored under "$this->getProjectDir().'/config'" directory
$container->registerExtension(new YourAppExtensionClass());
// ----- rest of the code
}
Then you can use your params as usual registering a Compiler Pass.
Hope this helps.
There's something that I'm missing.
Let's say I have a database from wich I start a new project. This database will be created after restoring a .dump file so it will also contain some "static" (static inserts from scratch db to populate it with "unchangeable" data) information inside it.
As this information will never change and as I need some of them (i.e.: an id for retrieve exactly a specific entity) I've thought to place them into a configuration file that is processed with the DIC & co.
First question: is this approach right or is better to create a specific .php file with some configuration values?
Second question: Why parameters I've defined through DIC aren't available?
I've create a folder called DependencyInjection inside my bundle, I've created MyCompanyMyBundleNameExtension file and create a Configuration file. I'm sure that files are placed in the right place as If I "remove" them all the things will mess up (just for people who will comment with "are you sure that ..." yes, I'm sure) I've also included inside my bundle "main" file the following
public function build(ContainerBuilder $container)
{
parent::build($container);
}
but if I dump (I use this approach as inside controller the exception "The parameter "xxxxx" must be defined. is raised") the config file inside Extension I can notice that is empty. Have I missing something?
UPDATE
This is Configuration.php file
namespace Foo\FooBundle\DependencyInjection;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
class Configuration implements ConfigurationInterface
{
/**
* Generates the configuration tree.
*
* #return TreeBuilder
*/
public function getConfigTreeBuilder()
{
$tree_builder = new TreeBuilder();
$root_node = $tree_builder->root('foo_bundle');
$root_node
->children()
->arrayNode('text_type')
->children()
->integerNode('title')->defaultValue(1)->end()
->integerNode('description')->defaultValue(2)->end()
->end()
->end()
->end()
;
return $tree_builder;
}
}
and this is the Extension
<?php
namespace Foo\BookingEngineBundle\DependencyInjection;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\Definition\Processor;
class FooFooBundleExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$processor = new Processor();
$configuration = new Configuration();
$config = $processor->processConfiguration($configuration, $configs);
error_log(print_r($config, true)); //empty
$this->remapParametersNamespaces($config, $container, array(
'' => array(
'text_type' => 'foo_bundle.text_type.%s',
),
));
}
protected function remapParametersNamespaces(array $config, ContainerBuilder $container, array $namespaces)
{
foreach ($namespaces as $ns => $map) {
if ($ns) {
if (!array_key_exists($ns, $config)) {
continue;
}
$namespaceConfig = $config[$ns];
} else {
$namespaceConfig = $config;
}
if (is_array($map)) {
$this->remapParameters($namespaceConfig, $container, $map);
} else {
foreach ($namespaceConfig as $name => $value) {
$container->setParameter(sprintf($map, $name), $value);
}
}
}
}
protected function remapParameters(array $config, ContainerBuilder $container, array $map)
{
foreach ($map as $name => $paramName) {
if (array_key_exists($name, $config)) {
$container->setParameter($paramName, $config[$name]);
}
}
}
UPDATE 2
If I run from command line the following php app/console config:dump-reference foo_bundle I can see configuration printed out. I'm pretty confused.
UPDATE 3
I got it finally:
it seems that you need to specify at least one parameter into your config.yml main file, otherwise the "merge" option performed into YourBundleNameExtension will fail and return an empty array.
Subquestion: there isn't a method that I can follow to free me from write parateter into parameters.yml?
I got it finally:
it seems that you need to specify at least one parameter (related to your Config D.I. file) into your config.yml main file, otherwise the "merge" option performed into YourBundleNameExtension will fail and return an empty array.
Looking at my Configuration.php file, in order to make the code works without specify parameter directly into parameters.yml file, modify Configuration.php that way:
<?php
namespace Koobi\BookingEngineBundle\DependencyInjection;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
class Configuration implements ConfigurationInterface
{
/**
* Generates the configuration tree.
*
* #return TreeBuilder
*/
public function getConfigTreeBuilder()
{
$tree_builder = new TreeBuilder();
$root_node = $tree_builder->root('koobi_booking_engine');
$root_node
->children()
->arrayNode('text_type')
->cannotBeOverwritten()
->addDefaultsIfNotSet()
->children()
->integerNode('title')->defaultValue(1)->end()
->integerNode('description')->defaultValue(2)->end()
->end()
->end()
->end()
;
return $tree_builder;
}
}
the key is ->addDefaultsIfNotSet() builder's method
How do I went to solution?
Look at Extension file: you should notice the
$config = $processor->processConfiguration($configuration, $configs);
snippet of code.
If you open use Symfony\Component\Config\Definition\Processor; and analyze processConfiguration() method, you'll see that $configs - that represent config.yml content - will be merged to $configuration (that represent Configuration.php, so the DIC file that extenion will manage).
So the idea that a common key should be present somewhere came in my mind: after that successful check I've started to search for some methodology that helps me avoid the explicit process of writing parameters into config.yml (that wasn't what i want) and now all works like a charm
SIDE NOTE
I still don't know if this is a valid approach so I'm not gonna check this as correct answer.
I create my own FacebookBundle and
I got this error:
There is no extension able to load the configuration for
"facebookbundle" (in /facebookx/app/config/config_dev.yml). Looked for
namespace "facebookbundle", found "framework", "security", "twig",
"monolog", "swiftmailer", "assetic", "doctrine",
"sensio_framework_extra", "jms_aop", "jms_di_extra",
"jms_security_extra", "d_facebook", "d_user", "d_security",
"web_profiler", "sensio_distribution"
The error message means that I got an entry "facebookbundle" in My config.yml which is not used by any extension ?
My config.yml
facebookbundle:
file: %kernel.root_dir%/../src/FacebookBundle/Facebook/FacebookInit.php
alias: facebook
app_id: xxx
secret: xxx
cookie: true
permissions: [email, user_birthday, user_location, user_about_me]
My DFacebookExtension
<?php
namespace D\FacebookBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
/**
* This is the class that loads and manages your bundle configuration
*
* To learn more see {#link http://symfony.com/doc/current/cookbook/bundles/extension.html}
*/
class DFacebookExtension extends Extension
{
/**
* {#inheritDoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
foreach (array('app_id', 'secret', 'cookie', 'permissions') as $attribute) {
$container->setParameter('facebookbundle.'.$attribute, $config[$attribute]);
}
if (isset($config['file']) && $container->hasDefinition('acebookbundle.api')) {
$facebookApi = $container->getDefinition('facebookbundle.api');
$facebookApi->setFile($config['file']);
}
}
}
were is error ?
Also, keep in mind that the root key of the configuration file must be a normalized form of the Bundle's name.
This is something I've encoutered a few times and it's very frustrating to solve if you're not aware of it.
Example: if bundle is called MyFirstAwesomeBundle, then the root key in the file must be my_first_awesome. So camel-case is converted to snake-case and the word "bundle" is ignored or stripped away.
So simply having the root key in your file match exactly the value specified in Configuration::getConfigTreeBuilder() is not enough.
If you don't follow this rule, then you'll get the There is no extension able to load the configuration error.
I hope this will help the next desperate soul who ends up on this page...
In order for custom config parameters to be accepted you have to define your bundle configuration using a Configuration.php class within your bundle.
src/FacebookBundle/DependencyInjection/Configuration.php:
<?php
namespace FacebookBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
* This is the class that validates and merges configuration from your app/config files
*
* To learn more see {#link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class}
*/
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('facebookbundle');
$rootNode
->children()
->scalarNode('file')->defaultValue('')->end()
->scalarNode('alias')->defaultValue('')->end()
->scalarNode('app_id')->defaultValue('')->end()
->scalarNode('secret')->defaultValue('')->end()
->booleanNode('cookie')->defaultTrue()->end()
->arrayNode('permissions')
->canBeUnset()->prototype('scalar')->end()->end()
->end()
;
return $treeBuilder;
}
}
?>
This happens when you forget to start the bundle in app/AppKernel.php :
<?php
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array (
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
new Symfony\Bundle\TwigBundle\TwigBundle(),
new Symfony\Bundle\MonologBundle\MonologBundle(),
new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
new Symfony\Bundle\DoctrineBundle\DoctrineBundle(),
new Symfony\Bundle\AsseticBundle\AsseticBundle(),
new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
new JMS\SecurityExtraBundle\JMSSecurityExtraBundle(),
//...
new D\FacebookBundle\DFacebookBundle(),
//...
);
if (in_array($this->getEnvironment(), array ('dev', 'test')))
{
$bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
$bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
$bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
}
return $bundles;
}
public function registerContainerConfiguration(LoaderInterface $loader)
{
$loader->load(__DIR__ . '/config/config_' . $this->getEnvironment() . '.yml');
}
}
This error can also be caused by a missing or invalid services key at the root of your config. It should look something like this:
services:
foo_bar.baz:
class: Foo\BarBundle\Service\BazService