4

我在服务层的应用程序中遇到了依赖问题。

我有以下课程:

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


} 

我只注入了三个依赖项。假设,我想添加下一个依赖项,例如:NextService。我的构造函数将再次增长。

如果我想在构造函数中传递更多依赖项怎么办?

也许我应该通过传递 IoC 容器然后获得理想的类来解决这个问题?这是一个例子:

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

} 

但是现在我的UserService类依赖于我正在注入的 IoC 容器。

如何按照良好做法解决问题?

问候,亚当

4

4 回答 4

11

出于多种原因,将容器作为服务的依赖项注入被认为是一种不好的做法。我认为这里的重点是找出原因,然后尝试理解导致您将“注入容器”作为可能的解决方案以及如何解决此问题的问题。

面向对象编程中,明确定义对象之间的关系很重要。当您查看给定的对象依赖项时,应该通过查看其公共 API 来直观地了解该对象的行为方式以及它依赖的其他对象是什么。

让您的对象依赖依赖解析器也是一个坏主意,在示例中,如果没有DI 组件container提供的对象,您共享的对象就无法生存。如果您想在其他地方使用该对象,例如在使用另一个框架的应用程序中,您将不得不重新考虑您的对象获取其依赖关系的方式并对其进行重构。

这里的主要问题是了解为什么您的服务需要所有这些依赖项,

面向对象编程中,单一职责原则 指出每个上下文(类、函数、变量等)都应该定义一个单一职责,并且该职责应该完全被上下文封装。它的所有服务都应与该责任密切相关。资料来源:维基百科

根据这个定义,我认为您应该将您的UserService服务拆分为每个只处理一个责任的服务。

  • 例如,获取用户并将其保存到您的数据库的服务
  • 另一个管理角色的服务,例如
  • ... 等等
于 2014-10-31T17:25:00.680 回答
1

我同意这__construct可以变得相当容易。

但是,您可以使用:http Setter DI: //symfony.com/doc/current/components/dependency_injection/types.html#setter-injection

Morover,有一个Property DI,但我不会推荐它,因为 ti 让您的服务对操纵敞开大门: http: //symfony.com/doc/current/components/dependency_injection/types.html#property-injection

于 2014-10-31T17:04:26.753 回答
0

您可以在一个帮助服务中抽象一些常用服务,然后将这个帮助注入到您的其他服务中。您还可以在此帮助服务中定义一些有用的功能。像这样的东西:

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

现在想象你有一个 UserService 然后你像这样定义它:

<?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');
     ... 
  }
于 2015-01-23T13:31:11.890 回答
-1

这个例子是为 Symfony 4 创建的,但是这个原理应该适用于旧版本。

正如其他人所提到的,最好设计您的应用程序以限制每个服务的功能范围并减少每个消费类的注入次数。

如果您确实需要多次注入,以下方法将有所帮助,但如果您在许多地方注入服务,这也是减少样板代码的好方法。

考虑一个App\Services\MyService您希望注入的服务App\Controller\MyController

为您的服务创建一个“注入器”特征。

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

在您的控制器内部:

<?php
namespace App\Controller;

class MyController
{
    use App\DependencyInjection\InjectsMyService;

    ...

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

这样:

  • 一行代码将使您的服务在任何容器托管类中可用,如果您在许多地方使用服务,这将非常方便
  • 很容易搜索您的注入器类以查找服务的所有用法
  • 不涉及魔术方法
  • 您的 IDE 将能够自动完成受保护的服务实例属性并知道它的类型
  • 控制器方法签名变得更简单,只包含参数

如果您有多次注射:

<?php
namespace App\Controller;

use App\DependencyInjection as DI;

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

    ...
}

您还可以为框架提供的服务创建一个注入器,例如理论实体管理器:

<?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;

最后一点:我个人不会试图让这些注射器比我所概述的更智能。尝试制作单个多态注入器可能会混淆您的代码并限制您的 IDE 自动完成和知道您的服务是什么类型的能力。

于 2020-01-07T02:16:28.510 回答