我在服务层的应用程序中遇到依赖关系问题。
我有以下课程:
<?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容器。
如何按照良好做法解决问题?
此致,Adam
由于多种原因,将容器作为服务的依赖项注入被认为是一种糟糕的做法。我认为这里的要点是找出原因,然后尝试理解导致您考虑将"注入容器"作为可能的解决方案的问题,以及如何解决这个问题。
在面向对象编程中,明确定义对象之间的关系很重要。当您查看给定的对象依赖关系时,应该通过查看其公共API直观地了解对象的行为以及它所依赖的其他对象。
让您的对象依赖依赖依赖解析程序也是一个坏主意。在您共享的示例中,您的对象离不开DI组件提供的container
。如果你想在其他地方使用该对象,例如在使用另一个框架的应用程序中,你就必须重新思考对象获取依赖关系的方式并对其进行重构
这里的主要问题是理解为什么您的服务需要所有这些依赖项,
在面向对象程序设计中,单一责任原则声明每个上下文(类、函数、变量等)定义一个单一的责任,并且该责任应该完全由上下文封装。其所有服务应与这一责任勉强一致。来源:维基百科
基于这个定义,我认为您应该将UserService
划分为服务,每个服务只处理一个责任。
- 例如,一种获取用户并将其保存到数据库的服务
- 例如,另一个管理角色的服务
- 。。。等等
我同意__construct
可以很容易地增长。
但是,您可以使用Setter DI
:http://symfony.com/doc/current/components/dependency_injection/types.html#setter-注入
Morover,有一个Property DI
,但我不建议使用它,因为它让您的服务非常容易被操纵:http://symfony.com/doc/current/components/dependency_injection/types.html#property-注入
您可以在一个助手服务中抽象一些常用的服务,然后将该助手注入到其他服务中。此外,您还可以在此助手服务中定义一些有用的函数。类似这样的东西:
<?php
namespace SamanLibraryService;
use SymfonyBundleFrameworkBundleRoutingRouter;
use SymfonyComponentFormFormFactory;
use SymfonyBundleFrameworkBundleTranslationTranslator;
use SymfonyBundleTwigBundleDebugTimedTwigEngine;
use SymfonyComponentSecurityCoreSecurityContext;
use DoctrineORMEntityManager;
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 SamanUserBundleService;
use SamanLibraryServiceHelperService;
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');
...
}
这个例子是为Symfony 4创建的,但原理应该在旧版本中有效
正如其他人所提到的,最好对应用程序进行设计,以限制每个服务的功能范围,并减少每个消费类上的注入次数。
如果你确实需要多次注射,以下方法会有所帮助,但如果你在许多地方注射服务,这也是一种减少样板的好方法。
考虑一个要注入AppControllerMyController
:的服务AppServicesMyService
为您的服务创建一个"Injector"特性。
<?php
// AppServicesMyService
namespace AppDependencyInjection;
use AppServicesMyService;
trait InjectsMyService
{
/** @var MyService */
protected $myService;
/**
* @param MyService $myService
* @required
*/
public function setMyService(MyService $myService): void
{
$this->myService = $myService;
}
}
控制器内部:
<?php
namespace AppController;
class MyController
{
use AppDependencyInjectionInjectsMyService;
...
public myAction()
{
$this->myService->myServiceMethod();
...
}
...
}
这样:
- 一行代码将使您的服务在任何容器托管类中都可用,如果您在许多地方使用服务,这将非常方便
- 搜索注入器类可以很容易地找到服务的所有用法
- 没有魔法方法
- IDE将能够自动完成受保护的服务实例属性,并知道其类型
- 控制器方法签名变得更简单,只包含参数
如果你有多次注射:
<?php
namespace AppController;
use AppDependencyInjection as DI;
class SomeOtherController
{
use DIInjectsMyService;
use DIInjectsMyOtherService;
...
use DIInjectsMyOtherOtherService;
...
}
您还可以为框架提供的服务创建注入器,例如条令实体管理器:
<?php
namespace AppDependencyInjection;
use DoctrineORMEntityManagerInterface;
trait InjectsEntityManager
{
/** @var EntityManagerInterface */
protected $em;
/**
* @param EntityManagerInterface $em
* @required
*/
public function setEm(EntityManagerInterface $em): void
{
$this->em = $em;
}
}
class MyClass
{
...
use AppDependencyInjectionInjectsEntityManager;
最后一点:我个人不会试图让这些注射器比我概述的更智能。试图制作一个多态注入器可能会混淆代码,并限制IDE自动完成和了解服务类型的能力。