17

I created a parameter in my parameters.yml file:

parameters:
    category:
        var: test

How can I access this parameter in my config.yml? For example, I want to pass this parameter to all my twig files as a global twig variable:

twig:
    globals:
        my_var: %category.var% # throws ParameterNotFoundException

7 Answers 7

11

In all the symfony config files I've seen, the entries under 'parameters:' have always been fully qualified. I don't fully understand why this is but it may help you to write the entries in your parameters.yml like this:

category1.var1: xxx
category1.var2: yyy
category1.var3. zzz

category2.subcategory1.var1: 5
category2.subcategory1.var2: 10
category2.subcategory2.var1: foo
category2.subcategory2.var2: bar

... and so on.

EDIT

I tried pasting the nested parameter from the question into parameters.local.yml in one of my projects and ran a trivial unit test to retrieve that and a fully qualified parameter from the container e.g.

$testUserEmail = $container->getParameter('test.user.email');
$this->assertEquals('[email protected]', $testUserEmail);
$testParam = $container->getParameter('category.var');
$this->assertEquals('test', $testParam);

The fully qualified parameter was fine, attempting to get the nested parameter resulted in an InvalidArgumentException: The parameter category.var must be defined. I don't think parameters can be defined with nesting.

Sign up to request clarification or add additional context in comments.

Comments

9
$this->container->getParameter('category')['var']

I have tested that on symfony 2.8 and it worked for me.

1 Comment

Worked on Symfony 4
6

Or you can write simple ParameterBag which can implement nested parameters dynamically (without config values duplicity):

<?php

namespace Your\Namespace;

use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;

class ParameterBagNested extends ParameterBag
{

    /**
     * wire $this->set() logic into add() too
     *
     * @param array $parameters
     */
    public function add( array $parameters )
    {
        foreach ( $parameters as $name => $value ) {
            $this->set( $name, $value );
        }
    }

    /**
     * sets all levels of nested array parameters with dot notation
     * - loggly[host: loggly.com] will be translated this way:
     *  - loggly: [host: loggly.com] - standard array parameter will be left as is
     *  - loggly.host: loggly.com - nested variables ar translated so you can access them directly too as parent.variable
     *
     * @param string $name
     * @param mixed $value
     */
    public function set( $name, $value )
    {
        if ( $this->has( $name ) ) {
            // this is required because of array values
            // we can have arrays defined there, so we need to remove them first
            // otherwise some subvalues would to remain in the system and as a result, arrays would be merged, not overwriten by set()
            $this->remove( $name );
        }
        $this->setNested( $name, $value );
    }

    /**
     * remove checks even if name is not array
     *
     * @param string $name
     */
    public function remove( $name )
    {
        $value = $this->get( $name );
        if ( is_array( $value ) ) {
            foreach ( $value as $k => $v ) {
                $this->remove( $name . '.' . $k, $v );
            }
        }
        if ( strpos( $name, '.' ) !== FALSE ) {
            $parts = explode( '.', $name );
            $nameTopLevel = reset( $parts );
            array_shift( $parts );
            $topLevelData = $this->removeKeyByAddress( $this->get( $nameTopLevel ), $parts );
            ksort( $topLevelData );
            $this->setNested( $nameTopLevel, $topLevelData );
        }
        parent::remove( $name );
    }

    /**
     * @param array $data
     * @param array $addressParts
     *
     * @return array
     */
    private function removeKeyByAddress( $data, $addressParts )
    {
        $updatedLevel = & $data;
        $i = 1;
        foreach ( $addressParts as $part ) {
            if ( $i === count( $addressParts ) ) {
                unset( $updatedLevel[$part] );
            } else {
                $updatedLevel = & $updatedLevel[$part];
                $i++;
            }
        }
        return $data;
    }

    /**
     * @see set()
     *
     * @param string $name
     * @param mixed $value
     */
    private function setNested( $name, $value )
    {
        if ( is_array( $value ) ) {
            foreach ( $value as $k => $v ) {
                $this->setNested( $name . '.' . $k, $v );
            }
        }
        parent::set( $name, $value );
    }

}

phpunit Test:

<?php

namespace Your\Namespace;

use Symfony\Component\DependencyInjection\Tests\ParameterBag\ParameterBagTest;

/**
 * its essential to use ParameterBagNested as ParameterBag because this way we run even parent class tests upon it
 * parent class is part of Symfony DIC standard test suite and we use it here just for check if our parameter bag is still ok
 */
use SBKS\DependencyInjection\ParameterBag\ParameterBagNested as ParameterBag;

/**
 * testing basic and even added ParameterBag functionality
 */
class ParameterBagNestedTest extends ParameterBagTest
{

    public function testConstructorNested()
    {
        $bag = new ParameterBag(
            array(
                'foo' => array( 'foo1' => 'foo' ),
                'bar' => 'bar',
            )
        );
        $this->assertEquals(
             array(
                 'foo.foo1' => 'foo',
                 'foo' => array(
                     'foo1' => 'foo',
                 ),
                 'bar' => 'bar',
             ),
             $bag->all(),
             '__construct() takes an array of parameters as its first argument'
        );
    }

    public function testRemoveNested()
    {
        $bag = new ParameterBag(
            array(
                'foo' => array(
                    'foo1' => array(
                        'foo11' => 'foo',
                        'foo12' => 'foo',
                    ),
                    'foo2' => 'foo',
                ),
                'bar' => 'bar',
            )
        );

        $bag->remove( 'foo.foo1.foo11' );
        $this->assertEquals(
             array(
                 'foo' => array(
                     'foo1' => array(
                         'foo12' => 'foo',
                     ),
                     'foo2' => 'foo',
                 ),
                 'foo.foo1' => array( 'foo12' => 'foo' ),
                 'foo.foo1.foo12' => 'foo',
                 'foo.foo2' => 'foo',
                 'bar' => 'bar',
             ),
             $bag->all(),
             '->remove() removes a parameter'
        );

        $bag->remove( 'foo' );
        $this->assertEquals(
             array(
                 'bar' => 'bar',
             ),
             $bag->all(),
             '->remove() removes a parameter'
        );
    }

    public function testSetNested()
    {
        $bag = new ParameterBag(
            array(
                'foo' => array(
                    'foo1' => array(
                        'foo11' => 'foo',
                        'foo12' => 'foo',
                    ),
                    'foo2' => 'foo',
                ),
            )
        );
        $bag->set( 'foo', 'foo' );
        $this->assertEquals( array( 'foo' => 'foo' ), $bag->all(), '->set() sets the value of a new parameter' );
    }

    public function testHasNested()
    {
        $bag = new ParameterBag(
            array(
                'foo' => array(
                    'foo1' => array(
                        'foo11' => 'foo',
                        'foo12' => 'foo',
                    ),
                    'foo2' => 'foo',
                ),
            )
        );
        $this->assertTrue( $bag->has( 'foo' ), '->has() returns true if a parameter is defined' );
        $this->assertTrue( $bag->has( 'foo.foo1' ), '->has() returns true if a parameter is defined' );
        $this->assertTrue( $bag->has( 'foo.foo1.foo12' ), '->has() returns true if a parameter is defined' );
        $this->assertTrue( $bag->has( 'foo.foo2' ), '->has() returns true if a parameter is defined' );
    }

}

Then you can use it by injecting Parameter bag into ContainerBuilder:

$parameterBag = new \Your\Namespace\ParameterBagNested();
$container = new ContainerBuilder($parameterBag);

Thats all, you can use nested parameter with dot notation now in Symfony DI container.

If you are using Symfony plugin in phpstorm it will autocomplete your nested attributes with dot notation too.

4 Comments

I like the easyness of keeping settings in the yaml format. So this would just bloat it up in a way that I want to avoid. But thanks for explaining this way.
I dont understand. This ParameterBag is just functionality to allow you to use nested configuration in your yaml configurations. Its backward compatible with all your current configurations. Thats all. It does not define php configuration.
Where exactly should I inject parameter bag to ContainerBuilder? In Extension classes ContainerBuilder already passed to "load" metod,
It depends on your stack, you have to pass it to ContainerBuilder constructor as you can see in my example.
6

Symfony's expression language syntax to the rescue! Give this a shot:

twig:
    globals:
        my_var: '@=parameter("category")["var"]'

1 Comment

The downside to this solution is that a missing parameter will not be detected during SF container build but only later on, during runtime.
5

A bit late, but here is a solution that worked for me.

# app/config/config.yml
twig:
    globals:
        my_var: category['var']

Explanation

When you import your parameters file into your app/config/config.yml file, you can automatically access all variables defined in your parameters file.

Keep in mind, when you use the structure:

# app/config/parameters.yml
parameters:
    category:
        var: test

You are defining a parameter called category with a value that is in turn itself a key-value pair array that contains var as key and test as value.

Update

In newer versions of symfony (3.4 - 4.2) you can now do the following:

# app/config/config.yml
twig:
    globals:
       custom_categories: '%categories%'

By having this parameter set up:

# app/config/parameters.yml
parameters:
    categories:       
      - category_A
      - category_B

Things to note here: - in parameters.yml categories is an array

so to use it in a twig template, you should be able to do something like:

<ul>
{% for category in categories %}
    <li> {{ category }} </li>
{% endfor %}
</ul>

Another example with objects:

# app/config/parameters.yml
parameters:
    marketplace:       
        name: 'My Store'
        address: '...'

Configuring twig variables:

# app/config/config.yml
twig:
    globals:
       marketplace: '%marketplace%'

Using it in twig:

...
<p>{{marketplace.name}}</p>
<p>{{marketplace.address}}</p>
...

Hope this helps! :)

7 Comments

Thanks for your answer. Can you specify the symfony version number (if applicable) since this is possible?
I couldn't find to much information about since when this is available, but in my case the need came up in a recent project that I'm working with which is set up with the version 3.2.* of symfony, but I've tested the same solution in a old project using the version: 2.4.*, and it is working as well. So we can conclude that in order to make this work, a project must be set up with the version 2.4.* of symfony.
I got the literal string category['var'] in the service. Are you sure?. If I use %category['var']% then I get undefined parameter. I'm using Symfony 3.4
I'm in the same boat. The value of my_var is the literal string "category['var']" and not the value of category.var. I'm on Symfony 4.1 and a bit flummoxed as to how this might work.
To access parameters, from a custom service the process is different, first you need to make sure you're importing the service container, and from there you can just do something like this: $categories = $this->container->getParameter('category'); echo $categories['var']; output: test. Hope this helps, cheers.
|
1

To provide an alternative approach that doesn't require removing values from the setter, I override the getter method instead.

namespace NameSpaceFor\ParameterBags;

use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;


class ParameterBagParser extends ParameterBag
{

    /**
     * {@inheritDoc}
     */
    public function get($name, $parent = null)
    {
        if (null === $parent) {
            $parent = $this->parameters;
        }
        $name = strtolower($name);
        if (!array_key_exists($name, $parent)) {
            if (!$name) {
                throw new ParameterNotFoundException($name);
            }
            if (false !== strpos($name, '.')) {
                $parts = explode('.', $name);
                $key = array_shift($parts);
                if (isset($parent[$key])) {
                    return $this->get(implode('.', $parts), $parent[$key]);
                }
            }
            $alternatives = [];
            foreach ($parent as $key => $parameterValue) {
                $lev = levenshtein($name, $key);
                if ($lev <= strlen($name) / 3 || false !== strpos($key, $name)) {
                    $alternatives[] = $key;
                }
            }
            throw new ParameterNotFoundException($name, null, null, null, $alternatives);
        }

        return $parent[$name];
    }

}

It recursively traverses through the name until it is all of the dot notations have been checked.

So it will work with arrays and scalar values.

config.yml:

parameters:
   my_param:
     - test

   my_inherited: '%my_param.0%' #test

ContainerAware:

$container->getParameter('my_param')[0]; //test

2 Comments

Did you edit ParameterBagParser in vendor? It looks like that and it is not the best idea. Can your solution be rewritten so that you rewrite ParameterBagParser somewhere in the AppBundle and let the symfony know about this?
ParameterBagParser is not a default Symfony component. ParameterBagParser extends ParameterBag, The ParameterBagParser would be put anywhere you want in your application code. You could then override AppKernel::getContainerBuilder to use the custom ParameterBagParser or used as explained in the answer by palmic
-1

Trick to emulate nested parameters, acccess in the yaml file:

parameters:
  crawler:
    urls:
      a: '%crawler.urls.a%'   # here the root of the rest of the tree/array
  crawler.urls.a:
    cat1:
      - aa
      - bb
    cat2:
      - cc

services:
  xx:
    class:  myclass
    arguments:
      $urls:   '%crawler.urls.a%'

In Symfony I now access the parameter('crawler') as complete tree, in service xx the subtree/array is accessible.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.