is there any way to preprocess controller data somehow. I'm going to take param from session, validate it and assign it as controller property and use it as $this->myVar inside actions of some controller or all of them if possible. Using controller's constructor gives me nothing, I couldn't access request and session data. Thanks!
UPD:
Thanks, jkucharovic, very good solution.
Also there is a bit dirtier solution, without injecting: setContainer() method, which has been called straight after $controller = new Controller();
use Symfony\Component\DependencyInjection\ContainerAwareInterface,
Symfony\Component\DependencyInjection\ContainerInterface;
class AppServiceController extends Controller {
private $my_property;
/**
* Used as constructor
*/
public function setContainer(ContainerInterface $container = null)
{
parent::setContainer($container);
$this->my_property = 'foo';
// your controller code
}
}
I'm not sure what you wan't to do is very usefull. A Controller instance will be created each time the controller is called. The session and request will be different each time you call the controller.
I think you should create a BaseController class extending Controller class with a shortcut method to access your MyVar value in session.
class BaseController extends Controller
{
public function getMyVar()
{
return $this->get('session')->get('MyVarSessionKey');
}
}
All your other Controller will extend from this BaseController.
To get the request, just use the shortcut method provided by Controller class, Controller::getRequest().
If you want to use services in __construct method, you have to inject that services first. Then you can use them before any other methods. For example:
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session;
public function __construct(Request $request, Session $session)
{
…
}
Related
class EtudiantController extends AbstractController
{
private $etudiant ;
private $form ;
public function __construct()
{
$this->etudiant = new Etudiant();
$this->form = $this->createForm(EtudiantType::class, new Etudiant());
}
}
** i'v got an error when instantiate a form in a constructor using the createForm() function **
Here is the wrong way to solve your problem:
class EtudiantController extends AbstractController
{
private $form;
public function __construct(FormFactoryInterface $formFactory)
{
$this->form = $formFactory->create(TextType::class, new Etudiant());
}
}
I say it is wrong (even though it will work) because creating things like forms really should be done in individual controller actions, not hidden in the constructor. You might be trying to apply Dont Repeat Yourself (DRY) but in cases like this, Don't Confuse Your Future Self takes precedence.
And as far as why injecting the form factory is necessary, I would once again urge you to look at the Symfony source code for AbstractController as well as ControllerTrait. Understanding how dependency injection works is critical to being able to effectively use the framework.
I got two Controllers:
class FirstController extends Controller
{
/**
* #Route("/first")
*/
public function indexAction()
{
$second = new SecondController();
$second->doit();
}
}
class SecondController extends Controller
{
public function doit()
{
$render = $this->renderView('::base.html.twig');
//write $render to disk
}
}
Instead of 'done' being written to my screen, I got error:
What am I doing wrong?
You are not supposed to instantiate controllers yourself. The framework is supposed to do that for you. If you are in controller1, and want controller2 to actually handle the request, which could use some explanation on your part, you need to forward to it, like this : $this->forward('MyBundle:MyController:myaction)
Also, you should maybe have your doit method in a service. Controllers are supposed to stay skinny, and care only about HTTP:
they receive the request
they call a service based on the request, this service should know nothing about HTTP, and be callable from a command as well as from a controller
they return a response based on what the service answers.
This seems to be the fastest and simpliest way to use a controller as service, but I am still missing a step because it doesn't work.
Here is my code:
Controller/service:
// Test\TestBundle\Controller\TestController.php
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
/**
* #Route(service="test_service")
*/
class TestController extends Controller {
// My actions
}
Use :
// Test\TestBundle\Controller\UseController.php
// ...
public function useAction() {
$testService = $this->get('test_service');
}
When I do that, I get the error
You have requested a non-existent service "test_service".
When I check the list of services with app/console container:debug, I don't see my newly created service.
What am I missing?
From Controller as Service in SensioFrameworkExtraBundle:
The #Route annotation on a controller class can also be used to assign the controller class to a service so that the controller resolver will instantiate the controller by fetching it from the DI container instead of calling new PostController() itself:
/**
* #Route(service="my_post_controller_service")
*/
class PostController
{
// ...
}
The service attribute in the annotation is just to tell Symfony it should use the specified service, instead of instantiating the controller with the new statement. It does not register the service on its own.
Given your controller:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
/**
* #Route(service="test_service")
*/
class TestController
{
public function myAction()
{
}
}
You need to actually register the controller as a service with the test_service id:
services:
test_service:
class: Test\TestBundle\Controller\TestController
The advantage of this approach is that you can inject your dependencies into the constructor by specifying them in the service definition, and you don't need to extend the base Controller class.
See How to define controllers as services and Controller as Service in SensioFrameworkExtraBundle.
For future folks, if you decide to use controller-as-a-service, you should better inject your services into the controller via the constructor instead of getting them through a service locator. The former is considered to be an antipattern, while the latter allows easy unit testing and is simply more verbose.
So instead of:
public function useAction() {
$testService = $this->get('test_service');
}
You should:
private $yourService;
public function __construct(YourService $yourService)
{
$this->yourService = $yourService;
}
public function useAction()
{
$this->yourService->use(...);
}
Don't create shortcuts, write solid, maintainable code.
For Symfony 3.4, we don't need to register controllers as services because they are already registered as services with the default services.yml configuration.
You just have to write this :
// current controller
public function myAction() {
$test = $this->get(SomeController::class)->someAction($param);
}
I have a form event subscriber which needs an entity repository.
I would like to inject this repository dependency ideally without having to use the constructors of the subscriber and its parents because this subscriber is needed in many different forms.
So basically I have the following chain :
Controller calls -> CustomManagerService instantiates-> Form instantiates -> EventSubscriber needs-> EntityRepository
the maanager is already a service. It is a pain both to transmit a constructor repository argument from the manager through the form to the subscriber and it is a pain to set each form as a service.
Why can't I instantiate the repository in the subscriber directly ? I have read it is a bad practice.
EDIT : this is what I have so far :
in my controller :
$unitRepository = $this->getDoctrine()->getRepository('UnitRepository');
$myManager = $this->get('my_manager')
$form = $myManager->createForm($unitRepository);
in myManager:
public function createForm(UnitRepository $unitRepository){
return $this->formFactory->createForm(
new xxxType($unitRepository)
}
in my form:
use MyBundle/AddUnitFieldSubscriber;
protected $unitRepository;
public function __construct(UnitRepository $unitRepository)
{
$this->unitRepository = $unitRepository;
}
public function buildform()
{
$builder->addEventSubscriber(new AddUnitFieldSubscriber($this->unitRepository));
}
in my subscriber:
protected $unitRepository;
public function __construct(UnitRepository $unitRepository)
{
$this->unitRepository = $unitRepository;
}
public function preSetData(FormEvent $event)
{
$unitRepository = $this->unitRepository;
$unitRepository->doStuff()
}
I found this extremely lenghty, and sometimes I have a form calling a subform which is the one using the eventSubscriber. if I set the forms as services, I also sometimes get errors cause I am instantiating them without the required first constructur parameter.
What would be the shortest path to do it right and to not repeat all this knowing only the subscriber need access to the repository ?
Thanks a lot !
I'm really not sure I understood everything or even anything, but I'm going to try a response.
I would suggest you to define a service SubscriberProvider which will be responsible of the instantiation of the subscriber and the injection of the repository in the subscriber (via a setter of the subscriber). You could retrieve an instance of subscriber using a method get, retrieve, create, provide or whatever you prefer of the service SubscriberProvider. You could then inject this provider in another service.
EDIT
Here is the definition of the service related to your form type:
services:
your_own_bundle.form.type.unit:
class: Your\OwnBundle\Form\Type\UnitType
arguments:
- "#doctrine"
tags:
- { name: form.type, alias: unit }
And its class:
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Doctrine\Common\Persistence\ManagerRegistry;
use Your\OwnBundle\Event\AddUnitFieldSubscriber;
class UnitType extends AbstractType
{
protected $unitRepository;
public function __construct(ManagerRegistry $doctrine)
{
$this->unitRepository = $doctrine->getRepository('UnitRepository');
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventSubscriber(
new AddUnitFieldSubscriber($this->unitRepository)
);
}
Then, you can use this type like that:
$builder->add('available_unit', 'unit', array());
This way, you don't have to pass the repository to your manager.
I am trying to access a service in a Symfony Controller
$session = $this->get('session');
But I get the next error:
PHP Fatal error: Call to a member function get() on a non-object
I thought that Symfony2 had the controllers defined as services by default.
Note: this question was originally asked by Dbugger, but he removed it for no reason, while it was already answered.
Using the container in controllers
get() is only a shortcut function provided by the Symfony base Controller class to access the container.
Your controller must extend this class to use this function:
namespace Acme\ExampleBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DefaultController extends Controller
{
// your actions
}
If you don't want to depend on this class (for some reasons) you can extend ContainerAware to get the container injected and use it like in the get() shortcut:
namespace Acme\ExampleBundle\Controller;
use Symfony\Component\DependencyInjection\ContainerAware;
class DefaultController extends ContainerAware
{
public function exampleAction()
{
$myService = $this->container->get('my_service');
// do something
}
}
Creating controllers on your own
Controllers are not defined as services per default, you can define them, but it's not needed to get the container. If a request is made, the routing framework determines the controller, which need to be called. Then the controller gets constructed and the container is injected via the setContainer() method.
But if you construct the controller on your own (in a test or anywhere else), you have to inject the container on your own.
$controller = new DefaultController();
$controller->setContainer($container);
// $container comes trough DI or anything else.