Symfony3 - Using controller as a service issues an error about the container property

Hello,

I’m new to symfony. I’m trying to use a simple contact form as a service, but I’ve got an error message :

Notice: Undefined property: AppBundle\Controller\ContactsFormController::$container" at /mnt/400Go/www/sy1/src/AppBundle/Controller/ContactsFormController.php line 23

I understand the message (I try to access a property that does not exist), but it’s… the container property? Why isn’t it there? Shall I call it during the __construct() ?

Best regards,

MC

Here is the services.yml file :

services:
    contacts_form_controller:
        class: AppBundle\Controller\ContactsFormController
        arguments: ['@form.factory']

Here is the controller :

<?php
// src/AppBundle/Controller/ContactsFormController.php
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use AppBundle\Entity\ContactSetClass;
use AppBundle\Form\Type\ContactsFormType;
/* */
class ContactsFormController
	{
	private $formFactory;
	public function __construct($formFactory)
		{
		$this->formFactory = $formFactory;
		}
	public function createForm($type, $data = null, array $options = array())
		{
		return $this->container->get('form.factory')->create($type, $data, $options); // THIS IS THE LINE CAUSING THE ERROR
		}
	public function contactsFormAction(Request $request)
		{
		$message='';
		$contactSet=new ContactSetClass();
		$ContactsFormType=new ContactsFormType();
		$form=$this->createForm(ContactsFormType::class, $contactSet);
		$form->handleRequest($request);
		if($form->isSubmitted())
			{
			if($form->isValid())
				{
				$contactName=$form->get('contactName')->getData();
				$contactEmail=$form->get('contactEmail')->getData();

				$contactSet->setContactName($contactName);
				$contactSet->setContactEmail($contactEmail);
				
				$entityManager=$this->getDoctrine()->getManager();
				$entityManager->persist($contactSet);
				$entityManager->flush();

				$message='Contact '.$contactName.' has been created for '.$contactEmail.'.';
				/* Reset of the form fields. */
				$contactSet=new ContactSetClass();
				$ContactsFormType=new ContactsFormType();
				$form=$this->createForm(ContactsFormType::class, $contactSet);
				
				// Will not work with render controller
				//return $this->redirect($request->getUri());
				}
			else
				{
				$message='Please correct your data.';
				}
			}
		return $this->render('contactsForm.html.twig', array('contactsForm' => $form->createView(),'message'=>$message,));
		}
	}

Here is how I call the controller on my page ;

	$request=new Request;
    	$contactsForm = $this->get('contacts_form_controller')->contactsFormAction($request);

Ok, I did several mistakes.

  1. I need to use the templating service in services.yml :
    contacts_form_controller:
        class: AppBundle\Controller\ContactsFormController
        arguments: ['@templating','@form.factory']
  1. I have to properly declare the templating service in the constructor of my controller :
    private $formFactory;
    private $templating;
    public function __construct($formFactory,$templating)
	   {
	   $this->formFactory = $formFactory;
	   $this->templating = $templating;
	   }
  1. The createForm function should be like this (but wait…)
	public function createForm($type, $data = null, array $options = array())
	   {
	   return $this->formFactory->create($type, $data, $options);
	   }
  1. The return line was incorrect too, as the controller does not extend Controller :
return $this->templating->render('contactsForm.html.twig', array('contactsForm' => $form->createView(),'message'=>$message,));

But, wait, another error popped up :

Attempted to call an undefined method named “create” of class “Symfony\Bundle\TwigBundle\TwigEngine”." at /mnt/400Go/www/sy1/src/AppBundle/Controller/ContactsFormController.php line 25

The line :

return $this->formFactory->create($type, $data, $options);

… is still incorrect.

The Symfony base controller has a number of helper functions such as createForm, getUser, render etc. For these to work you need to inject the container using the setContainer method. So to answer your first post, something like:

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class ContactsFormController extends Controller

services:
    contacts_form_controller:
        class: AppBundle\Controller\ContactsFormController
        calls: [[setContainer, ['@service_container']]]
        arguments: ['@contacts.repository']

This approach allows you to define controllers as services and use all the default functionality. It is extremely instructive to examine the base controller class to see how it does things.

You can then use constructor injection to inject services specific to a particular controller. In this case, I added the contacts repository as an example. The idea is to avoid any direct access to the container from within your controller action methods.

Going one step further, I think you will find that instead of defining controllers with multiple action methods as services, you will define each individual action as it’s own class. Lookup Action Domain Responder for more details.

That’s an answer :sunny: I need some time to understand it.

In the symfony doc (http://symfony.com/doc/current/controller/service.html), I read that you can’t “extends Controller” if the controller is used as a service…

But I understand, that without “extends Controller”, I’d have had to define the container before using it :

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

Am I right ?

I’ll dive in the doc for the calls and arguments parts. To thin down the controller used as a service through a class was of course the next step. Thanks a lot.

Either you read wrong or whomever wrote this was wrong. Injecting the container is often discouraged as it should be. But in this case, the advantages outweigh the disadvantages. At least in my opinion. Good luck.

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.