如何将容器作为参数传递给服务

37

在我的服务构造函数中

public function __construct(
        EntityManager $entityManager,
        SecurityContextInterface $securityContext)
{
     $this->securityContext = $securityContext;
    $this->entityManager = $entityManager;

我将entityManager和securityContext作为参数传递。 此外,我的services.xml文件在这里。

    <service id="acme.memberbundle.calendar_listener" class="Acme\MemberBundle\EventListener\CalendarEventListener">
        <argument type="service" id="doctrine.orm.entity_manager" />
        <argument type="service" id="security.context" />

但现在,我想在服务中使用容器,例如

$this->container->get('router')->generate('fos_user_profile_edit') 

我该如何将容器传递给服务?


为什么不将 fos_user_profile_edit 添加为参数呢?如果不需要,您可以使用setter注入。我认为您应该有一个非常好的理由来注入服务容器。这会使您的代码不可移植。 - Rocco
4个回答

61

如果服务扩展了ContainerAware,那么很容易实现。

use \Symfony\Component\DependencyInjection\ContainerAware;

class YouService extends ContainerAware
{
    public function someMethod() 
    {
        $this->container->get('router')->generate('fos_user_profile_edit') 
        ...
    }
}

服务配置文件.yml

  your.service:
      class: App\...\YouService
      calls:
          - [ setContainer,[ @service_container ] ]

最好引用@service_container。否则会抛出异常(“保留指示符“@”不能开始一个普通标量;您需要在第22行(接近“- [setContainer,[@service_container]]”)引用标量。”)。相关问题:http://stackoverflow.com/questions/34454834/symfony2-phpunit-yaml-parse-error - Stas Makarov
2
我不得不添加以下代码:`protected $container; public function __construct($container) { $this->container = $container; }并且在services.yml中,我使用了arguments: [ '@service_container' ]而不是calls: - [ setContainer,[ @service_container ] ]` 来使其在Symfony 2.8中正常工作。除此之外,一切都很好。谢谢。 - Strabek

51

添加:

<argument type="service" id="service_container" />

并且在你的监听器类中:

use Symfony\Component\DependencyInjection\ContainerInterface;

//...

public function __construct(ContainerInterface $container, ...) {

2
从技术上讲,你应该使用 __construct(ContainerInterface $container, ..) ,因为你可能没有使用容器接口中未定义的任何函数。 - Derick F

14

现在是2016年,你可以使用trait,它可以帮助你通过多个库扩展同一个类。

<?php

namespace iBasit\ToolsBundle\Utils\Lib;

use Doctrine\Bundle\DoctrineBundle\Registry;
use Symfony\Component\DependencyInjection\ContainerInterface;

trait Container
{
    private $container;

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

    /**
     * Shortcut to return the Doctrine Registry service.
     *
     * @return Registry
     *
     * @throws \LogicException If DoctrineBundle is not available
     */
    protected function getDoctrine()
    {
        if (!$this->container->has('doctrine')) {
            throw new \LogicException('The DoctrineBundle is not registered in your application.');
        }

        return $this->container->get('doctrine');
    }

    /**
     * Get a user from the Security Token Storage.
     *
     * @return mixed
     *
     * @throws \LogicException If SecurityBundle is not available
     *
     * @see TokenInterface::getUser()
     */
    protected function getUser()
    {
        if (!$this->container->has('security.token_storage')) {
            throw new \LogicException('The SecurityBundle is not registered in your application.');
        }

        if (null === $token = $this->container->get('security.token_storage')->getToken()) {
            return;
        }

        if (!is_object($user = $token->getUser())) {
            // e.g. anonymous authentication
            return;
        }

        return $user;
    }

    /**
     * Returns true if the service id is defined.
     *
     * @param string $id The service id
     *
     * @return bool true if the service id is defined, false otherwise
     */
    protected function has ($id)
    {
        return $this->container->has($id);
    }

    /**
     * Gets a container service by its id.
     *
     * @param string $id The service id
     *
     * @return object The service
     */
    protected function get ($id)
    {
        if ('request' === $id)
        {
            @trigger_error('The "request" service is deprecated and will be removed in 3.0. Add a typehint for Symfony\\Component\\HttpFoundation\\Request to your controller parameters to retrieve the request instead.', E_USER_DEPRECATED);
        }

        return $this->container->get($id);
    }

    /**
     * Gets a container configuration parameter by its name.
     *
     * @param string $name The parameter name
     *
     * @return mixed
     */
    protected function getParameter ($name)
    {
        return $this->container->getParameter($name);
    }
}

你的对象,将成为服务。

namespace AppBundle\Utils;

use iBasit\ToolsBundle\Utils\Lib\Container;

class myObject
{
    use Container;
}

您的服务设置

 myObject: 
        class: AppBundle\Utils\myObject
        calls:
            - [setContainer, ["@service_container"]]

在控制器中调用您的服务

$myObject = $this->get('myObject');

5
使用SMF3时,只需使用"\Symfony\Component\DependencyInjection\ContainerAwareTrait;"即可。 - Mohamed Ramrami
它不带有getUser()、getDoctrine和getParameter()等控制器中的功能,这会使生活更加轻松,但最终两者具有相同的结果。 - Basit

5
如果您的所有服务都是“ContainerAware”,我建议创建一个BaseService类,其中包含与其他服务相同的所有通用代码。
1)创建“Base\BaseService.php”类:
<?php

namespace Fuz\GenyBundle\Base;

use Symfony\Component\DependencyInjection\ContainerAware;

abstract class BaseService extends ContainerAware
{

}

2) 在您的services.yml中将此服务注册为抽象服务

parameters:
    // ...
    geny.base.class: Fuz\GenyBundle\Base\BaseService

services:
    // ...
    geny.base:
        class: %geny.base.class%
        abstract: true
        calls:
          - [setContainer, [@service_container]]

3) 现在,在您的其他服务中,扩展您的 BaseService 类,而不是 ContainerAware

<?php

namespace Fuz\GenyBundle\Services;

use Fuz\GenyBundle\Base\BaseService;

class Loader extends BaseService
{
   // ...
}

4) 最后,您可以在服务声明中使用 parent 选项。

geny.loader:
    class: %geny.loader.class%
    parent: geny.base

我倾向于这种方式,有以下几个原因:

  • 代码和配置之间有一致性
  • 避免为每个服务重复太多配置
  • 每个服务都有一个基类,非常有助于编写通用代码

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接