如何在Symfony2中访问嵌套参数值?

时间:2013-11-07 15:00:01

标签: symfony configuration parameters yaml

我在parameters.yml文件中创建了一个参数:

parameters:
    category:
        var: test

如何在config.yml中访问此参数?例如,我想将此参数作为全局树枝变量传递给我的所有树枝文件:

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

7 个答案:

答案 0 :(得分:9)

在我看过的所有symfony配置文件中,'parameters:'下的条目始终是完全限定的。我不完全理解为什么会这样,但它可以帮助您在parameters.yml中编写条目,如下所示:

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

......等等。

修改

我尝试将问题中的嵌套参数粘贴到我的一个项目中的parameters.local.yml中,然后运行一个简单的单元测试来从容器中检索它和一个完全限定的参数,例如。

$testUserEmail = $container->getParameter('test.user.email');
$this->assertEquals('dahlia.flower@randomtest.com', $testUserEmail);
$testParam = $container->getParameter('category.var');
$this->assertEquals('test', $testParam);

完全限定参数很好,尝试获取嵌套参数导致InvalidArgumentException:必须定义参数category.var。我认为参数不能用嵌套来定义。

答案 1 :(得分:5)

或者您可以编写简单的ParameterBag,它可以动态实现嵌套参数(没有配置值的双重性):

<?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测试:

<?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' );
    }

}

然后您可以injecting Parameter bag into ContainerBuilder使用它:

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

多数情况下,您现在可以在Symfony DI容器中使用带点符号的嵌套参数。

如果您使用Symfony plugin in phpstorm,它也会使用点表示法自动填充您的嵌套属性。

答案 2 :(得分:5)

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

我在symfony 2.8上测试了它,它对我有用。

答案 3 :(得分:3)

有点晚了,但这是一个对我有用的解决方案。

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

解释

将参数文件导入app/config/config.yml文件时,可以自动访问参数文件中定义的所有变量。

要记住,当您使用结构时:

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

您正在定义一个名为category的键值对数组,其中包含var作为键,test作为值。

更新

在较新版本的symfony(3.4 - 4.2)中,您现在可以执行以下操作:

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

设置此参数:

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

此处需要注意的事项: - 在parameters.yml中,类别是一个数组

所以要在树枝模板中使用它,你应该可以做类似的事情:

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

对象的另一个例子:

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

配置树枝变量:

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

在树枝中使用它:

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

希望这有帮助! :)

答案 4 :(得分:1)

为了提供一种不需要从setter中删除值的替代方法,我改为使用getter方法。

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];
    }

}

以递归方式遍历名称,直到检查完所有点符号为止。

因此它适用于数组和标量值。

config.yml:

parameters:
   my_param:
     - test

   my_inherited: '%my_param.0%' #test

ContainerAware:

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

答案 5 :(得分:1)

Symfony 的 expression language 语法来救援!试一试:

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

答案 6 :(得分:0)

技巧来模拟嵌套参数,请访问yaml文件:

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%'

现在在Symfony中,我以完整树的形式访问参数(“ crawler”),在服务xx中,可以访问子树/数组。