opensky / runtime-config-bundle
此捆绑包提供了一种通过公开RuntimeParameterBag服务在运行时向服务注入参数的方法,该服务的工作方式与Symfony2的自己的ParameterBags完全相同。
Requires
- php: ^8.0
- ext-json: *
- doctrine/orm: ^2.4
- symfony/doctrine-bridge: ^3.0|^4.0|^5.0|^6.0
- symfony/framework-bundle: ^3.0|^4.0|^5.0|^6.0
- symfony/validator: ^3.0|^4.0|^5.0|^6.0
- symfony/yaml: ^3.0|^4.0|^5.0|^6.0
Requires (Dev)
- phpunit/phpunit: ^8.0|^9.0
README
此捆绑包提供了一种通过公开RuntimeParameterBag服务在运行时向服务注入参数的方法,该服务的工作方式与Symfony2的自己的ParameterBags完全相同。
目前,Symfony2的服务容器被编译并缓存在磁盘上,这使得注入动态参数变得困难。通过公开ParameterBag服务,我们可以将其get()
方法返回的值注入到其他服务中。
你可能需要支持动态参数的原因之一是实现功能标志/切换器,就像GitHub和Flickr所使用的那样。有关此捆绑包背后的历史更多信息,可以在symfony-devs邮件列表中找到。
安装
步骤 1:使用composer下载OpenSkyRuntimeConfigBundle
使用composer要求捆绑包
$ composer require opensky/runtime-config-bundle
步骤 2:启用捆绑包
在内核中启用捆绑包
<?php // app/AppKernel.php public function registerBundles() { return [ new OpenSky\Bundle\RuntimeConfigBundle\OpenSkyRuntimeConfigBundle(), ]; }
步骤 3:配置应用程序的config.yml
RuntimeParameterBag可以配置如下
# app/config/config.yml opensky_runtime_config: provider: parameter.provider.service cascade: true logging: enabled: true level: debug
以下是对这些设置的说明
provider
:实现ParameterProviderInterface的服务。如果你使用Doctrine ORM作为数据源,这可能是一个EntityRepository。cascade
:如果为true,则在运行时配置中参数未定义时,将对服务容器调用get()
。这不会改变has()
或all()
的行为,它们始终只考虑运行时配置提供程序中的参数。logging.enabled
:是否启用对未定义参数的访问日志记录,无论是否启用服务容器级联。如果你使用Monolog,则日志将发送到"opensky.runtime_config"通道。logging.level
:要使用的日志级别(应为LoggerInterface方法)。
注意:当使用cascade
时,定义服务容器中运行时配置参数的默认值是个好主意。这有助于避免在发生意外时获取尚未在运行时配置中定义的参数而导致的ParameterNotFoundException。
注入参数
考虑以下场景:"my.service"依赖于动态参数"my.service.enabled"。
可以使用Symfony的表达式语言语法方便地注入运行时参数
建议在捆绑包内定义服务时使用XML
<?xml version="1.0" ?> <!-- src/AppBundle/Resources/config/services.xml --> <services> <service id="my.service" class="MyService"> <argument type="expression">service('opensky.runtime_config').get('my.service.enabled')</argument> </service> </services>
建议在全局服务中使用YAML
# app/config/services.yml services: my.service: class: MyService arguments: - "@=service('opensky.runtime_config').get('my.service.enabled')"
级联模式
如果你已启用级联模式,则get()
将在抛出异常之前尝试从服务容器中获取未定义的运行时参数。
基于前面的XML示例,这将看起来像
<?xml version="1.0" ?> <!-- src/AppBundle/Resources/config/services.xml --> <parameters> <parameter key="my.service.enabled">false</parameter> </parameters> <services> <service id="my.service" class="MyService"> <argument type="expression">service('opensky.runtime_config').get('my.service.enabled')</argument> </service> </services>
在此示例中,get('my.services.enabled')
将返回false,即使该参数未在运行时配置中定义。这是一种安全地引入新参数的方法,这些参数在部署时可能尚未由你的提供程序提供。
注意:来自运行时配置提供程序的参数不会解析为占位符语法(即"%"参考%""),这与在服务容器中定义的参数不同。
食谱:将参数值解释为YAML
如果你使用Doctrine ORM(或任何数据库)来保存你的参数,你可能会实现一个CRUD接口,通过你的应用程序中的管理控制器定义和编辑参数。
此外,这使我们能够向我们的ParameterProvider添加自定义行为。例如,我们可以使用Symfony2的YAML组件将数据库中存储的参数值作为字符串进行解释。
考虑以下Entity
<?php // src/MyBundle/Entity/Parameter.php namespace MyBundle\Entity\Parameter; use Doctrine\ORM\Mapping as ORM; use OpenSky\Bundle\RuntimeConfigBundle\Model\Parameter as BaseParameter; use Symfony\Bridge\Doctrine\Validator\Constraints as AssertORM; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Yaml; /** * @ORM\Entity(repositoryClass="MyBundle\Entity\ParameterRepository") * @ORM\Table( * name="parameters", * uniqueConstraints={ * @ORM\UniqueConstraint(name="name_unique", columns={"name"}) * } * ) * @Assert\Callback(methods={"validateValueAsYaml"}) */ class Parameter extends BaseParameter { /** * @var int * * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue */ private $id; /** * @return int */ public function getId() { return $this->id; } /** * @param ExecutionContextInterface $context */ public function validateValueAsYaml(ExecutionContextInterface $context) { try { Yaml::parse($this->value); } catch (ParseException $e) { $context->buildViolation('This value is not valid YAML syntax') ->atPath('value') ->addViolation(); } } }
这里发生了几件事情
- 我们必须映射一个ID字段,因为基础Parameter类只定义了必要的名和值字段。
- 基础类为名和值字段定义了断言(在组中,可以轻松禁用);然而,映射的基类没有在名上定义唯一约束,因此这是必要的。
- 使用回调断言来检查值属性是否是有效的YAML。
上述Entity类由以下EntityRepository补充,该Repository作为RuntimeParameterBag的ParameterProvider。
<?php // src/MyBundle/Entity/ParameterRepository.php namespace MyBundle\Entity\Parameter; use OpenSky\Bundle\RuntimeConfigBundle\Entity\ParameterRepository as BaseParameterRepository; use Symfony\Component\Yaml\Yaml; class ParameterRepository extends BaseParameterRepository { public function getParametersAsKeyValueHash() { return array_map(function($v) { return Yaml::parse($v); }, parent::getParametersAsKeyValueHash()); } }
基础ParameterRepository已经通过DQL查询从数据库中获取名/值对。使用array_map()
,我们可以通过相同的YAML组件方法轻松解释这些值。
注意:尽管我们验证了Entity,但可能某个值已经被手动更改到数据库中,并在获取参数时包含无效的YAML。如果这是一个问题,您可能希望在getParametersAsKeyValueHash()
中优雅地处理抛出的ParseExceptions。
食谱:将参数值解释为YAML
还可以将参数值存储为JSON。为了做到这一点,我们需要将值参数重新定义为“json”类型,而不是“string”。
考虑以下Entity
<?php // src/MyBundle/Entity/Parameter.php namespace MyBundle\Entity\Parameter; use Doctrine\ORM\Mapping as ORM; use OpenSky\Bundle\RuntimeConfigBundle\Model\Parameter as BaseParameter; use Symfony\Bridge\Doctrine\Validator\Constraints as AssertORM; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Context\ExecutionContextInterface; /** * @ORM\Entity(repositoryClass="MyBundle\Entity\ParameterRepository") * @ORM\Table( * name="parameters", * uniqueConstraints={ * @ORM\UniqueConstraint(name="name_unique", columns={"name"}) * } * ) * @Assert\Callback(methods={"validateValueAsJson"}) */ class Parameter extends BaseParameter { /** * @var int * * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue */ private $id; /** * @var mixed * * @ORM\Column(type="json", nullable=true) */ protected $value; /** * @return int */ public function getId() { return $this->id; } /** * @param ExecutionContextInterface $context */ public function validateValueAsYaml(ExecutionContextInterface $context) { @json_encode($this->value); if (json_last_error() !== JSON_ERROR_NONE) { $context->buildViolation('This value is not valid JSON') ->atPath('value') ->addViolation(); } } }
在这种情况下,我们不需要覆盖默认的仓库查询getParametersAsKeyValueHash(),因为数据库结果已经被Doctrine的json_decoded解码。
<?php // src/MyBundle/Entity/ParameterRepository.php namespace MyBundle\Entity\Parameter; use OpenSky\Bundle\RuntimeConfigBundle\Entity\ParameterRepository as BaseParameterRepository; class ParameterRepository extends BaseParameterRepository { }