Cracs
Cracs

Reputation: 525

Symfony2 & PHPUnit - Access getContainer()

I have a problem with my PHPUnit test on Symfony2. To connect to my application, I use a web service, so I created a UserProvider. In my function loadUserByUsername I use Symfony2 parameters saved in app/config/parameters.yml. As I'm not in a controller I need to use the global variable $kernel and get my args like this:

global $kernel;

$url = $kernel->getContainer()-getParameter('myparam');

When I use my application, it works, but when I write my test like this:

$crawler = $client->request('GET', '/login');
$form = $crawler->selectButton('submit')->form();
$form['_username'] = $username;
$form['_password'] = $pass;

and execute PHPUnit I get this error :

Fatal error : Call to a member function getContainer()

How can I access Symfony2 parameters or use getContainer when I execute PHPUnit?

Upvotes: 2

Views: 4272

Answers (2)

Francesco Casula
Francesco Casula

Reputation: 27130

First of all you should not access the kernel by using the global keyword. You can always inject it as a dependency (i.e. @kernel). Still if your goal is just to access the container then you should inject just the service container which is @service_container. The right and best way is to inject just the parameters like redbirdo suggested (i.e. %parameter_one%, %parameter_two%).

Regarding your test you should create and boot a propel kernel in order to be able to use it.

I suggest you create a KernelTestCase class to do that so that your test can extend it and access the container and everything like you do in your controllers.

<?php

namespace AppBundle\Tests;

use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase as TestCase;

/**
 * Class KernelTestCase
 * @package AppBundle\Tests
 * @author Francesco Casula <[email protected]>
 */
abstract class KernelTestCase extends TestCase
{
    /**
     * @var \Symfony\Component\DependencyInjection\ContainerInterface
     */
    private $container;

    /**
     * {@inheritdoc}
     */
    public function tearDown()
    {
        $this->container = null;
        parent::tearDown();
    }

    /**
     * @param array $options
     * @return \Symfony\Component\DependencyInjection\ContainerInterface
     */
    protected function getContainer(array $options = [])
    {
        if (!$this->container) {
            static::bootKernel($options);
            $this->container = static::$kernel->getContainer();
        }

        return $this->container;
    }

    /**
     * @param string $parameter
     * @return mixed
     */
    protected function getParameter($parameter)
    {
        return $this->getContainer()->getParameter($parameter);
    }
}

And then in your test class...

<?php

namespace AppBundle\Tests\IntegrationTests;

use AppBundle\Tests\KernelTestCase as TestCase;

/**
 * Class ExampleTest
 * @package AppBundle\Tests\IntegrationTests
 * @author Francesco Casula <[email protected]>
 */
class ExampleTest extends TestCase
{
    public function testMyMethod()
    {
        $parameter = $this->getParameter('whatever');

        // ...

        $service = $this->getContainer()->get('service');
    }
}

Upvotes: 2

redbirdo
redbirdo

Reputation: 4957

In the Symfony documentation How to Create a custom UserProvider, under 'Create a Service for the User Provider' it states:

The real implementation of the user provider will probably have some dependencies or configuration options or other services. Add these as arguments in the service definition.

So, rather than using a global $kernel variable you should be passing the relevant parameters into your user provider service by defining them as arguments in the service definition. For example:

services:
    webservice_user_provider:
        class: Acme\WebserviceUserBundle\Security\User\WebserviceUserProvider
        arguments: [%parameter_one%, %parameter_two%, ...]

As with any service, your user provider service class must then have a constructor which takes arguments corresponding to those in the service definition and stores them in private variables for use in the service's methods:

class WebserviceUserProvider implements UserProviderInterface
{
    private parameterOne;
    ...

    public function __construct($parameterOne, ...)
    {
        $this->parameterOne = $parameterOne;
        ...
    }
    ...
}

I've been writing Symfony apps for four years and I've never need to use the global $kernel variable. I daresay there may be valid circumstances but in general I'd say it's to be avoided.

Upvotes: 2

Related Questions