Skip to content

[DependencyInjection] add AsDecorator class attribute and InnerService parameter attribute #45834

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
Apr 19, 2022

Conversation

Jean-Beru
Copy link
Contributor

@Jean-Beru Jean-Beru commented Mar 24, 2022

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

The aim f this PR is to allow decorator declaration with PHP8 attributes by using AsDecorator attribute on class and, optionally, InnerService parameter attribute to inject decorated service.

To use it :

interface FooInterface
{
    public function myMethod(): string;
}

final class Foo implements FooInterface
{
    public function myMethod(): string
    {
        return 'foo';
    }
}

#[AsDecorator(
    decorates: Foo::class, 
    priority: 5, 
    onInvalid: ContainerInterface::NULL_ON_INVALID_REFERENCE,
)]
final class Bar implements FooInterface
{
    private string $arg1;
    private ?FooInterface $foo;

    public function __construct(string $arg1, #[InnerService] FooInterface $foo = null)
    {
        $this->arg1 = $arg1;
        $this->foo = $foo;
    }

    public function myMethod(): string
    {
        if (null === $this->foo) {
            return 'bar';
        }

        return $this->foo->myMethod().' bar ';
    }
}

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Mar 24, 2022

Thanks for the PR.

I see several issues with the current approach:

  • loading all classes in the container is no-go (this could have a huge perf impact at compile-time)
  • unconditionally looking for attributes is no-go (this breaks their declarative property by making them imperative configuration)
  • we should avoid using a generic name (Service) - future needs can be handled by new attributes. We could use #[AsDecorator] maybe?
  • decorationArgumentKey and decorationInnerName don't make sense to me. We could use another attribute instead, put on an argument, to tell where the decorated service should be injected, eg function __construct(#[DecoratedService] $decorated), or #[Inner].

Instead of adding a new pass, I think the attributes should be looked for in AutowirePass, where there is already some logic that deals with decoration. This would fix all listed issues I believe.

@Jean-Beru Jean-Beru requested a review from dunglas as a code owner April 8, 2022 21:00
@nicolas-grekas
Copy link
Member

(please mind the PR title+description also ;))

@Jean-Beru Jean-Beru changed the title [DependencyInjection] add Service class attribute [DependencyInjection] add AsDecorator class attribute and Inner parameter attribute Apr 14, 2022
@Jean-Beru
Copy link
Contributor Author

Thanks for your reviews ! Code and description have been updated :)

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.

LGTM. Can you please confirm that you ran this on a small project and that it worked as expected?

Copy link
Member

@dunglas dunglas left a comment

Choose a reason for hiding this comment

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

👍

@Jean-Beru
Copy link
Contributor Author

Jean-Beru commented Apr 15, 2022

I tried from a new demo application with :

final class Foo implements FooInterface
{
    public function __invoke(): string
    {
        return 'new '.__CLASS__.'()';
    }
}

#[AsDecorator(decorates: Foo::class, decorationPriority: 10)]
final class Bar10 implements FooInterface
{
    private FooInterface $foo;

    public function __construct(#[InnerService] FooInterface $foo)
    {
        $this->foo = $foo;
    }

    public function __invoke(): string
    {
        return 'new '.__CLASS__.'('.($this->foo)().')';
    }
}

#[AsDecorator(decorates: Foo::class, decorationPriority: 20)]
final class Bar20 implements FooInterface
{
    private FooInterface $foo;

    public function __construct(#[InnerServicce] FooInterface $foo)
    {
        $this->foo = $foo;
    }

    public function __invoke(): string
    {
        return 'new '.__CLASS__.'('.($this->foo)().')';
    }
}

#[AsController]
class TestController
{
    private FooInterface $foo;

    public function __construct(#[Autowire(service: Foo::class)] FooInterface $foo)
    {
        $this->foo = $foo;
    }

    #[Route('/test/')]
    public function index(): Response
    {
        return new Response(($this->foo)());
    }
}

Calling /test/ returns new App\Services\Bar10(new App\Services\Bar20(new App\Services\Foo())).

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.

Thanks! I'm just bike-shedding on the naming, WDYT?
Esp Inner vs DecoratedService? (or just Decorated)?
Any opinion @symfony/mergers? (see #45834 (comment) for examples)

@Jean-Beru Jean-Beru changed the title [DependencyInjection] add AsDecorator class attribute and Inner parameter attribute [DependencyInjection] add AsDecorator class attribute and InnerService parameter attribute Apr 15, 2022
@nicolas-grekas nicolas-grekas force-pushed the di/add-service-attribute branch from c0c5468 to d8a4680 Compare April 19, 2022 13:20
@nicolas-grekas
Copy link
Member

Thank you @Jean-Beru.

@nicolas-grekas nicolas-grekas merged commit 84d35a2 into symfony:6.1 Apr 19, 2022
@Jean-Beru Jean-Beru deleted the di/add-service-attribute branch April 19, 2022 16:23
fabpot added a commit that referenced this pull request Apr 22, 2022
…apDecorated]` (chalasr)

This PR was merged into the 6.1 branch.

Discussion
----------

[DependencyInjection] Rename `#[InnerService]` to `#[MapDecorated]`

| Q             | A
| ------------- | ---
| Branch?       | 6.1
| Bug fix?      | no
| New feature?  | no but tweaks one #45834
| Deprecations? | no
| Tickets       | -
| License       | MIT
| Doc PR        | -

When talking about the decorator pattern outside of Symfony, the term `inner` is pretty much (if not totally) inexistent. Worse, it makes the concept harder to explain. Take "there is a decorator and a decorated object" vs "there is a decorator and an inner object": using the later form, one has to explain both what is the decorator and what is the inner. While using the former, explaining what the decorator makes it obvious what the decorated is.

I propose to take the addition of this attribute as an opportunity to start making this special term go away.

Also I removed the `Service` suffix. It is tempting to keep it for explicitness, but it feels kinda redundant and AFAIK no other core attribute has such redundant suffix, maybe because their namespace is enough to indicate their target.

Commits
-------

c0a7979 [DependencyInjection] Rename `#[InnerService]` to `#[MapDecorated]`
@fabpot fabpot mentioned this pull request Apr 27, 2022
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.

5 participants