-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[DI] Support getter-based dependency injection #20657
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
Comments
Should it support abstract methods too ? We would of course have to define the abstract methods in the service declaration <?php
class foo {
abstract protected/public function getBar();
} And the proxy would be <?php
class extends foo {
private $container;
protected/public function getBar()
{
return $this->container->get('for_foo');
}
} for about the same service declaration you mentionned |
You should read the link I posted, you'll find exactly this example :) So yes, it will work by nature. |
this seems interesting. Note that the XML file should not use |
I just realized that getter-injection would have another big benefit: immutability! (if no corresponding setter exists of course) |
You compare getter injection with setter one here, but constructor injection has no such issue as well. Well, just don't use setter injection, it is always can be avoided. So is it fair to mention it as a benefit of getter injection?
This is true, but the only difference I see with existing lazy-services is that is allows to lazy-load services with This approach, in my opinion, has big disadvantage: it hurts class API (no ability to add In which cases getter injection is better than constructor one? |
@unkind every approach has drawbacks and benefits. That's fair. That's why we need all of them. |
@nicolas-grekas So what's the benefit this approach has over existing constructor injection? |
Laziness in the core! In a much more reasonable way than the current proxy-manager, which is an optional dep (and should remain such). |
Magic getter which works with this DI-container only is "much more reasonable" way? Why?
What's the problem? If you don't want to load this dep, so you probably don't need lazy-services. |
There is nothing magic, it's plain inheritance. I didn't wait to use Symfony to "open/close" some of my classes by providing a protected method for alternative behavior injection. Symfony DI was laking a way to take advantage of this design. Here is a PR to fill the hole. |
ProxyManager utilizes inheritance as well. I call getter injection "magic", because it doesn't simply work w/o your DI-container, I don't see dependencies in the constructor, they come from nowhere. |
I can imagine some situations where this would make sense, the Template pattern (for abstract classes) is exactly designed for this type of usage, plus with PHP 7 return types you can still guard it provides a correct implementation 👍 Else you can have an, oh.. he 😄 default implementation which can be replaced with a lazy loading version without the need of another class! 😵 of course you need to allow this type of replacement, but that's a choice "you" make. @unkind I share your concerns, this is not suitable for every situation but there are use-cases. |
I don't understand the feature, or it doesn't make sense to me. You just described what looks like factories to me, and then you replace the factory code completely, which is a big assumption. In my opinion, this is a big leap of faith, and you may also break internal code. Every time I tried to push the boundaries of the ProxyManager to allow scenarios like this one, I had huge issues in functional testing, since loads and loads of edge cases arise. So yes, dumping huge proxy classes is generally better than overriding implementations, because you only rely on the public signature, and never on the implementation. That's why it is transparent, and why I wrote over a damn megabyte of tests to ensure complete transparency at all times: it ABSOLUTELY MUST NOT feel nor behave magically. What can be done (and cannot be done immediately, since it needs accurate analysis first) is customising proxies to intercept only method calls to some "factory methods" to add:
This is what bitExpert/disco does, so you can directly look at that codebase for examples. Please also note that Disco doesn't alter method implementations, but only proxies the container itself, as well as the services being retrieved (much like symfony/dependency-injection currently does). Recap: DO NOT replace method implementations. DO NOT trust that you know why a method is implemented in a certain way. |
Don't think this way, please: this is where maintenance nightmares (for both vendor and consumer) start. |
Wouldn't this kind of couple the behavior with the container impl? I mean: are you proposing people to hardcode dependencies in getters ? That seems like a bad idea to me. |
People relying on this feature will write code that simply wouldn't work without the DIC, no? PS: by "wouldn't work", I mean wouldn't allow to change the harcoded factory. |
Wow, so much strong affirmative and negative statements here. Please keep the discussion open! |
@nicolas-grekas discussion is open, I'm just warning based on first hand bad experiences and failed attempts. If I didn't misinterpret (to be seen after a generated DIC PoC) what is being suggested here, I see lots of new problems. |
Agree. I already imagine classes with only stub methods, relying to be overridden with DI. That said, i dont see how this relates to dependency injection, i mean we're combining it with a proxy generator thingy.. right? Finally i would like to know when to use lazy getter overriding, opposed to lazy setter/constructor injection? Im not sure saving on a composer package justifies changing your approach.. |
The more I look on this issue, the more I think about it as autowiring implementation. See my gist https://gist.github.com/lisachenko/6345683 for the idea. This magic closure binding should be placed into the one single library to keep it clear and simple. It will be a nightmare to add one more layer into the symfony DI itself, because of increasing complexity. @nicolas-grekas maybe it's time to use AOP proxy generation for super-lazy control over the source-code? AOP has access to the protected properties and getters, can inject services and much more. It's also possible to implement fully decoupled app with super-lazy property injection. Super-lazy property injection means that service will be injected only during the real access or method call. AOP will unset getters and store information about how to inject this service, then magic getter for the class will be called that will inject our dependency. That's it ) BTW, my solution also works with final classes: engine removes |
sorry, didn't want to be offensive. I totally agree with you on trying and see how it goes. I just wanted to give my 2 cents. |
The PR is ready, it just misses a few tests (and doc). For those who'd like to try it, the configuration is the same as for properties, except of course that you should use getter/getters where property/properties is allowed in yaml/xml. "Key" is the name of the getter, "value" the return value of the getter. @lisachenko I agree with you that this converges with autowiring, and in fact we have some nice plan with @dunglas to take advantage of getter injection (Kevin might be working on it right now so we'll see how it shapes soon). I wouldn't go as far as doing AOP code rewriting for now. At least that doesn't look like an "easy pick", while this RFC was quite "easy" to implement. But if you see it coming closer, then that's nice :) |
doing this kind of overriding without any DIC is trivial: in tests just use e.g. phpunit's mock system (which basically does exactly that). Otherwise, use anonymous classes. Done in a few lines. No DIC, just plain PHP. Nothing designed "for Symfony". Decoupling is at its best (one of them). In your comment @docteurklein, it looks like you're thinking that people design their code to fit the DIC. Obviously, that's always a very bad idea so I don't take this path. Instead I like thinking about empowering people (me being the first "people"). I'm missing getter injection because I'm used since years to design some of my classes using a dynamic getter when I know that the related dependency is heavy to instantiate but not required all the time. If you look at e.g. Doctrine DBAL, that's almost exactly what they do also with the As you see with the Doctrine example (and I'm sure you'll find more), laziness is a first class concern in some libs. Saying "use So, this is using a getter as an internally managed initializer yes. That's fine when it's your responsibility to deal with laziness. Should the getter be open to extensibility? It could. This is just using inheritance to do some open/closed principle. Nothing new here. Symfony is just missing a way to take advantage of such design. Nice! Note that I never suggested that getter injection should rule them all. It just fits some use cases and as such, empowers the corresponding people. Nothing more. |
Strong statement. You can write something like final class MyLazyConnectionWithoutAnyFramework implements Connection
{
public function __construct(ConnectionFactory $connectionFactory, string $host, ...)
{
// ..
}
public function query(...)
{
if (!$this->connection) {
$this->connection = $this->connectionFactory->...
}
$this->connection->query(...);
}
}
Same I can say about It's about composition over inheritance. By some reason, you force inheritance where we can simply work with composition. I don't understand it.
|
@unkind where is this code using
but obviously not all of them are private... |
Let's say you have services:
connection:
alias: mysql_connection
mysql_connection:
class: Acme/MySqlConnection
lazy: true
arguments: [%mysql_host%, %mysql_user%] Then dev is like: "I don't want to have ProxyManager dep for some weird reason, let's get rid of it!" services:
connection:
alias: lazy_connection
lazy_mysql_connection:
class: Acme\MyLazyConnectionWithoutAnyFramework
arguments: [%mysql_host%, %mysql_user%]
|
OK, that's almost exactly what I tried to illustrate with my DBAL example. |
I read the linked article and the entire discussion here and I really only have one thing to say: everyone needs to calm down with their apocalypse-destined imaginations. The PR seems particularly harmless in terms of overhead and offers an alternate approach to OAC/DI/IOC (that has been implemented in many other frameworks/languages) that might be appropriate for different contexts. From my perspective, complaints about lack of testability are overblown as well: regardless of OAC/DI/IOC there is setup/teardown involved. Offering choice/options for the programmer isn't a bad thing, even if it isn't right for your project, and all methods have their tradeoffs. @nicolas-grekas Thanks for all your work on Symfony and this RFC/PR 👍 |
@nicolas-grekas thought about this today, and i definitely appreciate this feature (so dont get me wrong :). What about a more generic approach, so it allows for a) But maybe im going too far with this now ;-) |
Yep, totally. Missed it.. thanks! |
This PR was merged into the 3.3-dev branch. Discussion ---------- [DI] Add getter injection | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #20657 | License | MIT | Doc PR | symfony/symfony-docs#7300 Getter overriding by the container will allow a new kind of dependency injection which enables easier laziness and more immutable classes, by not requiring any corresponding setter. See linked issue for more. This is WIP: - [x] wire the concept - [x] dump anonymous classes with PhpDumper - [x] generate at runtime in ContainerBuilder::createService - [x] tests - [x] make it work on PHP 5 Commits ------- cb49858 [DI] Add getter injection
A concept well described in e.g. this thread:
http://www.theserverside.com/news/thread.tss?thread_id=26205
and also in #835
Basically, one could then be able override the
getConnection
method of such a class:with a generated proxy (
$this->container
auto-injected by some to-be-defined mean):in XML, this could defined with something like e.g.:
so that
$container->get('foo')
returns an instance of the generated class (and by this mean an instance offoo
of course).The benefits?
The text was updated successfully, but these errors were encountered: