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

Posted

技术标签:

【中文标题】如何访问 Symfony2 中的嵌套参数值?【英文标题】:How to access nested parameter values in Symfony2? 【发布时间】:2013-11-19 06:55:09 【问题描述】:

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

parameters:
    category:
        var: test

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

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

【问题讨论】:

【参考方案1】:

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

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

【讨论】:

【参考方案2】:

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

# 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: '%categories%'

通过设置此参数:

# 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>
...

希望这会有所帮助! :)

【讨论】:

感谢您的回答。你能指定 symfony 的版本号(如果适用的话)吗? 我找不到太多关于什么时候可用的信息,但在我的情况下,我正在处理的一个最近的项目中出现了这个需求,该项目是使用 3.2 版设置的。 * 的 symfony,但我已经在一个旧项目中使用版本:2.4.* 测试了相同的解决方案,它也可以正常工作。所以我们可以得出结论,为了使这个工作,必须使用 symfony 的 2.4.* 版本设置一个项目。 我在服务中得到了字符串 category['var']。你确定吗?。如果我使用 %category['var']% 那么我得到未定义的参数。我正在使用 Symfony 3.4 我在同一条船上。 my_var 的值是文字字符串“category['var']”,而不是 category.var 的值。我在 Symfony 4.1 上,对这可能如何工作感到有些困惑。 要访问参数,从自定义服务的过程是不同的,首先你需要确保你正在导入服务容器,然后你可以从那里做这样的事情: $categories = $ this->container->getParameter('category');回声 $categories['var'];输出:测试。希望这会有所帮助,干杯。【参考方案3】:

模拟嵌套参数的技巧,在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 中,子树/数组是可访问的。

【讨论】:

【参考方案4】:

在我见过的所有 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。我不认为参数可以用嵌套来定义。

【讨论】:

【参考方案5】:
$this->container->getParameter('category')['var']

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

【讨论】:

在 Symfony 4 上工作【参考方案6】:

或者你可以编写简单的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,它也会使用点符号自动完成您的嵌套属性。

【讨论】:

我喜欢将设置保存为 yaml 格式的简便性。所以这只会以我想避免的方式膨胀。但是感谢您以这种方式解释。 我不明白。此 ParameterBag 只是允许您在 yaml 配置中使用嵌套配置的功能。它向后兼容您当前的所有配置。就这样。它没有定义php配置。 我应该在哪里将参数包注入ContainerBuilder?在扩展类中 ContainerBuilder 已经传递给“加载”方法, 这取决于您的堆栈,您必须将其传递给 ContainerBuilder 构造函数,如您在我的示例中所见。【参考方案7】:

为了提供一种不需要从 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

容器感知:

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

【讨论】:

您是否在供应商中编辑了ParameterBagParser?它看起来像那样,这不是最好的主意。可以重写您的解决方案,以便您在 AppBundle 的某处重写 ParameterBagParser 并让 symfony 知道这一点吗? ParameterBagParser 不是默认的 Symfony 组件。 ParameterBagParser 扩展 ParameterBagParameterBagParser 可以放在应用程序代码中您想要的任何位置。然后您可以覆盖AppKernel::getContainerBuilder 以使用自定义ParameterBagParser 或用作explained in the answer by palmic

以上是关于如何访问 Symfony2 中的嵌套参数值?的主要内容,如果未能解决你的问题,请参考以下文章

从控制器 Symfony2 中的 URL 获取参数

访问 Symfony2 请求对象中的 POST 值

如何从控制器中的 URL 访问参数值?

如何在 Python 中使用表值参数访问 SQL Server 2008 存储过程

Angular ui-router - 如何访问从父模板传递的嵌套命名视图中的参数?

Symfony 2.0 - 如何使用数组参数创建路由?