Many dependencies in service

不打扰是莪最后的温柔 提交于 2021-01-27 15:19:46

问题


I have trouble with dependencies in my application in service layer.

I have following class:

   <?php

class UserService{

    private $userRepository;
    private $vocationService;
    private $roleService;

    public function __construct(UserRepository $userRepository, VocationService $vocationService, RoleService $roleService)
    {
        $this->userRepository = $userRepository;
        $this->vocationService = $vocationService;
        $this->roleService = $roleService;
    }


} 

There are only three dependencies which I'm injecting. Assume, I want to add next dependency, for example: NextService. My constructor will grow again.

What if I wanted to pass more dependencies within constructor ?

Maybe should I solve this problem by passing IoC container and then get desirable class? Here is an example:

<?php

class UserService{

    private $userRepository;
    private $vocationService;
    private $roleService;

    public function __construct(ContainerInterface $container)
    {
        $this->userRepository = $container->get('userRepo');
        $this->vocationService = $container->get('vocService');
        $this->roleService = $container->get('roleService');
    }

} 

But now my UserService class depends on IoC container which I'm injecting.

How to solve a problem following good practices?

Regards, Adam


回答1:


Injecting the container as a dependency to your service is considered as a bad practice for multiple reasons. I think the main point here is to figure out why and then try to understand the problem that leads you to think about "injecting the container" as a possible solution and how to solve this problem.

In object oriented programming, it's important to clearly define the relations between objects. When you're looking at a given object dependencies, it should be intuitive to understand how the object behaves and what are the other objects it relies on by looking at its public API.

It's also a bad idea to let your object rely on a dependency resolver, In the example you shared your object can't live without the container which is provided by the DI component. If you want to use that object elsewhere, in an application that uses another framework for example, you'll then have to rethink the way your object get its dependencies and refactor it.

The main problem here is to understand why your service needs all these dependencies,

In object-oriented programming, the single responsibility principle states that every context (class, function, variable, etc.) should define a single responsibility, and that responsibility should be entirely encapsulated by the context. All its services should be narrowly aligned with that responsibility. Source: Wikipedia

Based on this definition, I think you should split your UserService into services that handle only one responsability each.

  • A service that fetch users and save them to your dababase for example
  • Another service that manages roles for example
  • ... and so on



回答2:


I agree that __construct can grow fairly easy.

However, you have a Setter DI at your disposal: http://symfony.com/doc/current/components/dependency_injection/types.html#setter-injection

Morover, there is a Property DI, but I wouldn't recommed it as ti leaves your service wide-open to manipulation: http://symfony.com/doc/current/components/dependency_injection/types.html#property-injection




回答3:


You can abstract some of the commonly used services in one helper service and then just inject this helper into your other services. Also you can define some useful functions in this helper service. Something like this:

<?php 

namespace Saman\Library\Service;

use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\Form\FormFactory;
use Symfony\Bundle\FrameworkBundle\Translation\Translator;
use Symfony\Bundle\TwigBundle\Debug\TimedTwigEngine;
use Symfony\Component\Security\Core\SecurityContext;
use Doctrine\ORM\EntityManager;

class HelperService 
{
  protected $translator;
  protected $securityContext;
  protected $router;
  protected $templating;
  protected $em;

    public function __construct(
        Translator $translator, 
        SecurityContext $securityContext,
        Router $router,
        TimedTwigEngine $templating,
        EntityManager $em
        ) 
    {
        $this->translator = $translator;
        $this->securityContext = $securityContext;
        $this->router = $router;
        $this->templating = $templating;
        $this->em = $em;
    }

    Getters ...

    public function setParametrs($parameters)
    {
        if (null !== $parameters) {
            $this->parameters = array_merge($this->parameters, $parameters);
        }

        return $this;
    }

    /**
     * Get a parameter from $parameters array
     */
    public function getParameter($parameterKey, $defaultValue = null)
    {
        if (array_key_exists($parameterKey, $this->parameters)) {
            return $this->parameters[$parameterKey];
        }

        return $defaultValue;
    }
}

Now imagine you have a UserService then you define it like this:

<?php

namespace Saman\UserBundle\Service;

use Saman\Library\Service\HelperService;

class UserService
{
  protected $helper;

  public function __construct(
     Helper $helper,
     $parameters
     ) 
  {
     $this->helper = $helper;
     $this->helper->setParametrs($parameters);
  }

  public function getUser($userId)
  {
     $em = $this->helper->getEntityManager();
     $param1 = $this->helper->getParameter('param1');
     ... 
  }



回答4:


This example was created for Symfony 4 but the principle should work in older versions.

As others have mentioned, it's good to engineer your application to limit the functional scope of each service and reduce the number of injections on each consuming class.

The following approach will help if you truely need many injections, but it's also a nice tidy way to reduce boilerplate if you are injecting a service in many places.

Consider a service App\Services\MyService that you wish to inject into App\Controller\MyController:

Create an 'Injector' trait for your service.

<?php
// App\Services\MyService
namespace App\DependencyInjection;

use App\Services\MyService;

trait InjectsMyService
{
    /** @var MyService */
    protected $myService;

    /**
     * @param MyService $myService
     * @required
     */
    public function setMyService(MyService $myService): void
    {
        $this->myService = $myService;
    }
}

Inside your controller:

<?php
namespace App\Controller;

class MyController
{
    use App\DependencyInjection\InjectsMyService;

    ...

    public myAction()
    {
        $this->myService->myServiceMethod();
        ...
    }
    ...
}

In this way:

  • a single line of code will make your service available in any container managed class which is super handy if you're using a service in many places
  • it's easy to search for your injector class to find all usages of a service
  • there are no magic methods involved
  • your IDE will be able to auto-complete your protected service instance property and know it's type
  • controller method signatures become simpler, containing only arguments

If you have many injections:

<?php
namespace App\Controller;

use App\DependencyInjection as DI;

class SomeOtherController
{
    use DI\InjectsMyService;
    use DI\InjectsMyOtherService;
    ...
    use DI\InjectsMyOtherOtherService;

    ...
}

You can also create an injector for framework provided services, e.g. the doctrine entity manager:

<?php
namespace App\DependencyInjection;

use Doctrine\ORM\EntityManagerInterface;

trait InjectsEntityManager
{
    /** @var EntityManagerInterface */
    protected $em;

    /**
     * @param EntityManagerInterface $em
     * @required
     */
    public function setEm(EntityManagerInterface $em): void
    {
        $this->em = $em;
    }
}
class MyClass
{
   ...
   use App\DependencyInjection\InjectsEntityManager;

A final note: I personally wouldn't try to make these injectors any smarter than what I've outlined. Trying to make a single polymorphic injector will probably obfuscate your code and limit your IDE's ability to auto-complete and know what type your services are.



来源:https://stackoverflow.com/questions/26679524/many-dependencies-in-service

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!