2

I'm wondering how (if I can) register a shared service with a custom factory.

Consider the following code:

class MyApiFactory {
    private $config;

    public function ___construct(Config $config) {
        $this->config = $config;
    }

    //this method could either be __invoke
    //or the class could implement some factory interface
    public function create() {
        return new MyApi($this->config->get('some-value'), $this->config->get('some-other-value'));
    }

}

class MyApi {
}

class MyService1 {
    public function __construct(MyApi $api) {}
}

class MyService2 {
    public function __construct(MyApi $api) {}
}

With other PHP DI frameworks - I would register MyApiFactory as a factory for MyApi. If I requested for service MyService1 the DI container would see that It needs MyApi and therefore execute MyApiFactory to get MyApi - MyApi would then be stored as a service (if the container supports sharing).

Then I ask for MyService2 which also needs MyApi - as MyApi has already been created before - it just pulls it from the DI container and injects it in MyService2.

Therefore MyApi in MyService1 & MyService2 are the same.

I can't seem to find a way to do this in Magento 2. Basically I want to avoid creating MyApi twice.

The only way I can think to do this is to keep a record of the created MyApi inside MyApiFactory so the second time MyApiFactory::create is called it returns the existing instance. Then I would have to inject MyApiFactory into MyService1 & MyService2 and call create within there constructors or wherever.

This (to me) seems wholly unnecessary and quite unintuitive - is this a problem anyone else has come across? Is there some support for shared services with custom factories in Magento 2 ?

This was more of a question to the Magento 2 developers rather than the community, I did originally create this as an issue but that got shut down, refs: https://github.com/magento/magento2/issues/8182#issuecomment-273763661

2
  • It seems you are using the factory to add config values to the MyApi constructor. Why not inject the Config class into MyApi and get your config values from there? Then you can inject MyApi into other classes' constructor. By default MyApi will be a singleton, unless you define it not to be in your di.xml. In short, skip the factory, the ObjectManager will handle that for you. May 20, 2017 at 9:22
  • @VincentHornikx there are other things in the factory, I just reduced the code for brevity. In any case injecting only what I need instead of the config service improves my unit testing and also allows for my code to be decoupled from Magento. I consider factories throw away code and in this sense it is okay to couple to Magento in the throw away code. Also MyApi is not a singleton in my case, there is an instance per website with different configuration. May 21, 2017 at 12:45

1 Answer 1

0

It seems like you misunderstood the DI logic for Magento 2...

Factory usage

interface MyApiInterface {
}

class MyApi implements MyApiInterface {
}

class MyService1 {
    public function __construct(MyApiInterfaceFactory $apiFactory) {
        /** @var MyApiInterface $api */
        $api = $apiFactory->create();
    }
}

class MyService2 {
    public function __construct(MyApiInterfaceFactory $apiFactory) {
        /** @var MyApiInterface $api */
        $api = $apiFactory->create();
    }
}

Factories will be autogenerated by Magento Framework...

Singletons

In your case you want instanciate MyApi as a singleton in order to use the same instance in all services. In this case the code should look like following:

interface MyApiInterface {
}

class MyApi implements MyApiInterface {
}

class MyService1 {
    public function __construct(MyApiInterface $api) {}
}

class MyService2 {
    public function __construct(MyApiInterface $api) {}
}

Just use your MyApiInterface as type hint in the constructor and Magento will inject the same instance of MyApi each time...

Of course you need to define the preference for your MyApiInterface in di.xml...

Own factory

interface MyApiInterface {
}

interface MyApiFactoryInterface {
    public function create(...): MyApiInterface
}

class MyApiFactory implements MyApiFactoryInterface {
}

class MyApi implements MyApiInterface {
}

class MyService1 {
    public function __construct(MyApiFactoryInterface $apiFactory) {
        $api = $apiFactory->create();
    }
}

class MyService2 {
    public function __construct(MyApiFactoryInterface $apiFactory) {
        $api = $apiFactory->create();
    }
}

In this case Magento will inject each time the same instance of your factory. So the factory become kind of a singleton...

6
  • Please re-read the question and the comments, this is not what I wanted. I understand about auto-generated factories. I was asking about custom factories and whether I can map one to a service as in how other DI frameworks usually work. Also MyApi is not a singleton and if it was why would I inject a factory which produces the same instance all the time? May 24, 2017 at 14:15
  • @AydinHassan I understand that your MyApi ist not a Singleton - what I mean is that Magento keep all the DI-Instances in a internal storage and use them all the time. If you just use the interface MyApiInterface you'll get the same instance each time... If you use the MyApiInterfaceFactory you'll get an autogenerated Factory in order to create new instances of MyApiInterface. Of course you can use own factories: see update in my previous answer... May 24, 2017 at 16:00
  • @AydinHassan: Was that answer helpful? May 26, 2017 at 19:30
  • hey @vadim-justus, sorry no, I already knew this. I wanted to know if there was a way for Magento to execute my custom factory when I ask for a service which consumes something created by that factory - for example, how other PHP DI containers work. May 30, 2017 at 8:19
  • Magen will auto generate the factory only if it does not exists. So you can create your own using the factory interface of Magento and the DI will use your factory instead of auto generate it... I guess... May 31, 2017 at 10:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.