Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autowire ServiceLocator with iterable of tagged services #39924

Open
zmitic opened this issue Jan 21, 2021 · 6 comments
Open

Autowire ServiceLocator with iterable of tagged services #39924

zmitic opened this issue Jan 21, 2021 · 6 comments

Comments

@zmitic
Copy link

@zmitic zmitic commented Jan 21, 2021

Description

Use attributes to have autowiring between ServiceLocator and tagged services, by key that can be either FQCN (default) or allow users to define it. Upgrade of implementation: #29203

Example of FQCN index, along with psalm annotation use:

class MyService
{
    public function __construct(
        #[Tagged(name: 'form.type')] private ServiceLocator $tagged
    ) {}
    
    /**
    * @param class-string<FormTypeInterface> $className
    */
    public function get(string $className): FormTypeInterface
    {
        return $this->tagged->get($className);
    }
}

$formType = $myService->get(EntityType::class);

If users want different index (like #29203), optional parameter would be the name of static method used:

interface ExporterInterface
{
    public static function getName(): string;
}

class CSVExporter implements ExporterInterface
{   
    public static function getName(): string
    {
        return 'csv_exporter';
    }
}

class MyService
{
    public function __construct(
        #[Tagged(name: 'app.exporter', indexMethod: 'getName')] private ServiceLocator $tagged
    ) {}
    
    public function get(string $name): ExporterInterface
    {
        return $this->tagged->get($name);
    }
}

$exporter = $myService->get('csv_exporter'); // this would be instance of CSVExporter

If of any relevance, stub for ServiceLocator can be this:

/** 
 * @template T
 */
class ServiceLocator
{
    /** @return T */
    public function get($id){}
}
@zmitic
Copy link
Author

@zmitic zmitic commented Jan 21, 2021

Best case would be to allow interface usage instead of string-based tag names:

class MyService
{
    public function __construct(
        #[TaggedInterface(FormTypeInterface::class)] private ServiceLocator $tagged
    ) {}
    
    /**
    * @param class-string<FormTypeInterface> $className
    */
    public function get(string $className): FormTypeInterface
    {
        return $this->tagged->get($className);
    }
}

$formType = $myService->get(EntityType::class);

A feature like this could be useful for a day we get generics; tools like rector might provide automigration:

public function __construct(private ServiceLocator<FormTypeInterface> $tagged)
@nicolas-grekas
Copy link
Member

@nicolas-grekas nicolas-grekas commented Jan 21, 2021

#[Tagged(name: 'form.type')]

This looks like just one step after #39897 - could happen in 5.3 :)

#[TaggedInterface(FormTypeInterface::class)]

I would clearly advise against this. It's a shortcut that forgets that tags are absolutely required as intermediaries to create collections of services in a decoupled way.

But fortunately, together with #39804, you might end up being able to provide a very similar DX.

@zmitic
Copy link
Author

@zmitic zmitic commented Jan 21, 2021

I would clearly advise against this.

That is OK. My tagged services have same name as interface i.e.

$container->registerForAutoconfiguration(ExporterInterface::class)->addTag(ExporterInterface::class);

so I thought to maybe even remove this one line. But yes, it might be too much, sorry.

@nicolas-grekas
Copy link
Member

@nicolas-grekas nicolas-grekas commented Jun 21, 2021

PR welcome @zmitic

@zmitic
Copy link
Author

@zmitic zmitic commented Jun 21, 2021

@nicolas-grekas For #[TaggedInterface]?

If so; would I be allowed to create another class instead of ServiceLocator, and add stubs like in the first comment?

@nicolas-grekas
Copy link
Member

@nicolas-grekas nicolas-grekas commented Jun 21, 2021

For #[Tagged(name: 'form.type')] (see above about #[TaggedInterface])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
3 participants