With Symfony 2.8, this code $this->container is null.
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
class EntrarYPreregistroFormSubscriber implements EventSubscriberInterface, ContainerAwareInterface
{
use ContainerAwareTrait;
public function preSetData(FormEvent $event)
{
$l = $this->container->get('logger');
$l->notice('GOT LOGGER');
}
....
}
And my EntrarYPreregistroFormSubscriber service is configured as:
pmktconcursos.entrarypreregistro.form.subscriber:
class: PMKT\ConcursosBundle\EventListener\EntrarYPreregistroFormSubscriber
calls:
- [ setContainer,[ "#service_container" ] ]
I am getting exception at $l = $this->container->get('logger');
request.CRITICAL: Uncaught PHP Exception Symfony\Component\Debug\Exception\FatalErrorException: "Error: Call to a member function get() on a non-object" at /Users/vmarquez/Proyectos/GrupoPMKT/Promoticket/current/dev/chedraui1607/chedraui1607/src/PMKT/ConcursosBundle/Form/EventListener/EntrarYPreregistroFormSubscriber.php line 30 {"exception":"[object] (Symfony\\Component\\Debug\\Exception\\FatalErrorException(code: 0): Error: Call to a member function get() on a non-object at /Users/vmarquez/Proyectos/GrupoPMKT/Promoticket/current/dev/chedraui1607/chedraui1607/src/PMKT/ConcursosBundle/Form/EventListener/EntrarYPreregistroFormSubscriber.php:30)"}
Am I missing something?
You are making Form Event Listener, so inside your Form Type you have something like $builder->addEventSubscriber(new EntrarYPreregistroFormSubscriber()); so you can see that Form Event Listeners works with a little difference that a regular Event Listener. As it is you who creates the object, so it is you should call setContainer($serviceContainer). To do that you should have service container inside you Form Type. To do that, you should pass it as option while creating your form in controller
// in controller
$object = ...;
$form = $this->createForm(new YourFormType(), $object, array('service_container' => $this->get('service_container')));
// in YourFormType
$listener = new EntrarYPreregistroFormSubscriber();
$listener->setContainer($options['service_container']);
$builder->addEventSubscriber($listener);
...
// in setDefaultOptions method
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
...
'service_container' => null,
));
}
Related
I am a beginner on Symfony 6 and I am blocked because I have an error message: "Undefined method getDoctrine" with Intelephense
Here is my code:
#[Route('/recettes', name: 'app_recettes')]
public function index(int $id): Response
{
$em = $this->getDoctrine()->getManager();
$recette = $em->getRepository(Recettes::class)->findBy(['id' => $id]);
return $this->render('recettes/index.html.twig', [
'RecettesController' => 'RecettesController',
]);
}
Your controller should extends AbstractController from use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
You should not use getDoctrine()->getManager() in symfony 6. If you look into the method from AbstractController you can see:
trigger_deprecation('symfony/framework-bundle', '5.4', 'Method "%s()" is deprecated, inject an instance of ManagerRegistry in your controller instead.', __METHOD__);
You should just autowire your entity manager in your method or constructor and use it directly.
private EntityManagerInterface $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
#[Route('/recettes', name: 'app_recettes')]
public function index(int $id): Response
{
$recette = $this->entityManager->getRepository(Recettes::class)->findBy(['id' => $id]);
return $this->render('recettes/index.html.twig', [
'RecettesController' => 'RecettesController',
]);
}
You could also autowire your RecettesRepository directly instead of the entity manager if you just want to fetch some data.
I'm guessing that you want to show a specific resource by using its id. You probably want to add something /{id} in your route:
#[Route('/recettes/{id}', name: 'app_recettes')]
Dylan response is really good !
If you want to fecth a specific recette (blog de cuisine?), you can autowire the 'recette' as an argument :
#[Route('/recettes/{id}', name: 'app_recettes')]
public function index(Recette $recette): Response
{
return $this->render('recettes/index.html.twig', [
'recette' => $recette,
]);
}
To do so, don't forget to install and import :
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
I am building a custom exception controller in Symfony 4 to overwrite the ExceptionController class included in the Twig bundle.
I am doing this as per the Symfony documentation for customizing error pages.
# config/packages/twig.yaml
twig:
exception_controller: App\Controller\Error::handleException
The reason I am using a custom exception controller is because I need to pass some additional variable to the template that are given by a custom BaseController class.
The Symfony docs mention the following about using a custom controller:
The ExceptionListener class used by the TwigBundle as a listener of the kernel.exception event creates the request that will be dispatched to your controller. In addition, your controller will be passed two parameters:
exception
A FlattenException instance created from the exception being handled.
logger
A DebugLoggerInterface instance which may be null in some circumstances.
I need the FlattenException service to determine the error code but its not clear from the docs how these parameters are passed to the custom exception controller.
Here is my custom exception controller code:
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Debug\Exception\FlattenException;
class Error extends BaseController {
protected $debug, // this is passed as a parameter from services.yaml
$code; // 404, 500, etc.
public function __construct(BaseController $Base, bool $debug) {
$this->debug = $debug;
$this->data = $Base->data;
// I'm instantiating this class explicitly here, but have tried autowiring and other variations that all give an error.
$exception = new FlattenException();
$this->code = $exception->getStatusCode(); // empty
}
public function handleException(){
$template = 'error' . $this->code . '.html.twig';
return new Response($this->renderView($template, $this->data));
}
}
From the documentation page you are linking, at the very beginning of the chapter Overriding the default template the documentation actually cross link you to the class \Symfony\Bundle\TwigBundle\Controller\ExceptionController, and this shows you how to use it.
So as per Symfony's own ExceptionController, the FlattenException is actually an argument of the action showAction:
<?php
namespace App\Controller;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
class Error extends BaseController {
protected $debug; // this is passed as a parameter from services.yaml
protected $code; // 404, 500, etc.
protected $data;
public function __construct(BaseController $base, bool $debug) {
$this->debug = $debug;
$this->data = $base->data;
}
public function showAction(Request $request, FlattenException $exception, DebugLoggerInterface $logger = null) {
// dd($exception); // uncomment me to see the exception
$template = 'error' . $exception-> getStatusCode() . '.html.twig';
return new Response($this->renderView($template, $this->data));
}
}
I want to get current logged user in form event but for some reason I can't get it to work.
I used services to inject token_storage and create constructor method to fetch token storage instance but I got error right at constructor:
Type error: Argument 1 passed to AdminBundle\Form\EventListener\CompanyFieldSubscriber::__construct() must implement interface Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface, none given
I am not sure what is the problem and how to fix it. Does someone knows where is the problem?
EDIT 1:
I think that I found out where is the problem but I can't find "good" solution. I call this event in form type in this way:
->addEventSubscriber(new CompanyFieldSubscriber());
Problem is that I am not using 'service/dependency injection' to create event and I am sending nothing to constructor. That's why I have this error (not 100% sure to be hones).
Since I have around 20-30 forms and new forms will come in time I need to create service for each form that requires user (or token_storage) instance and as a argument call token_storage or this event subscriber as a argument of service.
I know that it will work if I create each form as a service and pass required data as arguments but is there way to process this "automatically" without creating new service for every form that needs to have some user data interaction in form events?
EDIT 2:
As suggested I tried to change event subscriber constructor but I got same error with different class name.
New code:
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
public function __construct(TokenStorage $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
New error:
Type error: Argument 1 passed to AdminBundle\Form\EventListener\CompanyFieldSubscriber::__construct() must be an instance of Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage, none given
This is code I am using:
Services:
admin.form.event_listener.company:
class: AdminBundle\Form\EventListener\CompanyFieldSubscriber
arguments: ['#security.token_storage']
tags:
- { name: form.event_listener }
Event Listener:
namespace AdminBundle\Form\EventListener;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class CompanyFieldSubscriber implements EventSubscriberInterface
{
private $tokenStorage;
private $user;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
$this->user = $this->tokenStorage->getToken()->getUser();
}
public static function getSubscribedEvents()
{
return [
FormEvents::PRE_SET_DATA => 'preSetData',
FormEvents::PRE_SUBMIT => 'preSubmitData',
];
}
public function preSetData(FormEvent $event)
{
$form = $event->getForm();
if (in_array("ROLE_SUPER_ADMIN", $this->user->getRoles())) {
$form->add('company', EntityType::class, [
'class' => 'AppBundle:Company',
'choice_label' => 'name'
]);
}
}
public function preSubmitData(FormEvent $event)
{
$form = $event->getForm();
$bus = $form->getData();
if (!in_array("ROLE_SUPER_ADMIN", $this->user->getRoles())) {
$bus->setCompany($this->user->getCompany());
}
}
}
You call subscriber in wrong way when you use:
new CompanyFieldSubscriber()
You do not pass TokenStorageInterface to subscriber constructor. Call it as a service, if it is in controller then:
->addEventSubscriber($this->get('admin.form.event_listener.company'));
if it is in form than pass from controller to form
$this->get('admin.form.event_listener.company')
as option and then use it in form
for Symfony >= 4
use Symfony\Component\Security\Core\Security;
class ExampleService
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function someMethod()
{
$user = $this->security->getUser();
}
}
See doc: https://symfony.com/doc/current/security.html#fetching-the-user-from-a-service
I am trying to create a service in Symfony2 to automatically pass Doctrine\ORM\EntityManager to __construct to avoid having to pass it each time I instantiate the class, i.e.
// use this
$TestClass= new TestClass;
// instead of this
$entityManager = $this->getDoctrine()->getEntityManager();
$TestClass= new TestClass($entityManager);
I created a class EntityManagerUser, tried to register it as a service and TestClass extends that.
services.yml is included, as another service works, and I've double-checked by adding (then removing) a syntax error.
I read the docs, this, this and this and I've ended up with the code below, which doesn't pass #doctrine.orm.entity_manager. However, the controller_listener service does receive #templating.
I've cleared cache via the console and manually deleted app/cache but I still see this error:
ContextErrorException: Catchable Fatal Error: Argument 1 passed to Test\TestBundle\ServiceUser\EntityManagerUser::__construct() must be an instance of Test\TestBundle\ServiceUser\Doctrine\ORM\EntityManager, none given, called in D:\Documents\www\Test\live\src\Test\TestBundle\Controller\MyController.php on line 84 and defined in D:\Documents\www\Test\live\src\Test\TestBundle\ServiceUser\EntityManagerUser.php line 14
services.yml
services:
# this one doesn't throw an error and passes #templating to __construct
test.eventlistener.before_controller_listener:
class: Test\TestBundle\Eventlistener\BeforeControllerListener
arguments: [ #templating ]
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
# the following one doesn't pass #doctrine.orm.entity_manager
test.service_user.entity_manager_user:
class: Test\TestBundle\ServiceUser\EntityManagerUser
arguments: [ #doctrine.orm.entity_manager ]
src/Test/TestBundle/ServiceUser/EntityManagerUser.php
namespace Test\TestBundle\ServiceUser;
use Doctrine\ORM\EntityManager;
class EntityManagerUser{
protected $entityManager;
public function __construct(EntityManager $entityManager){
$this->entityManager = $entityManager;
// N.B. it's not possible to do it this way:
// $this->entityManager = new EntityManager;
}
// also tried public function __construct($entityManager){
// and public function __construct(Doctrine\ORM\EntityManager $entityManager){
}
src/Test/TestBundle/Classes/TestClass.php
namespace Test\TestBundle\Classes\TestClass;
use Test\TestBundle\ServiceUser\EntityManagerUser;
class TestClass extends EntityManagerUser{
/* currently no functions */
}
In my controller, line 84
$test= new TestClass;
// I tested that this throws the same error, it does // $test= new EntityManagerUser;
What have I missed?
Services only get their arguments if they are called through the service constructor:
$this->get('test.service_user.entity_manager_user');
Declaring the class as a service doenst make a difference if you create a new class and extend the original.
What you could do is also declare this new class as a service and still have it extend the base class.
test.classes.test_class:
class: Test\TestBundle\Classes\TestClass\TestClass
arguments: [ #doctrine.orm.entity_manager ]
then you dont have to define the constructor in the extended class because it is the parent.
then get the class by doing:
$testClass = $this->get('test.classes.test_class');
//will be instanceof Test\TestBundle\Classes\TestClass\TestClass
I worked out how to do what I wanted, which was not define entityManager each time I was required in an instanciated class.
It made sense to rename EntityManagerUser to ContainerListener, a static class, and inject #service_container into it through services, so it can then also return other classes.
namespace Test\TestBundle\EventListener;
class ContainerListener{
static $container;
// knock out the parent::onKernelRequest function that we don't want
public function onKernelRequest($event){
return;
}
public function __construct($container){
self::$container = $container;
}
static function twig(){
return self::$container->get('twig');
}
static function entityManager(){
return self::$container->get('doctrine')->getEntityManager();
}
static function entityManagerConnection(){
$entityManager = self::$container->get('doctrine')->getEntityManager();
return $entityManager->getConnection();
}
}
services.yml
services:
test.event_listener.container_listener:
class: Test\TestBundle\EventListener\ContainerListener
arguments: [ #service_container ]
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
BaseClass.php gets entityManager
namespace Test\TestBundle\Class;
use Test\TestBundle\EventListener\ContainerListener;
class BaseClass{
public function __construct(){
$this->entityManager = ContainerListener::entityManager();
}
}
TestClass.php extends BaseClass as do others
class TestClass extends BaseClass(){
function someFunction(){
// etc etc
// $this->entityManager exists with no construct and without passing it
$stmt = $this->entityManager->getConnection()->prepare( $some_sql );
// etc etc
}
}
Somewhere in DefaultController.php
# nope # $entityManager = $this->getDoctrine()->getEntityManager();
# nope # $TestClass= new TestClass($entityManager);
$TestClass= new TestClass; # win!
namespace etc...
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Bundle\TwigBundle\Controller\ExceptionController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\HttpKernel\Exception\FlattenException;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
use Symfony\Component\HttpFoundation\Response;
class MyExceptionController extends ExceptionController
{
public function showAction(Request $request, FlattenException $exception, DebugLoggerInterface $logger = null, $format = 'html')
{
}
}
Doing nothing inside the controller returns a "Uncaught exception 'Symfony\Component\Routing\Exception\ResourceNotFoundException' in..." error. Not sure if that's right, or if that's another problem. I'd expect it to just do the usual action.
I just need to do it so it shows a specified route exactly as it would if I went to domain.com/page.
I've tried this:
$httpKernel = $this->container->get('kernel');
$response = $httpKernel->forward('AcmeMyBundle:Default:pageAction');
$this->setResponse(new Response($response));
...but get this error:
Call to a member function get() on a non-object in...
Your code looks similar to something I did yesterday. I wanted to get all NotFoundHttpException Exception and try to forward them to a default controller. I achieved this with an exception listener like this:
<?php
namespace Acme\MyBundle\Listener;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
class NotFoundHttpExceptionListener
{
protected $container;
public function setContainer($container)
{
$this->container = $container;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
if ($exception instanceof NotFoundHttpException) {
$httpKernel = $this->container->get('http_kernel');
$response = $httpKernel->forward(
'AcmeMyBundle:Controller:action',
array(
'uri' => $_SERVER['REQUEST_URI'],
)
);
$response->headers->set('X-Status-Code', '200');
$event->setResponse($response);
$event->stopPropagation();
}
}
}
Note that X-Status-Code is necessary if you want to return another status code than 404 because the handleException method in HttpKernel will use this to set the final status code and removes it from the header section.
My services.yml looks something like this:
notfoundhttp.exception.listener:
class: Acme\MyBundle\Listener\NotFoundHttpExceptionListener
calls:
- [ setContainer, [#service_container] ]
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }