-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[DependencyInjection] Make it easy to get a service from its FQCN #18300
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
Conversation
* | ||
* @return array the type map | ||
*/ | ||
public function populateAvailableTypes() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public
in order to optimize the classes using this feature (populate the types only once).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we make $this->typeMap
null by default (it actually is already) and then in getServiceIdsOfType
, call this private function if null === $this->typeMap
. Basically, this class should be able to manage the fact that all the types should be populated once, at the beginning of getServiceIdsOfType
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not exactly true, this method has to be called each time the class of a definition is updated.
What do you think of the last commit using \SplObserver
and \SplSubject
to remove this need ?
This is one of the cases where I actually think this would be very beneficial. The SensioFrameworkBundle could then add support for Would that means the "class" key can be omitted if services:
Foo\Bar: ~
Bar\Baz: ~ /**
* @Controller()
*/
class Foo\Bar
{
public function __construct(Bar\Baz $baz) {}
} Note that my wish is to rename the |
@iltar actually, the call would be And I like your idea but we have to investigate a bit more to see if it wouldn't create BC breaks/weird behaviour. |
anyone else review about this ? I'd like to know if this would be accepted or not before finalizing it (or if you prefer #18268). |
I think it's a nice improvement. |
Adding these methods to the |
@xabbuh this is useful to find services implementing a class at runtime, especially for controllers. |
@Ener-Getick and what if you make it like this: services:
Foo\Bar: ~
# same as, except that the key is the class name in the first example
services:
foo.bar:
class: Foo\Bar That means you can do |
@iltar this is not the issue solved by this PR, maybe I wasn't clear in its description. services:
app.foo:
class: Foo
app.bar:
class: Bar # extends Foo You can use this: $container->getServiceIdsOfType(Foo::class);
// will return ['app.foo', 'app.bar']
$container->getServiceIdsOfType(Bar::class);
// will return ['app.bar'] IMO this should not be used in a traditional app but it is useful for compiler passes or for classes needing to be very flexible (like the |
@xabbuh IMO we should keep at least |
Regarding the Controller Resolver, I'm working on deprecating the current method by introducing a factory (similar to the argument resolver). |
|
||
foreach ($reflectionClass->getInterfaces() as $reflectionInterface) { | ||
$this->set($reflectionInterface->name, $id); | ||
$this->autowiringTypes[$type] = $id; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we use the autowiring types in this feature ? Maybe should it be renamed then ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I know what you mean?
@xabbuh I removed the new methods of the |
I'm 👎 about this feature. It adds a new way to do what already can be done and I don't feel the new way is better. First, as explained by @Ener-Getick, this will throw exceptions in some cases because services don't have a one-to-one correspondence to classes (e.g. Doctrine repositories defined as services share the same class). Second, this is more complicated to explain. E.g. Before: if you want to get the main Doctrine service to access the different entity managers, just get $this->get('doctrine')->... After: use Symfony\Bridge\Doctrine\RegistryInterface;
$this->get(RegistryInterface::class)->... What's a registry? Why the interface? Why it doesn't include the |
I have the same feeling as @Ener-Getick. Since the landing of auto-wiring, I'm not comfortable with all the features that are proposed, they are quite far from the overall philosophy of the DI component, which has always favored explicitness. I'm also 👎 |
@javiereguiluz @fabpot and what do you think about the proposition of @xabbuh which is mainly meant to be used internally (only available at compilation)? An example of how to use it: $container->register('foo', 'Foo');
$container->register('bar', 'Bar'); // subclass of Foo
$container->getServiceIdsOfType('Foo');
// will return ['foo', 'bar']
$container->getServiceIdsOfType('Bar');
// will return ['foo'] It would only be possible to get the service ids of a certain type that way not their instance. |
* | ||
* @return string[] | ||
*/ | ||
public function getServiceIdsOfType($class, $reload = true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see a use for the $reload
parameter here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The controller definitions can be changed at any time so the type map has to be reloaded each time we use it.
This parameter is here to prevent this reload in case we are sure it is useless.
0ed266c
to
019ea01
Compare
The last commit uses |
$serviceIds = $this->container->getServiceIdsOfType($typeHint->name); | ||
if (1 === count($serviceIds)) { | ||
$value = new Reference($serviceIds[0]); | ||
} elseif (0 === count($serviceIds)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead you could use empty($serviceIds)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
After thinking, I created the utility class @dunglas proposed. This is much easier to maintain and to understand as we don't have to worry about definitions changes. |
@hason it's complex to share it as we have to reload the map each time a definition changes which is complicated to know (you can see my first try). |
c56d820
to
a923aa4
Compare
Something else that might be useful with something like this, is resolving the validator that belongs to a constraint. Right now you have to add a service id, but this could in theory be easy to resolve based on the class name, similar to form types. |
@iltar only if we allow this to be used at runtime (the current state is a class only available when compiling). |
Closing for now as it needs changes to work with the last changes. |
This would be really great, as it's autowiring by types is already present in the core. |
@TomasVotruba i agree but that's a big task and we have several ways to do it that all have benefits and disadvantages. I don't think i'll have the time to take a look at it again soon but maybe in a month or two i'll be able to reopen a pr :) |
@Ener-Getick Thank you for your persistence and work on this 👍. It's very difficult to make a mind-shift from named services to typed services. I'm starting something similar via external bundles. So it's optional and easy to integrate and test proof of concept: |
For you first example, you can take a look at DunglasActionBundle which does more or less the same thing but also applied to other part of the framework (commands, event subscribers, etc), the other one looks a bit redundant with the other one (and a bit more hacky). |
I know that bundle, but it does too much. I really need only the autowiring on controllers. Also didn't work on first use. The ActionAutowire is less clean, yet on half way to constructor injection. Rather see explanation here http://www.tomasvotruba.cz/blog/2016/09/19/how-to-get-more-than-request-in-controller-action/ |
@TomasVotruba @Ener-Getick You can use https://github.com/symfonette/class-named-services library. |
@hason this pr is for compile time whereas yours is for runtime, so that's not the same kind of use :) |
@GuilhemN Btw, do you have your utility class somewhere available as package? Would love to try it. |
@TomasVotruba no, but you can extract it from this PR if you want. |
@GuilhemN Sure, just prefer reusable open-source code. |
@TomasVotruba i understand, i'll reopen this pr for 3.3 when 3.2 will be released. |
see #20940 |
Alternative approach to #18268
This PR would allow people to use a new syntax to get their services by class name:
This is particularly useful internally (I updated the
AutowiringPass
to show an example). We can also imagine automatically determine the service corresponding to a controller.This could also be useful in third party bundles which could, for example, determine easily if a service already exists for a class (this is needed in
DunglasActionBundle
)./cc @hason @Nicofuma @dunglas