Symfony 4.如何在没有依赖注入的情况下从控制器访问服务?

时间:2019-02-08 10:59:25

标签: symfony dependency-injection symfony4

我有几个服务:DieselCaseService,CarloanCaseService LvCaseService。

控制器决定要获取的服务。

$type = $quickCheck["type"];
/**
 * @var $caseService \App\Service\Cases\CaseInterface
*/
$type = 'diesel; // for test purposes

$caseService = $this->get('case_service.' . $type);

服务别名的声明如下:

case_service.diesel:
    alias: App\Service\Cases\DieselCaseService
    public: true
class DieselCaseService implements CaseInterface 
{
.
.
.
}

如果我尝试获取DieselCaseService,则会收到错误消息

  

找不到服务“ case_service.diesel”:即使该服务存在于
  应用程序的容器,容器内部
  “ App \ Controller \ Api \ AccountController”是一个较小的服务定位器   只知道“教义”,“ form.factory”,
  “ fos_rest.view_handler”,“ http_kernel”,“ parameter_bag”,
  “ request_stack”,“路由器”,“ security.authorization_checker”,
  “ security.csrf.token_manager”,“ security.token_storage”,“序列化器”,
  “会话”,“模板”和“树枝”服务。尝试使用依赖项
  而是注射。

我该怎么办?我不想将所有服务注入到控制器中

3 个答案:

答案 0 :(得分:3)

对于任何“按键具有相同类型的多个实例”的情况,可以使用自动装配的数组。

1。具有App\名称空间的自动发现服务

services:
    _defaults:
        autowire: true

    App\:
        resource: ../src

2。需要在构造函数中自动装配数组

<?php

namespace App\Controller\Api;

use App\Service\Cases\DieselCaseService

final class AccountController
{
    /**
     * @var CaseInterface[]
     */
    private $cases;

    /**
     * @param CaseInterface[] $cases
     */
    public function __construct(array $cases)
    {
        foreach ($cases as $case) {
            $this->cases[$case->getName()] = $cases;
        }
    }

    public function someAction(): void
    {
        $dieselCase = $this->cases['diesel']; // @todo maybe add validation for exisiting key
        $dieselCase->anyMethod();
    }
}

3。在内核中注册编译器传递

自动接线阵列功能不在Symfony核心中。这有可能要感谢编译器传递。您可以编写自己的代码或使用以下代码:

use Symplify\PackageBuilder\DependencyInjection\CompilerPass\AutowireArrayParameterCompilerPass;

final class AppKernel extends Kernel
{
    protected function build(ContainerBuilder $containerBuilder): void
    {
        $containerBuilder->addCompilerPass(new AutowireArrayParameterCompilerPass);
    }
}

就是这样! :)

我在所有项目中都使用了它,它的工作就像一个魅力。


在我写的post about autowired arrays中阅读更多内容。

答案 1 :(得分:0)

一些下雨的星期天下午代码。这个问题基本上是其他几个问题的重复,但是我认为有足够的活动部分值得提供一些具体的解决方案。

首先定义案例,以确保我们都在同一页面上

interface CaseInterface { }
class DieselCaseService implements CaseInterface {}
class CarloanCaseService implements CaseInterface{}

回答问题的最简单方法是将案例服务直接添加到控制器的容器中:

class CaseController extends AbstractController
{
    public function action1()
    {
        $case = $this->get(DieselCaseService::class);

        return new Response(get_class($case));
    }
    // https://symfony.com/doc/current/service_container/service_subscribers_locators.html#including-services
    public static function getSubscribedServices()
    {
        return array_merge(parent::getSubscribedServices(), [
            // ...
            DieselCaseService::class => DieselCaseService::class,
            CarloanCaseService::class => CarloanCaseService::class,
        ]);
    }
}

我对您的问题所做的唯一更改是使用类名作为服务ID,而不是诸如“ case_service.diesel”之类的东西。您当然可以调整代码以使用您的ID,但类名更为标准。请注意,在services.yaml中不需要任何条目即可使它起作用。

上面的代码有两个问题,可能有或没有问题。首先是只有一个控制器可以访问案例服务。这可能就是您所需要的。第二个潜在问题是您需要明确列出所有案例服务。再次可以,但是自动选择实现案例接口的服务可能会很好。

这并不难,但确实需要遵循所有步骤。

开始定义案例定位符:

use Symfony\Component\DependencyInjection\ServiceLocator;

class CaseLocator extends ServiceLocator
{

}

现在,我们需要一些代码来查找所有案例服务并将其注入定位器。有几种方法,但也许最直接的方法是使用Kernel类。

# src/Kernel.php
// Make the kernel a compiler pass by implementing the pass interface
class Kernel extends BaseKernel implements CompilerPassInterface 
{
    protected function build(ContainerBuilder $container)
    {
        // Tag all case interface classes for later use
        $container->registerForAutoconfiguration(CaseInterface::class)->addTag('case');
    }
    // and this is the actual compiler pass code
    public function process(ContainerBuilder $container)
    {
        // Add all the cases to the case locator
        $caseIds = [];
        foreach ($container->findTaggedServiceIds('case') as $id => $tags) {
            $caseIds[$id] = new Reference($id);
        }
        $caseLocator = $container->getDefinition(CaseLocator::class);
        $caseLocator->setArguments([$caseIds]);
    }

如果执行上述所有步骤,则可以将案例定位器注入碰巧需要它的任何控制器(或其他服务)中:

class CaseController extends AbstractController
{
    public function action2(CaseLocator $caseLocator)
    {
        $case = $caseLocator->get(CarloanCaseService::class);

        return new Response(get_class($case));
    }

再一次,无需对您的services.yaml进行任何更改即可使所有这些正常工作。

答案 2 :(得分:-1)

我已经创建了包装器服务

<?php
namespace App;

use Symfony\Component\DependencyInjection\ContainerInterface;

class ServiceFactory
{

    /** @var ContainerInterface */
    private $container;


    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    public function getService($alias)
    {
        return $this->container->get($alias);
    }

    /**
     * @param $alias
     * @return bool
     */
    public function hasService($alias)
    {
        return $this->container->has($alias);
    }
}

比我将这项服务注入控制器

public function saveRegisterData(Request $request, AccountService $accountService, ServiceFactory $serviceFactory) 
{
.
.
.

    if (!$serviceFactory->hasService('case_service.'.$type)) {
        throw new \LogicException("no valid case_service found");
    }

    /**
    * @var $caseService \App\Service\Cases\CaseInterface
    */
    $caseService = $serviceFactory->getService('case_service.' . $type);

.
.
.
}