ServiceLocator, let's thoughts about it in ZF2

2019-02-17 16:36发布

问题:

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

回答1:

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.