37

in my services constructor

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

I pass entityManager and securityContext as argument. also my services.xml is here

    <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" />

but now,I want to use container in services such as

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

how can I pass the container to services?

ahmed hamdy
  • 5,096
  • 1
  • 47
  • 58
whitebear
  • 11,200
  • 24
  • 114
  • 237
  • Why don't you add `fos_user_profile_edit` as an argument? if it's not required you can use setter injection. I think you should have a very good reason for injecting the service container. I make your code not portable – Rocco Jun 15 '13 at 18:13

4 Answers4

61

It's easy, if service extends ContainerAware

use \Symfony\Component\DependencyInjection\ContainerAware;

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

service.yml

  your.service:
      class: App\...\YouService
      calls:
          - [ setContainer,[ @service_container ] ]
bigmax
  • 824
  • 1
  • 5
  • 14
  • It's better to quote `@service_container`. Because otherwise there will be Exception thrown (`The reserved indicator "@" cannot start a plain scalar; you need to quote the scalar at line 22 (near "- [ setContainer,[ @service_container ] ]").` ). Related issue: http://stackoverflow.com/questions/34454834/symfony2-phpunit-yaml-parse-error – Stas Makarov Mar 15 '16 at 09:01
  • 2
    I had to add: `protected $container; public function __construct($container) { $this->container = $container; }` and instead of `calls: - [ setContainer,[ @service_container ] ]` I used `arguments: [ '@service_container' ]` in services.yml to get this working in Symfony 2.8. Apart from this all work fine. Thanks. – Strabek Jun 30 '16 at 12:43
51

Add:

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

And in your listener class:

use Symfony\Component\DependencyInjection\ContainerInterface;

//...

public function __construct(ContainerInterface $container, ...) {
Alain Tiemblo
  • 36,099
  • 17
  • 121
  • 153
Sybio
  • 8,565
  • 3
  • 44
  • 53
  • 2
    technically, you should use __construct(ContainerInterface $container, ..) since you're probably not using any functions not defined in the container interface. – Derick F Dec 15 '14 at 15:48
14

It's 2016, you can use trait which will help you extend same class with multiple libraries.

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

Your object, which will be service.

namespace AppBundle\Utils;

use iBasit\ToolsBundle\Utils\Lib\Container;

class myObject
{
    use Container;
}

Your service settings

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

Call your service in controller

$myObject = $this->get('myObject');
Basit
  • 16,316
  • 31
  • 93
  • 154
  • 5
    with smf3 just "use \Symfony\Component\DependencyInjection\ContainerAwareTrait;" – Mohamed Ramrami Feb 09 '16 at 13:53
  • It does not come with getUser(), getDoctrine, getParameter()... which comes with controller. so this will make life easier, but in the end both have same result. – Basit Feb 09 '16 at 22:33
5

If all your services are ContainerAware, I suggest to create a BaseService class that will contain all common code with your other services.

1) Create the Base\BaseService.php class:

<?php

namespace Fuz\GenyBundle\Base;

use Symfony\Component\DependencyInjection\ContainerAware;

abstract class BaseService extends ContainerAware
{

}

2) Register this service as abstract in your services.yml

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

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

3) Now, in your other services, extends your BaseService class instead of ContainerAware:

<?php

namespace Fuz\GenyBundle\Services;

use Fuz\GenyBundle\Base\BaseService;

class Loader extends BaseService
{
   // ...
}

4) Finally, you can use the parent option in your services declaration.

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

I prefer this way for several reasons:

  • there is consistency between the code and the config
  • this avoids duplicating too much config for each service
  • you have a base class for each services, very helpful for common code
Alain Tiemblo
  • 36,099
  • 17
  • 121
  • 153