18
votes

I am having some trouble with Symfony2. Namely in how to use the __construct() function. the Official Documentation is shockingly bad!

I want to be able to use the following:

public function __construct()
{
    parent::__construct();
    $user = $this->get('security.context')->getToken()->getUser();
}

How ever I get the following error:

Fatal error: Cannot call constructor in /Sites/src/DEMO/DemoBundle/Controller/Frontend/HomeController.php on line 11

Line 11 is "parent::__construct();"

I removed it and got the following, new error

Fatal error: Call to a member function get() on a non-object in /Sites/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php on line 242

I think I might need to set up the ContainerInterface DIC, but I have no idea how to do this (I tried and failed, miserably)

Any ideas folks?

Update - Tried changing to extend ContainerAware and got this error:

Fatal error: Class DEMO\DemoBundle\Controller\Frontend\HomeController cannot extend from interface Symfony\Component\DependencyInjection\ContainerAwareInterface in /Sites/src/DEMO/DemoBundle/Controller/Frontend/HomeController.php on line 43

Using the following code in the controller:

<?php

namespace DEMO\DemoBundle\Controller\Frontend;

use Symfony\Component\DependencyInjection\ContainerAware;

class HomeController extends ContainerAwareInterface
{
     protected $container;

     public function setContainer(ContainerInterface $container = null)
     {
         $this->container = $container;
     }
7
I think you can't access container in __construct.gremo
Why don't you simply inject the user instance as an argument in the constructor?Nanocom
I want to be able to use __construct() or a similar feature to allow me to set some variables I can re-use in my controller. In CodeIgniter2 I can use __construct no problems, but Symfony2 is quite different and I have to take about 20 steps to get something so simple running (or not as the case is)Mr Pablo
just inject the container in the constructor instead of using ContainerAware. (Define your controller as a service).Florian Klein
Also you want to use the keyword "implements" instead of "extends". Class are extended, interfaces are implemented.Thomas K

7 Answers

23
votes

I'm assuming you are extending the default Symfony controller? If so, a look at the code will reveal the answer:

namespace Symfony\Bundle\FrameworkBundle\Controller;

use Symfony\Component\DependencyInjection\ContainerAware;

class Controller extends ContainerAware
{

Notice that there is no Controller::__construct defined so using parent::__construct will not get you anywhere. If we look at ContainerAware:

namespace Symfony\Component\DependencyInjection;

class ContainerAware implements ContainerAwareInterface
{
    protected $container;
    public function setContainer(ContainerInterface $container = null)
    {
        $this->container = $container;
    }
}

Again, no constructor and the container is not available until setContainer is called. So override setContainer and put your logic there. Or else just make a stand alone controller that does not extend the base controller class and inject your dependencies directly into the constructor.

Update Aug 2017

Still getting a few hits on this. If you really want to execute something before each controller then use a kernel controller listener. If all you need is the user then of course use getUser(). And please don't override setContainer(). In some cases it would work but it would just convolute your code.

4
votes

I also frequently want an instance of the current User in most of my controllers. I find it is easiest to just do something like this:

class SomeController extends Controller
{
    protected $user;

    public function getUser()
    {
        if ($this->user === null) {
            $this->user = $this->get('security.context')->getToken()->getUser();
        }
        return $this->user;
    }
}

However, this is an overly simplistic example case. If you want to do more work before a Controller action is started, I suggest you define your Controller as a Service.

Also take a look at this article: Moving Away from the Base Controller

1
votes

I have to retrieve the 'facade' manager for my rest api's resource. Not using the constructor and using a private function seems the easiest and simplest for me.

/**
 * Class ExchangesController
 * @RouteResource("Exchange")
 */
class ExchangesController extends Controller
{
    /**
     * Get exchange manager
     * @return ExchangeManager
     */
    protected function getExchangeManager()
    {
        return $this->get('exchange_manager');
    }

    /**
     * @ApiDoc(
     *  description="Retrieve all exchanges",
     *  statusCodes={
     *    200="Successful"
     *  }
     * )
     */
    public function cgetAction()
    {
        return $this->getExchangeManager()->findAll();
    }

PS It's ok for me to use private/protected functions in my controller as long as it contains zero conditionals

0
votes

You cannot call getUser() or get() for services in controller constructors. If you remember that, you will save lots of debugging time.

0
votes

I know the question is very old, but I didn't found an answer until now. So I'll share it.

The goal here, is to execute a code everytime a action in our controller is called.

The __construct method doesn't work, because it's called before anything else, so you can't access the service container.

The trick is to overload each method automatically when they are called :

<?php
namespace AppBundle\DefaultController;

class DefaultController extends Controller {
    private function method1Action() {
        return $this->render('method1.html.twig');
    }

    private function method2Action() {
        return $this->render('method2.html.twig');
    }

    public function __call($method, $args) {
         $user = $this->get('security.tokenStorage')->getToken()->getUser();

         // Do what you want with the User object or any service. This will be executed each time before one of those controller's actions are called.

        return call_user_func_array(array($this, $method), $args);
    }
}

Warning ! You have to define each method as a private method ! Or the __call magic method won't be called.

0
votes

There are only two solutions to this problem:

  1. Use a private method as pointed out by @Tjorriemorrie here. But this is a dirty method for purists. (I'm using this! :D );

  2. Define the controller as a service, but this way you will lose all the shortcuts provided by Symfony\Bundle\FrameworkBundle\Controller\Controller. Here is the article that shows how to do this.

As told, personally, in my situation, I prefere a solution like this:

class MyController extends Controller
{
    /** @var AwesomeDependency */
    private $dependency;

    public function anAction()
    {
         $result = $this->getDependency();
    }

    /**
     * Returns your dependency.
     */
    private function getDependency()
    {
        if (null === $this->dependency)
            $this->dependency = $this->get('your.awesome.dependency');

        return $this->dependency;
    }
}

This is typically a class that I call MyManager where I put the code that I use in more than one action in the controller or that unusefully occupies lines (for example the code to create and populate forms, or other code to do heavy tasks or tasks that require a lot of code).

This way I mantain the code in the action clear in its purposes, without adding confusion.

Maybe the use of a property to store the dependency is an overoptimization, but... I like it :)

-3
votes

As i see, Controller extends ContainerAware, and if we take a look of ContainerAware it implements ContainerAwareInterface. So, ContainerAware must have declared the exact methods in it's interface. Add this line

public function __construct();

to the ContainerAwareInterface definition and it will be solved.