Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is the new way of dependency injection in ZF2 without serviceLocator->get() way more inefficient?

Since version 2.7.0 of zend-mvc the ServiceLocatorAwareInterface is depricated, so are $this->serviceLocator->get() calls inside controllers.

Thats why some days ago I did a huge refactoring of all my modules to inject the needed services/objects through constructors using factories for mostly everything.

Sure, I understand why this is the better/cleaner way to do things, because dependendies are much more visible now. But on the other side:

This leads to a heavy overhead and much more never-used class instances, doesn't it?

Let's look to an example:

Because all my controllers having dependencies, I've created factories for all of them.

CustomerControllerFactory.php

namespace Admin\Factory\Controller;
class CustomerControllerFactory implements FactoryInterface {
    public function createService(ServiceLocatorInterface $controllerManager) {
        $serviceLocator = $controllerManager->getServiceLocator();
        $customerService = $serviceLocator->get('Admin\Service\CustomerService');
        $restSyncService = $serviceLocator->get('Admin\Service\SyncRestClientService');

        return new \Admin\Controller\CustomerController($customerService, $restSyncService);
    }
}

CustomerController.php

namespace Admin\Controller;

class CustomerController extends AbstractRestfulController {
    public function __construct($customerService, $restSyncService) {
        $this->customerService = $customerService;
        $this->restSyncService = $restSyncService;
    }
}

module.config.php

'controllers' => [
  'factories' => [
    'Admin\Controller\CustomerController' => 'Admin\Factory\Controller\CustomerControllerFactory',
  ]
],
'service_manager' => [
  'factories' => [
    'Admin\Service\SyncRestClientService' => 'Admin\Factory\SyncRestClientServiceFactory',
  ]
]

SyncRestClientServiceFactory.php

namespace Admin\Factory;
class SyncRestClientServiceFactory implements FactoryInterface {
    public function createService(ServiceLocatorInterface $serviceLocator) {
        $entityManager = $serviceLocator->get('doctrine.entitymanager.orm_default');
        $x1 = $serviceLocator->get(...);
        $x2 = $serviceLocator->get(...);
        $x3 = $serviceLocator->get(...);
        // ...

        return new \Admin\Service\SyncRestClientService($entityManager, $x1, $x2, $x3, ...);
    }
}

The SyncRestService is a complex service class which queries some internal server of our system. It has a lot of dependencies, and is always created if a request comes to the CustomerController. But this sync-service is only used inside the syncAction() of the CustomerController! Before I was using simply $this->serviceLocator->get('Admin\Service\SyncRestClientService') inside the syncAction() so only then it was instantiated.

In general it looks like a lot of instances are created through factories at every request, but the most dependencies are not used. Is this an issue because of my design or it is a normal side-effect behaviour of "doing dependency injection through constructors"?

like image 348
Moongazer Avatar asked Jan 07 '23 05:01

Moongazer


2 Answers

In my opinion it is a normal effect of dependency injection through constructors.

I think you have now two options (not mutually exclusive) to improve how your application works:

  1. Split your controllers, so that the dependencies are instanciated only when needed. This would certainly give rise to more classes, more factories, and so on, but your code would attain more to the single responsability principle

  2. You could use Lazy Services, so that, even if some services are dependencies of the whole controller, they will be actually instanciated only the first time they are called (so never for the actions where they are not called!)

like image 112
marcosh Avatar answered Jan 08 '23 18:01

marcosh


If you only use your SyncRestClientService inside a controller you should consider changing it from a service to a controller plugin (or make a controller plugin where you inject your SyncRestClientService).
Like that you can still get it inside your controller syncAction method very similar to like you did before. This is exactly the purpose of the ZF2 controller plugins.

First you need to create your controller plugin class (extending Zend\Mvc\Controller\Plugin\AbstractPlugin):

<?php
namespace Application\Controller\Plugin;

use Zend\Mvc\Controller\Plugin\AbstractPlugin;

class SyncPlugin extends AbstractPlugin{

    protected $syncRestClientService;

    public function __constuct(SyncRestClientService $syncRestClientService){
        $this->syncRestClientService = $syncRestClientService
    }

    public function sync(){
        // do your syncing using the service that was injected
    }
}

Then a factory to inject your service in the class:

<?php
namespace Application\Controller\Plugin\Factory;

use Application\Controller\Plugin\SyncPlugin;

class SyncPluginFactory implements FactoryInterface
{
    /**
     * @param  ServiceLocatorInterface $serviceController
     * @return SyncPlugin
     */
    public function createService(ServiceLocatorInterface $serviceController)
    {
        $serviceManager = $serviceController->getServiceLocator();
        $syncRestClientService = $serviceManager>get('Admin\Service\SyncRestClientService');
        return new SyncPlugin($syncRestClientService);
    }
}

Then you need to register your plugin in your module.config.php:

<?php
return array(
    //...
    'controller_plugins' => array(
        'factories' => array(
            'SyncPlugin' => 'Application\Controller\Plugin\Factory\SyncPluginFactory',
        )
    ),
    // ...
);

Now you can use it inside your controller action like this:

protected function syncAction(){
    $plugin = $this->plugin('SyncPlugin');
    //now you can call your sync logic using the plugin
    $plugin->sync();
}

Read more on controller plugins here in the documentation

like image 25
Wilt Avatar answered Jan 08 '23 19:01

Wilt