我们正试图找到在Symfony项目中实现依赖项注入的最佳方法,该项目有一个非常具体的问题。
在用户级别,我们的应用程序依赖于"帐户"原则实体,该实体在针对域属性的HTTP_HOST
全局的帮助下加载(多域应用程序(。继续域example.domain.tld将加载匹配的实体和设置。
在devops级别,我们还需要同时对多个帐户上的CLI脚本进行批处理。
我们面临的问题是,如何编写能够同时满足这两种需求的服务?
让我们用一个简化的例子来说明这一点。对于用户级别,我们有这个,一切都很好:
控制器/FileController.php
public function new(Request $request, FileManager $fileManager): Response
{
...
$fileManager->addFile($file);
...
}
Service/FileManager.php
public function __construct(AccountFactory $account)
{
$this->account = $account;
}
Service/AccountFactory.php
public function __construct(RequestStack $requestStack, AccountRepository $accountRepository)
{
$this->requestStack = $requestStack;
$this->accountRepository = $accountRepository;
}
public function createAccount()
{
$httpHost = $this->requestStack->getCurrentRequest()->server->get('HTTP_HOST');
$account = $this->accountRepository->findOneBy(['domain' => $httpHost]);
if (!$account) {
throw $this->createNotFoundException(sprintf('No matching account for given host %s', $httpHost));
}
return $account;
}
现在,如果我们想编写以下控制台命令,它将失败,因为FileManager
只接受AccountFactory
,而不接受Account实体。
$accounts = $accountRepository->findAll();
foreach ($accounts as $account) {
$fileManager = new FileManager($account);
$fileManager->addFile($file);
}
我们可以在AccountFactory
中进行调整,但这感觉不对。。。事实上,情况更糟,因为Account在服务中的依赖性更深。
有人知道如何正确制作吗?
作为一种好的做法,您应该为FileManager创建一个接口,并将此FileManagerInterface设置为依赖项注入(而不是FileManager(。然后,可以有不同的类,它们遵循相同的接口规则,但只是有不同的构造函数。
使用这种方法,您可以实现以下内容:
Service/FileManager.php
interface FileManagerInterface
{
// declare the methods that must be implemented
public function FileManagerFunctionA();
public function FileManagerFunctionB(ParamType $paramX):ReturnType;
}
FileManager界面.php
class FileManagerBase implements FileManagerInterface
{
// implement the methods defined on the interface
public function FileManagerFunctionA()
{
//... code
}
public function FileManagerFunctionB(ParamType $paramX):ReturnType
{
//... code
}
}
FileManagerForFactory.php
class FileManagerForFactory implements FileManagerInterface
{
// implement the specific constructor for this implementation
public function __construct(AccountFactory $account)
{
// your code here using the account factory object
}
// additional code that is needed for this implementation and that is not on the base class
}
文件管理器Another.php
class FileManagerForFactory implements FileManagerInterface
{
// implement the specific constructor for this implementation
public function __construct(AccountInterface $account)
{
// your code here using the account object
}
// additional code that is needed for this implementation and that is not on the base class
}
最后但同样重要的是:
控制器/FileController.php
public function new(Request $request, FileManagerInterface $fileManager): Response
{
// ... code using the file manager interface
}
另一种看起来也是正确的方法是,假设FileManager
依赖于AccountInstance
才能工作,则可以对FileManager
依赖项进行更改,使AccountInstance
而不是Factory
成为依赖项。只是因为事实上,FileManager
不需要工厂,它需要工厂生成的结果,所以,自动地,承载整个工厂不是FileManager的责任。
使用这种方法,您只需更改您的声明,如:
Service/FileManager.php
public function __construct(AccountInterface $account)
{
$this->account = $account;
}
Service/AccountFactory.php
public function createAccount():AccountInterface
{
// ... your code
}