Laravel 标记所有实现接口的类

tot*_*che 6 tags bind interface laravel

我正在使用 Laravel 8,我想获取实现 Interface X 的所有类。

几个月前我用 symfony4 和 DI 做到了:

服务.yml

_instanceof:
    App\Calculator\Budget\BudgetCalculatorInterface:
      tags: ['app.budget_calculator']
App\Handler\CalculatorBudgetHandler:
  arguments: [!tagged app.budget_calculator]
Run Code Online (Sandbox Code Playgroud)

然后在我的类 CalculatorBudgetHandler.php 中

private $calculatorList = [];

public function __construct(iterable $calculatorList)
{
    $this->calculatorList = $calculatorList;
}

public function __construct(iterable $calculatorList)
{
    $this->calculatorList = $calculatorList;
}

public function calculate(array $data): float
{
    foreach ($this->calculatorList as $calculator) {
        if ($calculator->supports($data)) {
            return $calculator->calculate($data);
        }
    }

  
}
Run Code Online (Sandbox Code Playgroud)

但我不明白如何用 Laravel 做到这一点。我想我必须在绑定或标记中传递所有类:

$this->app->tag([CpuReport::class, MemoryReport::class], 'reports');
Run Code Online (Sandbox Code Playgroud)

这意味着如果我有一个实现 X 的新类,我必须将其添加到绑定/标签中?我想自动完成。

谢谢 !

mvm*_*oay 5

我也需要这个。找了很久,基本找到了解决办法。这样做的坏处是,在 PHP 中,当您没有声明类时,实际上并没有声明use它们。因此,您必须扫描整个项目中的类并测试每个类以查找实现您的接口的类,或者(更好)您使用 Composer 自动加载类映射。在那里,您可能可以将类的搜索范围限制为子命名空间。

以这种方式工作的一个小而酷的包是: https: //gitlab.com/hpierce1102/ClassFinder - 基本上它使用 Composer PSR4 类映射,并且总体上性能良好。

这是我找到的解决方案:

// Add to service provider
private function tagByInterface(string $interfaceName, string $tagName, string $rootNamespace)
{
    foreach (ClassFinder::getClassesInNamespace($rootNamespace, ClassFinder::RECURSIVE_MODE) as $className) {
        $class = new \ReflectionClass($className);
        if ($class->isAbstract() || $class->isInterface()) {
            continue;
        }

        if ($class->implementsInterface($interfaceName)) {
            $this->app->tag($className, $tagName);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后可以像这样使用register()

$this->tagByInterface(SomeInterface::class, 'some-tag', 'App\Domain\Something');

$this->app->when(SomeClass::class)->needs('$myServices')->giveTagged('some-tag');
Run Code Online (Sandbox Code Playgroud)

由于类是使用反射加载的,如果您的根命名空间设置不正确或太宽,此操作仍然可能需要一些时间。反射很快(据我所知比从缓存加载信息更快),但您仍然应该考虑使用延迟提供程序来执行任务,以便仅在实际需要时触发对实现类的搜索。

几个月后更新

该解决方案可行,但如果项目变大,可能会严重消耗性能。我现在正在缓存标记的类。像这样的事情:

use HaydenPierce\ClassFinder\ClassFinder as HPClassFinder;
use Illuminate\Contracts\Cache\Repository;

class InheritanceClassFinder
{
    public function __construct(private ?Repository $cache = null)
    {
    }

    public function findClassesImplementingOrExtending(string $interfaceOrClass, string $rootNamespace): array
    {
        if ($this->cache) {
            return $this->cache->rememberForever(
                'inheriting-classes-'.$interfaceOrClass,
                fn () => $this->findClassesInheriting($interfaceOrClass, $rootNamespace));
        }

        return $this->findClassesInheriting($interfaceOrClass, $rootNamespace);
    }

    private function findClassesInheriting(string $interfaceOrClass, string $rootNamespace): array
    {
        $classes = [];
        foreach (HPClassFinder::getClassesInNamespace($rootNamespace, HPClassFinder::RECURSIVE_MODE) as $className) {
            if (!is_subclass_of($className, $interfaceOrClass)
                || ($class = new \ReflectionClass($className))->isAbstract() || $class->isInterface()) {
                continue;
            }

            $classes[] = $className;
        }

        return $classes;
    }
}

Run Code Online (Sandbox Code Playgroud)

这意味着只要注入缓存,就会加载一次内容,然后从缓存中取出。我仅在生产中注入缓存,因此在本地它有点慢但始终是最新的。在生产中,我在每次部署时都会丢弃缓存,因此每次部署后我都会获得一次新的加载。