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 }
Related
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'd like to create a custom Exception on Symfony3 that returns a JSON response to be able to handle it in JavaScript afterwards.
Does someone know if it's possible and how to do it ?
Create a new exception handler class, like this:
namespace AppBundle\Subscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
class ExceptionSubscriber implements EventSubscriberInterface
{
/* ... */
public static function getSubscribedEvents()
{
return [ KernelEvents::EXCEPTION => 'onKernelException' ];
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
$customResponse = new JsonResponse(['error' => 'My custom error message']);
$event->setResponse($customResponse);
}
}
Don't forget to register the new service in app/config/services.yml:
app.exception_subscriber:
class: AppBundle\Subscriber\ExceptionSubscriber
tags:
- { name: kernel.event_subscriber }
I am trying to set up a kernal.controller listener to redirect to another route when a function returns true. I have the route available to me but no way to set the controller from this route using $event->setController().
I'm getting the following error:
FatalThrowableError in FilterControllerEvent.php line 59:
Type error: Argument 1 passed to Symfony\Component\HttpKernel\Event\FilterControllerEvent::setController() must be callable, string given
Does anyone have suggestions on how I can complete this?
class BlockListener
{
public function onKernelController(FilterControllerEvent $event)
{
$block = $this->blockService->checkForBlock($user->getId());
if ($block instanceof Block) {
// $block-getRoute() is a standard Symfony route string. It doesn't work!
$event->setController($block->getRoute());
}
}
}
We were able to get it working by using a Lambda function. Thanks for the help!
if ($block instanceof Block) {
$redirectUrl = $this->router->generate($block->getRoute());
$event->setController(function() use ($redirectUrl) {
return new RedirectResponse($redirectUrl);
});
};
You can modify options below as you wish.
OPTION 1
Full details
LISTENER
namespace Application\BackendBundle\EventListener;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class KernelExceptionListener
{
private $router;
private $redirectRouter = 'application_frontend_default_index';
public function __construct(Router $router)
{
$this->router = $router;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
if ($exception instanceof NotFoundHttpException) {
if ($event->getRequest()->get('_route') == $this->redirectRouter) {
return;
}
$url = $this->router->generate($this->redirectRouter);
$response = new RedirectResponse($url);
$event->setResponse($response);
}
}
}
SERVICE DEFINITION
services:
application_backend.event_listener.kernel_exception:
class: Application\BackendBundle\EventListener\KernelExceptionListener
arguments: [#router]
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
OPTION 2
Full details
LISTENER
namespace Application\FrontendBundle\Listener;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class PlayerListener
{
public function onKernelController(FilterControllerEvent $event)
{
$message = 'Bye inanzzz';
$event->setController(
function() use ($message) {
return new Response($message, 400);
}
);
}
}
SERVICE DEFINITION
services:
application_frontend.listener.player:
class: Application\FrontendBundle\Listener\PlayerListener
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
Try this instead:
$event->setController( $event->getController() );
I think it should work, but no guarantees.
This passes in the controller instead of a string, which is what your error indicates.
I was using Authentication success handler to populate some values on session on every success login. I wanted some database operation to be done so i pass #doctrine.dbal.default_connection from my config file. Here is my config file where i override success_handler function.
services:
security.authentication.success_handler:
class: XYZ\UserBundle\Handler\AuthenticationSuccessHandler
arguments: ["#security.http_utils", {}, #doctrine.dbal.default_connection]
tags:
- { name: 'monolog.logger', channel: 'security' }
In AuthenticationSuccessHandler.php my code is like this...
namespace Sourcen\UserBundle\Handler;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler;
use Symfony\Component\Security\Http\HttpUtils;
use Doctrine\DBAL\Connection;
class AuthenticationSuccessHandler extends DefaultAuthenticationSuccessHandler {
private $connection;
public function __construct( HttpUtils $httpUtils, array $options, Connection $dbalConnection ) {
$this->connection = $dbalConnection;
parent::__construct( $httpUtils, $options );
}
public function onAuthenticationSuccess( Request $request, TokenInterface $token ) {
$response = parent::onAuthenticationSuccess( $request, $token );
// DB CODE GOES
return $response;
}
}
This is working when i execute some controller URL directly. But when i execute my app home url like "www.xyz.com/web" it throws following error...
Catchable fatal error: Argument 3 passed to XYZ\UserBundle\Handler\AuthenticationSuccessHandler::__construct() must be an instance of Doctrine\DBAL\Connection, none given, called in /opt/lampp/xyz/app/cache/prod/appProdProjectContainer.php on line 1006 and defined in /opt/lampp/xyz/src/Sourcen/UserBundle/Handler/AuthenticationSuccessHandler.php on line 18
Any idea how it can be solved ?
You don't need to extends the DefaultAuthenticationSuccessHandlerclass.
Try to define your service class like:
namespace XYZ\UserBundle\Handler;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Doctrine\DBAL\Connection;
class AuthenticationSuccessHandler {
private $connection;
public function __construct( Connection $dbalConnection ) {
$this->connection = $dbalConnection;
}
public function onAuthenticationSuccess( InteractiveLoginEvent $event ) {
$user = $event->getAuthenticationToken()->getUser();
// DB CODE GOES
return $response;
}
}
and configure your service tagged to the event listener component security.interactive_login
services:
security.authentication.success_handler:
class: XYZ\UserBundle\Handler\AuthenticationSuccessHandler
arguments: [#doctrine.dbal.default_connection]
tags:
- { name: 'kernel.event_listener', event: 'security.interactive_login'. method:'onAuthenticationSuccess' }
PS: Why don't you use the doctrine.orm.entity_managerinstead of doctrine.dbal.default_connection? (In my sf i haven't this service dumped in the php app/console container:debug command)
I'm trying to do a redirect when a user impersonates another user.
For this I registered a service:
ACME_listener.security_switch_user:
class: ACME\CustomerLoginBundle\Listener\SecuritySwitchUserListener
arguments: [#service_container, #router, #security.context]
tags:
- { name: kernel.event_listener, event: security.switch_user, method: onSecuritySwitchUser }
My listener class looks like this:
namespace ACME\CustomerLoginBundle\Listener;
use Symfony\Component\Security\Http\Event\SwitchUserEvent;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
class SecuritySwitchUserListener implements ListenerInterface {
public function __construct($appContainer, $router) {
$this->router = $router;
$this->appContainer = $appContainer;
}
public function onSecuritySwitchUser(SwitchUserEvent $event) {
echo "im in here!";
// this does get called
}
public function handle(GetResponseEvent $event) {
echo "but not here :(";
// this does not get called!
}
}
Now the problem is that I can not redirect the user from within the onSecuritySwitchUser method. Returning a RedirectResponse does NOT work and the SwitchUserEvent does NOT have a setResponse() method.
What do I have to do so that the handle() method does get called?
I think that handle() is called from onSecuritySwitchUser(). But I can be wrong.
UPDATE
You can overwrite the event with your own request :)
Look at:
Symfony\Component\Security\Http\Firewall\SwitchUserListener
And then Dispach new SwitchUserEvent with overwritten request
if (null !== $this->dispatcher) {
$switchEvent = new SwitchUserEvent($request, $token->getUser());
$this->dispatcher->dispatch(SecurityEvents::SWITCH_USER, $switchEvent);
}
Maybe that will help you.