5

According to Marco's Pivetta thoughts with this, this old question and my answer to an other question

I was interrogating myself about the better way to use our Services in Zend Framework 2 application.

Actually we can use the ServiceLocatorAwareInterface combined with ServiceLocatorAwareTrait. With the fact In ZF3 service locator will be removed in controller It may be possible that they will also remove this interface, or advice people not using it, it make sense.

The only way I see how our Services may be constructed is :

Don't use ServiceLocator in your Services, use DependancyInjection.

The problem is :

Some project are just so big that you have either :

  • 15 services's class for one workflow.
  • A service's class with 15 Dependancies.
  • Pick your nightmare...

Some example for what you may need in a service :

  • Get back the formManager (you can't call it in the controller)
  • You may need to get your ViewRenderer to render template before returning an HTML string to the view through AJAX, and JSON response;
  • You may need to get back the translator or every service you want provided by ZF2
  • Get your entity Manager, if you have multiple database, add count here
  • Get others service like MailService, ExportService, ImportService and so on...
  • If you have to load specifics services depends on a client (multi-client website in BtoB... add somes services, because you can't load | call an AbstractFactory)

Maybe for some of those points, they're can be solved by a tricks that I don't know.

My Question is :

Is it a good practise to have 15 or more Dependancies for one service and give up the ServiceLocator, in controllers, but also in services ?

Edit from comments

For illustrate my point, I paste one of my constructor :

public function __construct(
    ToolboxService $toolboxService,
    EntityService $entityService,
    UserService $userService,
    ItemService $itemService,
    CriteriaService $criteriaService,
    Import $import,
    Export $export,
    PhpRenderer $renderer
) {
    $this->toolboxService = $toolboxService;
    $this->entityService = $entityService;
    $this->userService = $userService;
    $this->emOld = $this->toolboxService->getEmOld();
    $this->emNew = $this->toolboxService->getEmNew();
    $this->serviceLocator = $this->toolboxService->getServiceLocator();
    $this->itemService = $itemService;
    $this->criteriaService = $criteriaService;
    $this->import = $import;
    $this->export = $export;
    $this->renderer = $renderer;
    $this->formManager = $this->toolboxService->getFormManager();
}

As you can see, ToolboxService is an object with multiple dependancies itself. This Service is in my Application folder, and almost everywhere. I have 2 entity Managers (connection to 2 databases, but maybe soon, i will need a third one...)

You can see that I use the serviceLocator throught a dependancy, so this service doesn't implements ServiceLocatorAwareInterface. If I'm not using it, i'm literraly screwed for my AbstractFactory call with

// Distribute somes orders depends on Clients
$distributionClass = $this->serviceLocator->get(ucfirst($param->type));
            if ($distributionClass instanceof DistributeInterface) {
                $distributionClass->distribute($orders, $key);
            } else {
                throw new \RuntimeException("invalid_type_provided", 1);
            }
Community
  • 1
  • 1
Greco Jonathan
  • 2,517
  • 2
  • 29
  • 54
  • Can you give a concrete example of a service that has 15 hard dependencies? I Would say if that's the case it should be more a matter of organizing things differently... – Wilt Nov 03 '15 at 10:16
  • A matter of organizing, I already have thoughts about it. When you have huge application, with years of developpment, plus, with multi-clients application where you have to handle all of them with a common workflow then handle specificities.. ServiceLocator is good to achieve that – Greco Jonathan Nov 03 '15 at 10:37
  • I think you can group these `UserService|ItemService|EntityService` in some `ResourceService` and `Import|Export` in an `ExchangeService` or `TransportService`. Then you have 5 dependencies. But maybe this is not the comment you are waiting for :D – Wilt Nov 03 '15 at 12:32
  • Grouping dependancies is not the solution because each service can be independant from each other, and even belongs to different modules. User and item are into 2 different modules. My module order has 3 dependancies : Application / Users / Items. Plus, grouping dependancies give nothing more in architectural terms, it's just hiding the mess. – Greco Jonathan Nov 03 '15 at 12:37
  • I agree with @Wilt here; you could further encapsulate multiple services into one, reducing the number of dependencies. These new services will simply be a container of related services (also injected) so they could still be used individually. – AlexP Nov 03 '15 at 13:55

1 Answers1

3

Let's say you would inject the ServiceLocator instance. There is no guarantee that the ServiceLocator actually holds your hard dependencies, thus breaking the DI pattern. When using constructor dependency injection you are sure that all the services that are needed are really available. If not, the constructing of the service will simply fail.

When using a ServiceLocator you will end up in an instantiated service class where hard dependencies might or might not be available through the ServiceLocator. This means you have to write all kind of additional logic (check dependencies, throw exceptions) in case the dependency cannot be resolved from the ServiceLocator instance the moment you ask for it. Writing all this code will probably be much more work then injecting 15 dependencies and on top of that the logic will be cluttered all over your service.

Additionally you would still need to add all the setter and getter methods to be able to get your services from your ServiceLocator and to make your service testable.

IMHO injecting 15 dependencies is less code and easier to maintain then injecting a ServiceLocator instance.

Wilt
  • 41,477
  • 12
  • 152
  • 203
  • Thanks for your answer, but you missed something : what about calling an abstract factory to handle all Client specificities ? – Greco Jonathan Nov 03 '15 at 13:16
  • @Hooli, sure, you can use a normal and an abstract factory to create your service instance, but then you still have DI and 15 dependencies in your constructor I guess? Or do you mean differently? – Wilt Nov 03 '15 at 13:45
  • Hum, maybe i've not made my point clear. By an AbstractFactory call, i meant in my service, I call another script for a specific client and this call is made by using an AbstractFactory with a name dynamically given. Look at my last piece of code i pasted. – Greco Jonathan Nov 03 '15 at 13:54
  • @Hooli. If you say abstract factory I think about [the ZF2 `abstract_factories`](http://framework.zend.com/manual/2.3/en/modules/zend.service-manager.intro.html). This custom code/solution you propose is nothing a ZF2 programmer that will be new in your project will understand/expect. – Wilt Nov 03 '15 at 14:11
  • @Hooli You didn't accept the answer. Is it not what you were looking for? – Wilt Nov 23 '15 at 10:48
  • Since I have no viable alternative to call my "Customer Services" via an Abstract Factory, I can not help but have the locator service in my services. – Greco Jonathan Nov 23 '15 at 11:16