matthiasnoback/symfony-service-definition-validator

用于验证使用Symfony依赖注入组件创建的服务定义的库

v1.2.10 2022-02-02 15:13 UTC

README

由Matthias Noback编写

Build Status Scrutinizer Quality Score Latest Stable Version

安装

使用Composer

php composer.phar require matthiasnoback/symfony-service-definition-validator

验证器可以发现的错误

使用此库中的服务定义验证器,可以识别以下服务定义问题

  • 不存在的类
  • 不存在的工厂方法
  • 调用不存在的方法
  • 构造函数缺少必需的参数
  • 非公开的构造函数方法
  • 非静态的工厂方法
  • 方法调用缺少必需的参数
  • 构造函数参数类型提示不匹配(数组或类/接口)
  • 方法调用参数类型提示不匹配(数组或类/接口)
  • 表达式参数中的语法错误(请参阅 表达式语言
  • 在评估时引起错误的表达式

这将防止许多运行时问题,并在早期就提醒你服务定义中的不一致性。

报告假阴性

我已经使用最新的Symfony标准版测试了验证器,该版当然只包含有效的服务定义。如果您在项目中报告验证器在不应失败的情况下失败,请告诉我。在报告问题时,请附上错误信息的副本以及 app/cache/dev/appDevDebugProjectContainer.xml 中相关的行。

使用方法

如果您有一个现有的Symfony应用程序并希望确保服务定义始终有效,最简单的方法是将编译器传递添加到您的bundle中,具体方法请参阅此处。这将在编译bundle时验证服务定义,如果关闭了缓存(在调试模式下是默认设置),则每次请求都会发生。

如果您想为Symfony项目中的phpunit验证服务定义,请按照此处的步骤操作。

服务验证器工厂

您可以使用独立验证器处理单个定义

<?php

use Matthias\SymfonyServiceDefinitionValidator\ServiceDefinitionValidatorFactory;

// an instance of Symfony\Component\DependencyInjection\ContainerBuilder
$containerBuilder = ...;

$validatorFactory = new ServiceDefinitionValidatorFactory();
$validator = $validatorFactory->create($containerBuilder);

// an instance of Symfony\Component\DependencyInjection\Definition
$definition = ...;

// will throw an exception for any validation error
$validator->validate($definition);

要同时处理多个定义,请将验证器包装在批处理验证器中

<?php

use Matthias\SymfonyServiceDefinitionValidator\BatchServiceDefinitionValidator;
use Matthias\SymfonyServiceDefinitionValidator\Error\ValidationErrorFactory;

$batchValidator = new BatchServiceDefinitionValidator(
    $validator,
    new ValidationErrorFactory()
);

$errorList = $batchValidator->validate($serviceDefinitions);

生成的错误列表将包含有关有问题的服务定义的错误

编译器传递

要检查编译时所有服务定义的有效性,请将 ValidateServiceDefinitionsPass 编译器传递添加到 ContainerBuilder 实例中

<?php

use Matthias\SymfonyServiceDefinitionValidator\Compiler\ValidateServiceDefinitionsPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;

class SomeBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        if ($container->getParameter('kernel.debug')) {
            $container->addCompilerPass(
                new ValidateServiceDefinitionsPass(),
                PassConfig::TYPE_AFTER_REMOVING
            );
        }
    }
}

此编译器传递将抛出异常。该异常的消息将包含无效服务定义的列表。

在PHPUnit中运行验证器

在Symfony中,添加编译器传递会在每次在浏览器中加载页面或使用命令时验证您的服务。但您想使用PHPUnit按需运行验证器怎么办?要这样做,首先按照上述说明设置编译器传递,然后创建一个新的PHPUnit测试,如下所示

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class ContainerServiceDefinitionsTest extends WebTestCase
{
    /**
     * @test
     */
    public function validateServiceDefinitions()
    {
        $kernel = static::createKernel();
        $kernel->boot();

        // Add assertion to phpunit count which will prevent marking test as "risky"
        $this->addToAssertionCount(1);
    }
}

这个简单的功能测试只是使用 "test" 环境启动symfony内核。如果您回顾一下设置编译器传递时添加的代码,您会看到我们也为 "test" 环境启用了验证器。因此,这个简单的PHPUnit测试将在每次运行时验证服务。

实际上,如果您已经有了功能测试,就不需要这个测试,因为内核将在其他功能测试中启动(因此将验证服务)。上面的示例测试仅在您没有现有的任何 Symfony 功能测试时才需要。

配置验证器

Both ValidateServiceDefinitionsPassServiceDefinitionValidatorFactory 接受一个 Configuration 对象。它允许您配置是否应该评估表达式参数。由于评估表达式可能会引起各种运行时错误,它默认是 关闭的,但您可以轻松地将其打开。

<?php

use Matthias\SymfonyServiceDefinitionValidator\Configuration;
use Matthias\SymfonyServiceDefinitionValidator\Compiler\ValidateServiceDefinitionsPass;

$configuration = new Configuration();
$configuration->setEvaluateExpressions(true);

$compilerPass = new ValidateServiceDefinitionsPass($configuration);

// or

$validatorFactory = new ServiceDefinitionValidatorFactory($configuration);

修复第三方包中的无效服务定义

当验证器发现您自己的服务定义存在问题,您当然可以自行解决问题,但如果无效的服务定义在某个其他包中定义,您仍然可以通过动态修改服务定义来解决问题。首先,您需要创建一个编译器通过

<?php

namespace YourBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class FixInvalidServiceDefinitionPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        // find the bad definition:

        $invalidDefinition = $container->findDefinition('the_service_id');

        // for example, fix the class
        $invalidDefinition->setClass('TheRightClass');
    }
}

在您选择了无效的服务定义后,您可以以任何您喜欢的任何方式对其进行修改。有关您可以执行的操作列表 Definition 对象,请参阅 https://symfony.ac.cn/doc/master/components/dependency_injection/definitions.html

不要忘记在您的包类中注册编译器通过

<?php

namespace MyBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class MyBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        if ($container->getParameter('kernel.debug')) {
            $container->addCompilerPass(new FixInvalidServiceDefinitionPass());
        }
    }
}