Autowiring problem with 2 services having the same parent

3 years ago

helvete
Member | 12
+
+1
-

Hello,

I would like to know whether there is a solution how to define two services (of which one extends the other) within a config file in order to be autowired.

This emerges quite often since our application is based on modules.

An example follows:
Classes:

<?php
namespace App\Models\AdvertiserUser;
class Collection extends \Company\Modules\User\Collection {
}
?>

This one (^) extends the other one (V).

<?php
namespace \Company\Modules\User;
class Collection extends \Company\Modules\Core\DataModel\Collection {
}
?>

App config:

<?php
services:

    - App\Models\AdvertiserUser\IModelFactory
    adUsersCollection:
        class: App\Models\AdvertiserUser\Collection('advertiser_user', @App\Models\AdvertiserUser\IModelFactory)
?>

Module config:

<?php
services:
    userCollection:
        factory: Company\Modules\User\Collection('user', @Company\Modules\User\IModelFactory)
?>

results in Nette\DI\ServiceCreationException like this:

Multiple services of type Company\Modules\User\Collection found: userCollection, adUsersCollection

I can surely work it around somehow, but I am interested in whether it can be done a proper way.

Last edited by helvete (2016-04-27 14:33)

3 years ago

CZechBoY
Member | 3393
+
0
-

And how do you recognize which class you want use?
If you inject the factory for extending class there is no problem.

3 years ago

helvete
Member | 12
+
0
-

I recognize between them easily as I define the exact class in the injection comment like this:

<?php
class Model extends \Company\Core\DataModel\Model
{
    /** @var \App\Models\AdvertiserUser\Collection @inject */
    var $adUserCollection;

    ...
?>

and

<?php
class AccountPresenter extends \Company\Core\Presenters\BasePresenter
{
    /** @var \Company\Modules\User\Collection @inject **/
    var $userCollection;
?>

The problem is that \App\Models\AdvertiserUser\Collection extends \Company\Modules\User\Collection and container builder dies when trying to parse the configs.

These services are registered each in a separate config file because one is part of a separate module and the other is a part of the application itself.

Since these classes are part of the model I cannot see any advantage by injecting a fatory – I need different object type each time.

The question is more about how can this be achieved in theory, situations like this are quite common.

I already found one workaround to this, but I don't like it:

Object A (parent) (\Company\Modules\User\Collection)
Object B (extended-functionality) (\App\Models\AdvertiserUser\Collection)
Object C (dummy wrapper) (\Empty\Wrapper\Around\Company\Modules\User\Collection)

Let's assume we have one parent class (A) and one class that extends it (B). These two cannot be registered in DI configuration both. The workaround I told about would be not to address the (A) as (A) but to create a dummy wrapper (empty extension class) ©, then register in config and address the (A) in the application as ©. Which is not a way to go, but it would work.

3 years ago

CZechBoY
Member | 3393
+
0
-

Do you need only 1 instance in application or can you have more instances? If you can have more then you can create factory, which returns new instance.

interface ICompanyCollection
{
    /** @return Company\Collection */
    function create();
}
interface IAppCollection
{
    /** @return App\Collection */
    function create();
}

register both to DIC and inject which you need…

3 years ago

helvete
Member | 12
+
0
-

Thanks for your response.

I will try to manage a solution using a factory like you suggested, but I don't like the approach very much because I would like to process all models the same way. I will look into this and we will see.

Anyway the standard container builder process is weird anyway. Why there could not be two services defined within configs in case one is parent of the other? Unexpected behaviour definitely.

3 years ago

David Matějka
Moderator | 5960
+
+1
-

You can disable autowiring for one of the services:

services:
    adUsersCollection:
        class: App\Models\AdvertiserUser\Collection('advertiser_user', @App\Models\AdvertiserUser\IModelFactory)
        autowired: false

but then you would have to manually specify dependency everywhere you use this service, e.g

services
    - SomeModel(@adUsersCollection)

btw, are you sure you need to use inheritance?

3 years ago

helvete
Member | 12
+
0
-

Currently I made it working exactly by disabling the inheritance between the two services (model collections). It works OK this way, but the contents of these file is unnecessarily duplicated (1:1).

Disabling autowiring is no solution for me, it would have been like destroying a virus by killing a patient;-)

Anyway I was wondering how to solve the problem generally. Not to be helped with an individual issue. So something like “It is not possible to achieve what you are trying to.” is an acceptable answer for me (if it is so, obviously).

Thanks

Last edited by helvete (2016-04-28 10:55)