忽略自动装配目录中的 class

Ignore a class in a autowired directory

我的服务/文件夹中某处有异常,Symfony 正在尝试自动装配它:

Cannot autowire service "App\Service\Order\Exception\StripeRequiresActionException": argument "$secretKey" of method "__construct()" is type-hinted "string", you should configure its value explicitly.

这是我的 class :

class StripeRequiresActionException extends \Exception
{
    /**
     * @var string
     */
    protected $secretKey;

    public function __construct(string $secretKey)
    {
        parent::__construct();

        $this->secretKey = $secretKey;
    }

    /**
     * @return string
     */
    public function getSecretKey(): string
    {
        return $this->secretKey;
    }
}

我不希望它自动装配。有没有一种简单的方法可以防止 class 被 DI 加载,例如 带有注释 ?我知道我可以在我的 yaml 配置中排除这个 class,但我不想这样做,因为我发现它很难维护。

也许您可以排除所有异常,无论它们在哪里。

如果您的所有异常都遵循您在问题中显示的模式,您可以执行类似于:

App\:
  resource: '../src/*'
  exclude: ['../src/{Infrastructure/Symfony,Domain,Tests}', '../src/**/*Exception.php']

这直接来自我在这里打开的一个项目。 Symfony 的默认 exclude 看起来有些不同。但重要的一点是将模式 *Exception.php 添加到排除的文件中。

这比注释更易于维护,即使注释是可能的(我相信它不是)。将配置全部保存在同一个地方,您可以创建新的异常,而无需更改配置或添加不必要的代码。

即使我同意在您的特定情况下最干净的方法是执行 yivi 建议的操作,我认为我有一个更通用的解决方案可以适用于更多情况。

在我的例子中,我有一个 PagesScanner 服务 returns PageResult 对象,两者都深入到自动装配目录中。

像建议的那样排除 class 是一种痛苦,并且随着异常数量的增加会使 yaml 很快变得不可读。

所以我创建了一个新的编译器通道,它在 App/ 文件夹下的每个 class 上搜索 @IgnoreAutowire 注释:

<?php
namespace App\DependencyInjection\Compiler;

use App\Annotation\IgnoreAutowire;
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

final class RemoveUnwantedAutoWiredServicesPass implements CompilerPassInterface
{
    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container)
    {
        $annotationReader = new AnnotationReader();
        $definitions = $container->getDefinitions();
        foreach ($definitions as $fqcn => $definition) {
            if (substr($fqcn, 0, 4) === 'App\') {
                try {
                    $refl = new \ReflectionClass($fqcn);
                    $result = $annotationReader->getClassAnnotation($refl, IgnoreAutowire::class);
                    if ($result !== null) {
                        $container->removeDefinition($fqcn);
                    }
                } catch (\Exception $e) { 
                    // Ignore
                }
            }
        }
    }
}

这样我所要做的就是将注释添加到 classes 我不想被自动装配:

<?php
namespace App\Utils\Cms\PagesFinder;

use App\Annotation\IgnoreAutowire;

/**
 * @IgnoreAutowire()
 */
class PageResult
{
    [...]
}

这个方法的另一个好处是你甚至可以在 class 构造函数中使用参数而不会出现任何错误,因为实际的自动装配是在编译器通过之后完成的。

您也可以在 config/services.yaml:

中仅禁用此 class 的自动配置
App\Service\Order\Exception\StripeRequiresActionException:
    autoconfigure: false

Symfony 不会将此 class 添加到 DI。

php8 属性的顺便说一句代码:

CompilerPass

<?php
namespace App\DependencyInjection\Compiler;

use App\Annotation\IgnoreAutowire;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

final class RemoveUnwantedAutoWiredServicesPass implements CompilerPassInterface
{
    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container)
    {
        $definitions = $container->getDefinitions();
        foreach ($definitions as $fqcn => $definition) {
            if (str_starts_with($fqcn, 'App\')) {
                try {
                    $refl = new \ReflectionClass($fqcn);
                    $attribute = $refl->getAttributes(IgnoreAutowire::class)[0] ?? null;
                    if ($attribute !== null) {
                        $container->removeDefinition($fqcn);
                    }
                } catch (\Exception $e) {
                    // Ignore
                }
            }
        }
    }
}

属性

<?php

namespace App\Annotation;

use Attribute;

/**
 * Annotation class for @IgnoreAutowire().
 *
 * @Annotation
 * @Target({"CLASS"})
 */
#[Attribute(Attribute::TARGET_CLASS)]
class IgnoreAutowire
{
}