Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating configurations in Symfony

Tags:

php

symfony

For a few hours now I've been struggling to do the most simple thing you can imagine and it just won't work. I've read tons of stackoverflow questions, read the complete Symfony documentation on configuration files and with every article or other piece of information I read, it gets harder and harder to understand.

Details

I've created my own Bundle. Lets call it HappyBundle. I've put this Bundle in my company's folder. So naturally I've got CompanyHappyBundle.

I want to make a configuration file specifically for this bundle as I want it to be reusable.

As I test i created the following:

# src/Company/HappyBundle/Resources/config/config.yml
company_happy:
    import:
        path: /tmp

Now, what I want is to be able to use this value in my Controller. I just don't know how. It throws me the following error:

[Symfony\Component\Config\Exception\FileLoaderLoadException]
There is no extension able to load the configuration for "company_happy" (in /home/user/symfony/src/Company/HappyBundle/Resources/config/config.yml).

Looked for namespace "company_happy", found "framework", "security", "twig", "monolog", "swiftmailer", "assetic", "doctrine", "sensio_framework_extra", "debug", "web_profiler", "sensio_distribution" in /home/user/symfony/src/Company/HappyBundle/Resources/config/config.yml (which is being imported from "/home/user/symfony/app/config/config.yml").

Update

In the config.yml I added the following:

#app/config/config.yml
imports:
    - { resource: "@CompanyHappyBundle/Resources/config/config.yml" }

I've also made a Configuration class because I read somewhere this was required. I really do think this is alot of work to make just one config file.

namespace Company\HappyBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

class Configuration implements ConfigurationInterface
{
    /**
     * {@inheritDoc}
     */
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();
        $rootNode = $treeBuilder->root('company_happy');

        $rootNode
            ->children()
            ->arrayNode('import')
                ->children()
                    ->scalarNode('attachments_path')->defaultValue('/tmp')->end()
                    ->scalarNode('method')->defaultValue('ALL')->end()
                    ->booleanNode('move_mail')->defaultValue(true)->end()
                    ->booleanNode('mark_read')->defaultValue(true)->end()
                ->end()
            ->end()
        ;

        return $treeBuilder;
    }
}

What I am actually looking for are the steps and requirements needed to get this working. The thing with symfony is that it has a million ways to do this. The documentation doesn't just give a workflow.

Can someone please help me out?

like image 921
Peter Avatar asked Sep 09 '15 14:09

Peter


People also ask

What is a service configurator in Symfony?

The service configurator is a feature of the service container that allows you to use a callable to configure a service after its instantiation. A service configurator can be used, for example, when you have a service that requires complex setup based on configuration settings coming from different sources/services.

Where is .ENV file Symfony?

Instead of defining env vars in your shell or your web server, Symfony provides a convenient way to define them inside a . env (with a leading dot) file located at the root of your project. The . env file is read and parsed on every request and its env vars are added to the $_ENV & $_SERVER PHP variables.

What is a Symfony bundle?

A Symfony bundle is a collection of files and folders organized in a specific structure. The bundles are modeled in such a way that it can be reused in multiple applications. The main application itself is packaged as a bundle and it is generally called AppBundle.

What is Symfony Service Container?

In Symfony, these useful objects are called services and each service lives inside a very special object called the service container. The container allows you to centralize the way objects are constructed. It makes your life easier, promotes a strong architecture and is super fast!


2 Answers

I have solved my own issue but not without trouble. I'm not at all pleased with Symfony's configuration system.

Step one - Create your config file

Create a file named config.yml in src/<bundle name>/Resources/config/

yourbundle:
    param_one: value_one
    param_two: value_two
    param_three: value_three
    param_four: value_four
    param_five:
        subparam_one: subvalue_one
        subparam_two: subvalue_two
        subparam_three: subvalue_three
        subparam_four: subvalue_four

Step two - Importing your configuration file

Go to app/config/config.yml and add:

#app/config/config.yml
imports:
    - { resource: "@YourBundle/Resources/config/config.yml" }

Step three - Create a configuration class

Create a file named Configuration.php in src/<bundle name>/DependencyInjection/

namespace YourBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

class Configuration implements ConfigurationInterface
{
    /**
     * {@inheritDoc}
     */
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();
        $rootNode = $treeBuilder->root('yourbundle');

        $rootNode
            ->children()
                ->scalarNode('param_one')->defaultValue('value_one')->end()
                ->scalarNode('param_two')->defaultValue('value_two')->end()
                ->scalarNode('param_three')->defaultValue('value_three')->end()
                ->scalarNode('param_four')->defaultValue('value_four')->end()
                ->arrayNode('param_five')
                    ->children()
                        ->scalarNode('subparam_one')->defaultValue('subvalue_one')->end()
                        ->scalarNode('subparam_two')->defaultValue('subvalue_two')->end()
                        ->scalarNode('subparam_three')->defaultValue('subvalue_three')->end()
                        ->scalarNode('subparam_four')->defaultValue('subvalue_four')->end()
                    ->end()
            ->end()
        ;

        return $treeBuilder;
    }
}

Step four - Creating an extension

Last but not least, you'll have to create an extension. Create a file <yourbundle>Extension.php in src/<your bundle>/DependencyInjection/

namespace YourBundle\DependencyInjection;

use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;

class YourbundleExtension extends Extension
{
    /**
     * @var ContainerBuilder
     */
    protected $container;

    /**
     * {@inheritDoc}
     */
    public function load(array $configs, ContainerBuilder $container)
    {
        $this->container = $container;

        $configuration = new Configuration();
        $config = $this->processConfiguration($configuration, $configs);

        foreach ($config as $key => $value) {
            $this->parseNode('yourbundle.'.$key, $value);
        }

        $container->setParameter('yourbundle', $config);
    }

    /**
     * @param string $name
     * @param mixed  $value
     *
     * @throws \Exception
     */
    protected function parseNode($name, $value)
    {
        if (is_string($value)) {
            $this->set($name, $value);

            return;
        }
        if (is_integer($value)) {
            $this->set($name, $value);

            return;
        }
        if (is_array($value)) {
            foreach ($value as $newKey => $newValue) {
                $this->parseNode($name.'.'.$newKey, $newValue);
            }

            return;
        }
        if (is_bool($value)) {
            $this->set($name, $value);

            return;
        }
        throw new \Exception(gettype($value).' not supported');
    }

    /**
     * @param string $key
     * @param mixed  $value
     */
    protected function set($key, $value)
    {
        $this->container->setParameter($key, $value);
    }
}

All these steps are required just to be able to call a configuration parameter specific for your bundle.

If any of you know any way to do this easier, feel free to post an answer or comment.

like image 163
Peter Avatar answered Sep 23 '22 14:09

Peter


A few notices:

In config.yml, you are trying to define import as array. It seems like symfony doesn't allow creating array elements in the root of your config, meaning that you have to nest arrays deeper down the tree. So you can not do:

company_happy:
    import:
        path: /tmp
    another_import:
        ...

I am not sure this is exactly what you were trying to do, but you defined import as array, which makes me assume so.

On the other hand, you can do:

company_happy:
    imports:
        import:
            path: /tmp
        another_import:
            ...

Regarding no extension able to load the configuration error: Make sure your extension file is following naming convetions.It should be called CompanyHappyExtension.php with CompanyHappyExtension class defined inside.

I have created a sample CompanyHappyBundle bundle which is working fine on Symofny 3 (probably works on S2 as well). Feel free to clone/download it :)

The services.yml file is an added bonus, as you will most likely need it anyway.

src/Company/Bundle/HappyBundle/CompanyHappyBundle.php:

<?php

namespace Company\Bundle\HappyBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class CompanyHappyBundle extends Bundle
{

}

src/Company/Bundle/HappyBundle/DependencyInjection/CompanyHappyExtension.php

<?php

namespace Company\Bundle\HappyBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;


class CompanyHappyExtension extends Extension implements ExtensionInterface
{


    public function load(array $configs, ContainerBuilder $container)
    {
        $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
        $loader->load('services.yml');

        $configuration = new Configuration();
        $options = $this->processConfiguration($configuration, $configs);

        // Do something with your options here
    }


}

src/Company/Bundle/HappyBundle/DependencyInjection/Configuration.php

<?

namespace Company\Bundle\HappyBundle\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('company_happy');

        $rootNode
            ->children()
                ->arrayNode('imports')
                    ->prototype('array')
                    ->children()
                        ->scalarNode('path')->defaultValue('/tmp')->end()
                        ->scalarNode('method')->defaultValue('ALL')->end()
                        ->booleanNode('move_mail')->defaultValue(true)->end()
                        ->booleanNode('mark_read')->defaultValue(true)->end()
                    ->end()
                ->end()
            ->end()
        ;

        return $treeBuilder;
    }
}

src/Company/Bundle/HappyBundle/Resources/config/config.yml

company_happy:
    imports:
        import:
            path: /tmp

src/Company/Bundle/HappyBundle/Resources/config/services.yml

# Define your services here
services:
like image 35
Karolis Avatar answered Sep 26 '22 14:09

Karolis