Skip to content

[DI] Add exclude to TaggedIterator and TaggedLocator #44774

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

Merged
merged 1 commit into from
Dec 28, 2021

Conversation

ruudk
Copy link
Contributor

@ruudk ruudk commented Dec 23, 2021

Q A
Branch? 6.1
Bug fix? no
New feature? yes
Deprecations? no
Tickets
License MIT
Doc PR

With this change, you can exclude one or more services from a tagged iterator
or locator.

It's common when working with Delegator or Chain services that call a list of services that
implement an interface, to also want to implement the interface on Delegator/Chain.

An example of this is in the Symfony HttpKernel component:

interface CacheClearerInterface
{
/**
* Clears any caches necessary.
*/
public function clear(string $cacheDir);
}

class ChainCacheClearer implements CacheClearerInterface
{
private iterable $clearers;
/**
* @param iterable<mixed, CacheClearerInterface> $clearers
*/
public function __construct(iterable $clearers = [])
{
$this->clearers = $clearers;
}

The ChainCacheClearer implements CacheClearerInterface and calls a list of CacheClearerInterface
clearers.

If that would have been configured with #[AutoconfigureTag] and #[TaggedIterator], the
ChainCacheClearer would receive itself in the iterable $clearers.

With this change, it can exclude itself and rely fully on autowire/configure.

Another example:

#[AutoconfigureTag]
interface ErrorTracker
{
    public function trackError(string $error): void;
}

final class SentryErrorTracker implement ErrorTracker
{
    public function trackError(string $error): void
    {
        echo "Send error to Sentry\n";
    }
}

final class NewRelicErrorTracker implement ErrorTracker
{
    public function trackError(string $error): void
    {
        echo "Send error to NewRelic\n";
    }
}

final class DelegatingErrorTracker implements ErrorTracker
{
    public function __construct(
        #[TaggedIterator(ErrorTracker::class, exclude: self::class)]
        private iterable $trackers
    ) {}

    public function trackError(string $error): void
    {
        foreach($this->trackers as $tracker) {
            $tracker->trackError($error);
        }
    }
}

// Alias ErrorTracker interface to DelegatingErrorTracker service

final class MyController
{
    public function __construct(private ErrorTracker $errorTracker) {}
    public function __invoke()
    {
        $this->errorTracker->trackError('Hello, World!');
    }
}

Without this change, the DelegatingErrorTracker would receive itself in the tagged
iterator.

@ruudk ruudk requested a review from dunglas as a code owner December 23, 2021 13:20
@carsonbot carsonbot added this to the 6.1 milestone Dec 23, 2021
@ruudk ruudk force-pushed the exclude-tagged-iterator branch 2 times, most recently from 0a9ed22 to 19509c0 Compare December 23, 2021 14:19
@ruudk
Copy link
Contributor Author

ruudk commented Dec 23, 2021

The fabbot.io suggestion doesn't make any sense.

@ruudk
Copy link
Contributor Author

ruudk commented Dec 23, 2021

@nicolas-grekas Curious what you think about this approach.

@carsonbot
Copy link

Hey!

I think @fractalzombie has recently worked with this code. Maybe they can help review this?

Cheers!

Carsonbot

@fractalzombie
Copy link
Contributor

@ruudk Hi, this is the first time I've seen such use of tags, I don't quite understand the rationality of this solution.

@ruudk

This comment has been minimized.

@Jeroeny
Copy link
Contributor

Jeroeny commented Dec 24, 2021

Should it be added to this method too DependencyInjection/Loader/Configurator/ContainerConfigurator.php#L142 ?

@ruudk
Copy link
Contributor Author

ruudk commented Dec 24, 2021

@Jeroeny Good one, added it to the to-do. First I would like to know if this is something that could be merged. Then I can improve/finalize the PR.

@wouterj
Copy link
Member

wouterj commented Dec 24, 2021

The fabbot.io suggestion doesn't make any sense.

Yes, you can ignore the suggestion. Fabbot tries really hard to act like a human, but sometimes they just can't hide being a robot :)

@nicolas-grekas nicolas-grekas changed the title Add exclude option to TaggedIterator attribute [DependencyInjection] Add exclude option to TaggedIterator attribute Dec 26, 2021
Copy link
Member

@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me, here are some notes. Thanks for submitting.

@ruudk ruudk force-pushed the exclude-tagged-iterator branch from 19509c0 to 3867821 Compare December 27, 2021 11:37
@ruudk ruudk changed the title [DependencyInjection] Add exclude option to TaggedIterator attribute [DependencyInjection] Add exclude to TaggedIterator and TaggedLocator Dec 27, 2021
With this change, you can exclude one or more services from a tagged iterator
or locator.

It's common when working with Delegator or Chain services that call a list of services that
implement an interface, to also want to implement the interface on Delegator/Chain.

An example of this is in the Symfony HttpKernel component:

https://github.com/symfony/symfony/blob/0d6e859db236e37b8baa2b2bf4c8b7d14d151570/src/Symfony/Component/HttpKernel/CacheClearer/CacheClearerInterface.php#L19-L25
https://github.com/symfony/symfony/blob/0d6e859db236e37b8baa2b2bf4c8b7d14d151570/src/Symfony/Component/HttpKernel/CacheClearer/ChainCacheClearer.php#L21-L31

The `ChainCacheClearer` implements `CacheClearerInterface` and calls a list of `CacheClearerInterface`
clearers.

If that would have been configured with `#[AutoconfigureTag]` and `#[TaggedIterator]`, the
`ChainCacheClearer` would receive itself in the `iterable $clearers`.

With this change, it can exclude itself and rely fully on autowire/configure.

Another example:

```php
#[AutoconfigureTag]
interface ErrorTracker
{
    public function trackError(string $error): void;
}

final class SentryErrorTracker implement ErrorTracker
{
    public function trackError(string $error): void
    {
        echo "Send error to Sentry\n";
    }
}

final class NewRelicErrorTracker implement ErrorTracker
{
    public function trackError(string $error): void
    {
        echo "Send error to NewRelic\n";
    }
}

final class DelegatingErrorTracker implements ErrorTracker
{
    public function __construct(
        #[TaggedIterator(ErrorTracker::class, exclude: self::class)]
        private iterable $trackers
    ) {}

    public function trackError(string $error): void
    {
        foreach($this->trackers as $tracker) {
            $tracker->trackError($error);
        }
    }
}

// Alias ErrorTracker interface to DelegatingErrorTracker service

final class MyController
{
    public function __construct(private ErrorTracker $errorTracker) {}
    public function __invoke()
    {
        $this->errorTracker->trackError('Hello, World!');
    }
}
```

Without this change, the `DelegatingErrorTracker` would receive itself in the tagged
iterator.
@ruudk ruudk force-pushed the exclude-tagged-iterator branch from 3867821 to c89fab7 Compare December 27, 2021 11:39
@ruudk
Copy link
Contributor Author

ruudk commented Dec 27, 2021

@nicolas-grekas Thanks for your review, I applied your feedback and added more tests. The CI fails due to unrelated errors on 6.1 branch.

@carsonbot carsonbot changed the title [DependencyInjection] Add exclude to TaggedIterator and TaggedLocator Add exclude to TaggedIterator and TaggedLocator Dec 28, 2021
@fabpot
Copy link
Member

fabpot commented Dec 28, 2021

Thank you @ruudk.

@fabpot fabpot merged commit 3aa976b into symfony:6.1 Dec 28, 2021
@ruudk ruudk deleted the exclude-tagged-iterator branch December 28, 2021 14:25
@fabpot fabpot mentioned this pull request Apr 15, 2022
@ruudk ruudk changed the title Add exclude to TaggedIterator and TaggedLocator [DI] Add exclude to TaggedIterator and TaggedLocator Apr 16, 2022
nicolas-grekas added a commit that referenced this pull request Oct 19, 2022
…/locators exclude option to xml and yaml (HypeMC)

This PR was merged into the 6.2 branch.

Discussion
----------

[DependencyInjection] Add support for tagged iterators/locators exclude option to xml and yaml

| Q             | A
| ------------- | ---
| Branch?       | 6.2
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | -
| License       | MIT
| Doc PR        | -

Followup to #44774 .

The previous PR added support for the `exclude` option to php attributes & php config files, but not xml or yaml, making it impossible to use this feature with those config types.
Also, since the support for this feature was never added to the yaml & xml dumpers, the generated `App_KernelDevDebugContainer.xml` is actually incorrect (possibly should be considered a bug), eg the following php configuration:

```php
    $services->set(TestExclude::class)
        ->public()
        ->args([
            tagged_iterator('app.test', exclude: TestExclude::class),
        ]);
```

produces this xml file:

```xml
    <service id="App\Service\TestExclude" class="App\Service\TestExclude" public="true">
      <tag name="app.test"/>
      <argument type="tagged_iterator" tag="app.test"/>
    </service>
```

With this PR, the dumpers are now aware of the `exclude` option:

```diff
    <service id="App\Service\TestExclude" class="App\Service\TestExclude" public="true">
      <tag name="app.test"/>
-     <argument type="tagged_iterator" tag="app.test"/>
+     <argument type="tagged_iterator" tag="app.test" exclude="App\Service\TestExclude"/>
    </service>
```

Commits
-------

9afa5c9 [DI] Add support for tagged iterators/locators `exclude` option to xml and yaml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants