Skip to content

Conversation

@dg
Copy link
Member

@dg dg commented Sep 10, 2015

All services are autowired. setAutowired(TRUE) means that service is preferred, setAutowired(FALSE) means that is autowired too, but not preferred (yes, name setAutowired(FALSE) is weird here, but this is just experiment).

Only services that are excluded via $excludedClasses are not autowired.

Now you can create services FileStorage (autowired: on) and DevNullStorage (autowired: off) and access the first one via getByType('IStorage') and second one viagetByType('DevNullStorage')`.

I tried a different approach

@dg
Copy link
Member Author

dg commented Sep 10, 2015

cc @matej21

@matej21
Copy link
Contributor

matej21 commented Sep 10, 2015

👍

@dg
Copy link
Member Author

dg commented Sep 10, 2015

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.

@ondrejmirtes
Copy link
Contributor

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?

Copy link
Contributor

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

Copy link
Member Author

Choose a reason for hiding this comment

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

findByType() is not affected.

Copy link
Contributor

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 :)

@dg
Copy link
Member Author

dg commented Sep 10, 2015

@ondrejmirtes this can be solved in two ways:

  1. option autowired will have three states: off, on and preferred (the default one), so you can disable autowiring in the same way as before
  2. option autowired will have two states: on and preferred (the default one?) and you can disable autowiring via excluded classes and they will be configurable via config.neon.

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 autowire: off, but it fits perfectly to your scenario. You can disable autowiring for i.e ILogger, for all services with a single line, so it is less error prone.

If people use autowired: no in sense not preferred, the 2) is fine. If they use it in sense „prevent accidental injection“, the 2) is maybe better solution too.

(Now I am not talking about back compatibility, it will remain, just about best practices.)

@fprochazka
Copy link
Contributor

I've tried it and it doesn't exactly work :) But I don't have much time to dig in.

snimek obrazovky porizeny 2015-09-11 20 49 35

@MartinMystikJonas
Copy link

@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.

@dg
Copy link
Member Author

dg commented Sep 14, 2015

@MartinMystikJonas I think it is the same as first option here #84 (comment)

@MartinMystikJonas
Copy link

@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 - IStorage, FileStorage ->setAutowire(3)
Service B - IStorage, FileStorage, MyFileStorage ->setAutowire(2)
Service C - IStorage, DevNullStorage ->setAutowire(1)

getByType('IStorage') -> Service A
getByType('FileStorage') -> Service A
getByType('MyFileStorage') -> Service B
getByType('DevNullStorage') -> Service C

@dg
Copy link
Member Author

dg commented Sep 14, 2015

Service A with setAutowired(2) and B & C with setAutowired(1) will have the same result, or not?

@MartinMystikJonas
Copy link

You are right. My solution is only useful in even more complex situations and therefore probably not needed.

@JanTvrdik
Copy link
Contributor

👍 Just found out I need this.

@enumag
Copy link
Contributor

enumag commented Oct 28, 2015

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.

@brabijan
Copy link

brabijan commented Nov 3, 2015

👍

@paveljanda
Copy link

👍

@milo milo added this to the v2.4 milestone Mar 29, 2016
@dg dg force-pushed the master branch 2 times, most recently from 231a29c to 7f12a9f Compare April 21, 2016 13:03
@dg dg force-pushed the autowire-alt branch from e556e3c to 213c117 Compare May 8, 2016 01:24
@dg
Copy link
Member Author

dg commented May 8, 2016

Now option autowired can contain name or names of classes/interfaces. It make services autowired only for specified types and preferably.

$defAutowired = $def->isAutowired();
if (is_array($defAutowired)) {
foreach ($defAutowired as $k => $aclass) {
if ($aclass === self::THIS_SERVICE) {
Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Contributor

@JanTvrdik JanTvrdik May 8, 2016

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?

@JanTvrdik
Copy link
Contributor

JanTvrdik commented May 8, 2016

Because you choose the instance of approach instead of === approach, I don't understand why would you ever need to declare multiple autowire types, i.e. why support bool|array|string and not just bool|string.


But maybe I'm missing something. I had a bit of trouble reading the code.

@dg
Copy link
Member Author

dg commented May 8, 2016

I tried to implement a completely different solution. Option autowired can be true or false (the same meaning), or class name or list of class names.

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 autowired: self), service will be autowired only when argument typehint === specified interface.

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.

@dg dg force-pushed the autowire-alt branch from 213c117 to efa9d25 Compare May 8, 2016 07:56
@JanTvrdik
Copy link
Contributor

I think I finally understand it now, the getByType logic can be rewritten in pseudocode as

coalesce(
    definitions.filter(_.class instanceof type).filter(type instance of _.autowired),
    definitions.filter(_.class instanceof type).filter(_.autowired === true)
)

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'));
});

@JanTvrdik
Copy link
Contributor

JanTvrdik commented May 8, 2016

Having a class hierarchy graph (including interfaces of course), the setClass() marks one node (green) in graph and setAutowired() marks the second node (orange). The resulting autowired types are all nodes along the path from the setClass() node to the setAutowired() node.

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()
Copy link
Contributor

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.

@dg dg force-pushed the autowire-alt branch from efa9d25 to 286167d Compare May 9, 2016 01:19
@dg dg force-pushed the master branch 7 times, most recently from 9fbd1d4 to a71f267 Compare May 11, 2016 07:11
@dg dg force-pushed the autowire-alt branch from 86b3bd8 to 6cbe9c5 Compare May 19, 2016 23:20
@dg
Copy link
Member Author

dg commented May 20, 2016

@JanTvrdik I'll use your more explicit tests

@dg dg force-pushed the autowire-alt branch from 6cbe9c5 to 61e4ba7 Compare May 20, 2016 09:44
@dg dg merged commit 83b6a27 into nette:master May 20, 2016
@dg dg deleted the autowire-alt branch May 20, 2016 09:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.