Laravel tag all class implemeting an interface

Viewed 496

I'm using Laravel 8 and i want to get all the classes that implements an Interface X.

I did it with symfony4 few month ago with DI :

services.yml

_instanceof:
    App\Calculator\Budget\BudgetCalculatorInterface:
      tags: ['app.budget_calculator']
App\Handler\CalculatorBudgetHandler:
  arguments: [!tagged app.budget_calculator]

and then in my class 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);
        }
    }

  
}

but i dot not understand how to do it with Laravel. I think i have to pass all my classes in a bind or tag :

$this->app->tag([CpuReport::class, MemoryReport::class], 'reports');

thats mean if i got a new class implementing X, i have to add it in the bind/tag ? I want to do it automatically .

thx !

1 Answers

I needed this too. Looked for a longer time and I basically found a solution. The bad thing about this is that in PHP classes aren't actually declared when you did not use them. So you'll have to either scan the entire project for classes and test each class to find classes implementing your interface or (better) you use the composer autoload class maps. There you could probably limit the searching scope for classes to a sub namespace.

A small but cool package working this way is this one: https://gitlab.com/hpierce1102/ClassFinder - basically it uses composer PSR4 classmaps and is in general fine performance wise.

Here is the solution to which I came:

// 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);
        }
    }
}

Which can then be used like this in the register():

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

$this->app->when(SomeClass::class)->needs('$myServices')->giveTagged('some-tag');

As the classes are loaded using reflection, this operation still might take time if your root namespace is not properly set or too wide. Reflection is quick (as far as I know quicker than loading the information from cache), but you should still think of using a deferred provider for the task so that the search for implementing classes only triggers when it's actually needed.

Related