symfony2在config中注入服务

时间:2016-07-19 15:26:34

标签: validation symfony dependency-injection

我需要根据特定的配置,使用特定的验证器来验证JSON消息的功能。

我已将验证器和约束定义为服务:

services:
    validator.constraint.message.country_code:
        class: RenamedBundle\Validator\Constraints\CountryCode
        arguments: ...

    validator.constraint.message.price_comma:
        class: RenamedBundle\Validator\Constraints\PriceComma
        arguments: ...

    message.validator:
        class: RenamedBundle\Service\Validator\MessageValidatorService
        arguments: ['@validator']
        calls:
            - [addConstraint, ['@validator.constraint.message.country_code']]
            - [addConstraint, ['@validator.constraint.message.price_comma']]

DtoValidatorService我使用约束列表调用validate()

问题的挑战是,相同的JSON消息只需要使用列表中的少量验证器进行验证,这取决于消息属性,即。对于波兰我想验证所有浮动值(在波兰分隔符中是',',不是'。')。我试图通过config.yml执行此操作。

renamed:
    pritners:
        warehouse_wa:
            characteristic:
                country: 'pl'
                source: 'hq-pl'
            validators:
                - '@validator.constraint.message.country_code'
                - '@validator.constraint.message.price_comma'
        warehouse_ny:
            characteristic:
                country: 'us'
                source: 'hq-us'
            validators:
                - '@validator.constraint.message.country_code'

我添加了扩展程序:

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

        $rootNode
            ->children()
                ->arrayNode('printers')
                ->useAttributeAsKey('name')
                    ->prototype('array')
                        ->children()
                            ->arrayNode('characteristic')
                                    ->children()
                                        ->scalarNode('country')->end()
                                        ->scalarNode('source')->end()
                                    ->end()
                            ->end()
                        ->end()
                        ->children()
                            ->arrayNode('validators')
                                ->prototype('scalar')->end()
                            ->end()
                        ->end()
                    ->end()
                ->end()
            ->end();

        return $treeBuilder;
    }
}

class RenamedExtension extends Extension
{
    /**
     * {@inheritDoc}
     * @throws \Exception
     */
    public function load(array $configs, ContainerBuilder $container)
    {
        $configuration = new Configuration();
        $config = $this->processConfiguration($configuration, $configs);
        $container->setParameter('printers', $config['printers']);

        $loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
        $loader->load('services.yml');
    }
}

此配置有效,但问题是当我将'%printers%'参数传递给服务时,我收到了服务名称列表:

Array
(
    [0] => @validator.constraint.message.country_code
)

但是当我通过约束时,我得到了对象。

array(1) {
  [0] =>
  class RenamedBundle\Validator\Constraints\CountryCode#688 (5) {
    ...
  }
}

我现在走到了尽头。如何参数化打印机配置并避免传递内联/硬编码验证器类名。不允许在参数部分调用服务。在配置中调用它们为我提供了额外的控制和验证。 也许有人得到了更好的解决方案?

修改 根据@Artur Vesker的建议,我改变了扩展加载方法的实现。

public function load(array $configs, ContainerBuilder $container)
{
    $configuration = new Configuration();
    $config = $this->processConfiguration($configuration, $configs);

    $printers = [];
    foreach ($config['printers'] as $printerName => $printerConfig) {

        $constraints = [];
        foreach($printerConfig['valdiators'] as $constraintName) {
            $constraintName = ltrim($constraintName, '@');
            $constraints[] = new Reference($constraintName);
        }
        $printerConfig['valdiators'] = $constraints;

        $printers[$printerName] = $printerConfig;
    }

    $container->setParameter('printers', $printers);

    $loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
    $loader->load('services.yml');
}

尝试构建我得到的缓存:

Symfony\Component\DependencyInjection\Exception\InvalidArgumentException 
You cannot dump a container with parameters that contain references to other services 

看起来我的方法在symfony世界中不被允许;]

2 个答案:

答案 0 :(得分:1)

使用Reference

在config中设置ids:

        validators:
            - 'validator.constraint.message.country_code'
            - 'validator.constraint.message.price_comma'

扩展程序

中使用参考创建参数
$validators = array_map(function($id) {
     return new Reference($id); 
}, $config['pritners.warehouse_wa.validators']);

$messageValidatorDefinition = new Definition('RenamedBundle\Service\Validator\MessageValidatorService', [new Reference('validator)]);

   foreach ($validators as $validator) {
      messageValidatorDefinition->addMethodCall('addConstraint', [$validator])
   } 

答案 1 :(得分:0)

感谢Artur Vesker,您的解决方案适合我。 我尝试将message.validator保留在services.yml(我希望有PHPStorm提示)并通过$container->getDefinition('message.validator')将其延伸,但这样addMethodCall无效。所以任何人都会尝试这种方式,它不会起作用,你必须像Artur建议的那样创造服务动力。

我的完整代码:   - 同一class Configuration implements ConfigurationInterface   - 同一config.yml   - 从message.validator移除services.yml   - 在messageValidator中我决定按打印机名称

对约束进行分组
/**
 * Class MessageValidatoService
 * @package RenamedBundle\Service
 */
class MessageValidatoService
{
    /** PrinterConfigurationDto[] */
    protected $printersConfig;

    /**
     * array of constraints for preValidator grouped by printer name ex:
     * [
     *    'printer_name' => [Constraint1, Constraint2]
     * ]
     * @var array
     */
    protected $constraints;


    /**
     * MessageValidatorService constructor.
     *
     * @param array      $printersConfiguration
     * @param Serializer $serializer
     *
     * @throws \RuntimeException
     */
    public function __construct(array $printersConfiguration, Serializer $serializer)
    {
        $this->serializer = $serializer;
        $this->constraints = [];
        $this->printersConfig = [];

        foreach ($printersConfiguration as $printerConfig) {
            /** @var PrinterConfigurationDto $configDto */
            $configDto = $this->serializer->fromArray(
                $printerConfig,
                'RenamedBundle\Dto\PrinterConfiguration\PrinterConfigurationDto'
            );
            $this->printersConfig[] = $configDto;
        }
    }

    /**
     * @param string     $printerName
     * @param Constraint $constraint
     *
     * @return $this
     */
    public function addConstraint($printerName, Constraint $constraint)
    {
        $this->constraints[$printerName][] = $constraint;

        return $this;
    }

    /**
     * Return PrinterConfigurationDto, or throw RuntimeException if none configuration fir to ReceiptDto parameters.
     *
     * @param ReceiptDto $receiptDto
     *
     * @return PrinterConfigurationDto
     *
     * @throws \RuntimeException
     * @throws \OutOfRangeException
     */
    protected function getPrinterConfig($receiptDto)
    {
        $countryCodes = $receiptDto->getCountryCodes();
        if (array_key_exists($receiptDto->getCountryId(), $countryCodes) === false) {
            throw new \OutOfRangeException('Missing country code for country id:' . $receiptDto->getCountryId());
        }

        /** @var PrinterConfigurationDto $printerConfig */
        foreach ($this->printersConfig as $printerConfig) {
            if ($printerConfig->getSourceApp() === $receiptDto->getSourceApp()
                && $printerConfig->getCountry() === $countryCodes[$receiptDto->getCountryId()]
            ) {
                return $printerConfig;
            }
        }
        throw new \RuntimeException(
            'No printer configuration found for app:' . $receiptDto->getSourceApp()
            . ', country id: ' . $receiptDto->getCountryId()
        );
    }

    /**
     * @param ReceiptDto $receiptDto
     *
     * @return Constraint[]
     *
     * @throws \RuntimeException
     * @throws \OutOfRangeException
     */
    public function getValidatorConstraints(ReceiptDto $receiptDto)
    {
        $printerConfig = $this->getPrinterConfig($receiptDto);

        if (array_key_exists($printerConfig->getName(), $this->constraints) === false) {
            return [];
        }

        return $this->constraints[$printerConfig->getName()];
    }
}

/**
 * Class RenamedExtension
 * @package RenamedBundle\DependencyInjection
 */
class RenamedExtension extends Extension
{
    /**
     * {@inheritDoc}
     * @throws \Exception
     * @throws ServiceNotFoundException
     * @throws InvalidArgumentException
     * @throws BadMethodCallException
     */
    public function load(array $configs, ContainerBuilder $container)
    {
        $configuration = new Configuration();
        $config = $this->processConfiguration($configuration, $configs);

        //set parameters before load services
        //rewrite printers configuration from RenamedBundle\Resources\printers.yml
        //structure changes must be implemented also in RenamedBundle\Dto\PrinterConfiguration\PrinterConfigurationDto
        $printers = [];
        foreach ($config['printers'] as $printerName => $printerConfig) {
            $printers[] = [
                'name' => $printerName,
                'country' => $printerConfig['characteristic']['country'],
                'source_app' => $printerConfig['characteristic']['source_app'],
            ];
        }

        $container->setParameter('printers', $printers);

        //load services, add constraints
        $loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
        $loader->load('services.yml');

        $definition = new Definition(PrinterManagerService::class);

        $definition->addArgument('%printers%');
        $definition->addArgument(new Reference('serializer'));

        foreach ($config['validators'] as $printerName => $printerConfig) {
            if (array_key_exists('validators', $printerConfig)
                && is_array($printerConfig['validators'])
            ) {
                foreach ($printerConfig['validators'] as $constraintName) {
                    $constraint = new Reference(ltrim($constraintName, '@'));
                    $definition->addMethodCall('addConstraint', [$printerName, $constraint]);
                }
            }
        }

        $container->setDefinition('message.validator', $definition);
    }
}