-
-
Notifications
You must be signed in to change notification settings - Fork 75
Alternative autowiring approach [WIP] #84
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
|
cc @matej21 |
|
👍 |
|
I wonder how this commit affects large containers (could it try @fprochazka, @ondrejmirtes, @pilec, …)? In my projects containers are the same, only with minor difference in meta data. |
|
I use setting autowired to off to exclude services from being accidentally injected somewhere where I do not want them. For example I have all instances of Monolog\Logger set to autowired: off because when I ask for a logger in a constructor, I want the DI container to fail and require specifying which concrete logger I want to inject into the service. I am afraid that this change will lead to injecting of random unwanted services. Or is my scenario somehow solvable after this change? |
src/DI/ContainerBuilder.php
Outdated
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.
This breaks ApplicationExtension which looks for presenters by excluded class name: https://api.nette.org/2.3.5/source-Bridges.ApplicationDI.ApplicationExtension.php.html#97
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.
findByType() is not affected.
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.
Oh, I didn't notice commit 6b24a1a :)
|
@ondrejmirtes this can be solved in two ways:
I have absolutely no idea which approach is better. The 1) is close to current approach, but three states will make it very difficult for programmers to figure out which state they should use. The 2) is really different, because there is not If people use (Now I am not talking about back compatibility, it will remain, just about best practices.) |
|
@dg: Just idea: What abou setAutowired($priority). FALSE = not autowire, TRUE=1=autowired lowest priority, 2+=autowired with ascending priority. It would be backward compatible but sloves what I think you trying to solve. |
|
@MartinMystikJonas I think it is the same as first option here #84 (comment) |
|
@dg Basically yes. But my solution can solve this for deeper inheritance hierarchies when you need to set prefferences when multiple services in inheritance hierarchies matches searched type. For example:
|
|
Service A with setAutowired(2) and B & C with setAutowired(1) will have the same result, or not? |
|
You are right. My solution is only useful in even more complex situations and therefore probably not needed. |
|
👍 Just found out I need this. |
|
One thing that I don't like in Nette/DI is overriding of autowired services by another extension. For example I've an extension which provides alternative implementation of Nette/Application/Application. Now what the extension can do to override the default implementation? In my opinion it's wrong for the custom extension to change the original service in any way (changing class/factory, turning of autowiring etc.) - but it currently can't be solved without it. This PR doesn't solve this of course. I'm mentioning it because it could solve it (by using priorities or something). If you think it's off-topic, just ignore this comment. |
|
👍 |
|
👍 |
231a29c to
7f12a9f
Compare
|
Now option |
| $defAutowired = $def->isAutowired(); | ||
| if (is_array($defAutowired)) { | ||
| foreach ($defAutowired as $k => $aclass) { | ||
| if ($aclass === self::THIS_SERVICE) { |
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.
So you can write autowire: [self]? Is that useful?
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.
It is autowired only when typehint === class name.
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.
Yes, but in what instances would you write self instead of writing directly the class name?
Also, it's not autowired when typehint === class name but when typehint instanceof class name, or not?
|
|
|
I tried to implement a completely different solution. Option In this case, autowiring is limited only to these classes. For example, when class implements three interfaces and I want to autowired it only for one or two of them, I will enumerate these interfaces. So when argument typehint matches of one of enumerated interfaces, service will be used for autowiring. Therefore when I will specify it's own class name (or use At the same time, in addition to limiting the class list, the service becomes for that type preferred. So when there will be multiple (normally) autowired services that meet the specified class/interface, this service with specific option will win. |
|
I think I finally understand it now, the getByType logic can be rewritten in pseudocode as Making the following 8 tests cover the core logic. test(function () {
$builder = new DI\ContainerBuilder;
$builder->addDefinition('bar')
->setClass('Bar')
->setAutowired('Bar');
Assert::same('bar', $builder->getByType('Bar'));
Assert::same(NULL, $builder->getByType('IBar'));
Assert::same(NULL, $builder->getByType('Foo'));
Assert::same(NULL, $builder->getByType('IFoo'));
});
test(function () {
$builder = new DI\ContainerBuilder;
$builder->addDefinition('bar')
->setClass('Bar')
->setAutowired('IBar');
Assert::same('bar', $builder->getByType('Bar'));
Assert::same('bar', $builder->getByType('IBar'));
Assert::same(NULL, $builder->getByType('Foo'));
Assert::same(NULL, $builder->getByType('IFoo'));
});
test(function () {
$builder = new DI\ContainerBuilder;
$builder->addDefinition('bar')
->setClass('Bar')
->setAutowired('Foo');
Assert::same('bar', $builder->getByType('Bar'));
Assert::same(NULL, $builder->getByType('IBar'));
Assert::same('bar', $builder->getByType('Foo'));
Assert::same(NULL, $builder->getByType('IFoo'));
});
test(function () {
$builder = new DI\ContainerBuilder;
$builder->addDefinition('bar')
->setClass('Bar')
->setAutowired('IFoo');
Assert::same('bar', $builder->getByType('Bar'));
Assert::same(NULL, $builder->getByType('IBar'));
Assert::same('bar', $builder->getByType('Foo'));
Assert::same('bar', $builder->getByType('IFoo'));
});
test(function () {
$builder = new DI\ContainerBuilder;
$builder->addDefinition('bar')
->setClass('Bar')
->setAutowired(['IFoo', 'IBar']);
Assert::same('bar', $builder->getByType('Bar'));
Assert::same('bar', $builder->getByType('IBar'));
Assert::same('bar', $builder->getByType('Foo'));
Assert::same('bar', $builder->getByType('IFoo'));
});
test(function () {
$builder = new DI\ContainerBuilder;
$builder->addDefinition('bar')
->setClass('Bar')
->setAutowired(['Foo', 'Bar']);
Assert::same('bar', $builder->getByType('Bar'));
Assert::same(NULL, $builder->getByType('IBar'));
Assert::same('bar', $builder->getByType('Foo'));
Assert::same(NULL, $builder->getByType('IFoo'));
});
test(function () {
$builder = new DI\ContainerBuilder;
$builder->addDefinition('bar')
->setClass('Bar')
->setAutowired(['Foo', 'IBar']);
Assert::same('bar', $builder->getByType('Bar'));
Assert::same('bar', $builder->getByType('IBar'));
Assert::same('bar', $builder->getByType('Foo'));
Assert::same(NULL, $builder->getByType('IFoo'));
});
test(function () {
$builder = new DI\ContainerBuilder;
$builder->addDefinition('bar')
->setClass('Bar')
->setAutowired(['IFoo', 'Bar']);
Assert::same('bar', $builder->getByType('Bar'));
Assert::same(NULL, $builder->getByType('IBar'));
Assert::same('bar', $builder->getByType('Foo'));
Assert::same('bar', $builder->getByType('IFoo'));
}); |
|
Having a class hierarchy graph (including interfaces of course), the test(function () {
$builder = new DI\ContainerBuilder;
$builder->addDefinition('bar')
->setClass('Bar')
->setAutowired('IFoo');
Assert::same('bar', $builder->getByType('Bar'));
Assert::same(NULL, $builder->getByType('IBar'));
Assert::same('bar', $builder->getByType('Foo'));
Assert::same('bar', $builder->getByType('IFoo'));
});😍 |
| * @return bool | ||
| * @return bool|string[] | ||
| */ | ||
| public function isAutowired() |
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.
Method with isX name, that returns bool and string looks really confusing to me.
I'd keep preferred autowired types and TRUE/FALSE state separated.
9fbd1d4 to
a71f267
Compare
|
@JanTvrdik I'll use your more explicit tests |


All services are autowired.setAutowired(TRUE)means that service is preferred,setAutowired(FALSE)means that is autowired too, but not preferred (yes, namesetAutowired(FALSE)is weird here, but this is just experiment).Only services that are excluded via
$excludedClassesare not autowired.Now you can create services FileStorage (autowired: on) and DevNullStorage (autowired: off) and access the first one viagetByType('IStorage') and second one viagetByType('DevNullStorage')`.I tried a different approach