In my Application I'm using a init function to init an action
the init function validate the user input
(for example the user is looking for an product what not exist -> the init function should redirect him to an errorpage "product ... not found")
/**
* #Route("/route/{var}", name="xyzbundle_xyz_index")
* #Template("VendorXyzBundle:xyz:index.html.twig")
*/
public function indexAction ($var)
{
$xyz = $this->initxyz($var);
...
.. more code
.
}
And there is a private function in this controller that should validate the from url given parameter and if it is wrong (dont exist in database etc), the private function should redirect
private function init($var)
{
if($this->databasesearchforexyz($var)){
// redirect to Errorpage (No xyz found named ...)
return $this->redirect($this->generateUrl('xyz_error_...'));
}
if($this->checksomethingelse($var)){
// redirect to some other error page
}
}
Please note, these are not my real method/variable/path/etc. names.
The problem is, it is not redirecting.
You can check if the init function returns an actual response, then you can return it directly from the main code. Like this:
public function indexAction ($var)
{
$xyz = $this->initxyz($var);
if ($xyz instanceof \Symfony\Component\HttpFoundation\Response) {
return $xyz;
}
...
.. more code
.
}
Btw, if you only need to check database existance you can use symfony's paramconverter
Here's some suggestion.
Return true from the init function if there's no redirect and return false if there's a redirect.
Example:
private function init($var) {
if ($error) {
// An error occurred, redirect
$this->redirect($this->generateUrl('xyz_error_...'));
return false;
}
// Else, everything alright
return true;
}
public function indexAction ($var) {
if (!$this->init($var)) {
// Failed to init, redirection happening
return;
}
// Continue as normal
}
Using the answer of #alex88, I aggregate an exception and an exception listener to do the redirect. That avoid me to repeat the condition over and over again, because my function could redirect the user under different scenarios.
1. Controller
namespace AppBundle\Controller;
use AppBundle\Exception\UserHasToBeRedirectedException;
class DefaultController extends Controller
{
public function indexAction(...)
{
...
$this->userHasToBeRedirected();
...
}
private function userHasToBeRedirected()
{
...
if ($userHasToBeRedirected) {
$response = $this->redirect($this->generateUrl(...));
throw new UserHasToBeRedirectedException($response);
}
...
}
}
2. Exception
namespace AppBundle\Exception;
use Exception;
use Symfony\Component\HttpFoundation\Response;
class UserHasToBeRedirectedException extends Exception
{
private $response;
public function __construct(Response $response)
{
$this->response = $response;
}
public function getResponse()
{
return $this->response;
}
public function setResponse(Response $response)
{
$this->response = $response;
return $this;
}
}
3. Exception Listener
namespace AppBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use AppBundle\Exception\UserHasToBeRedirectedException;
class ExceptionListener
{
public function onKernelException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
...
if ($exception instanceof UserHasToBeRedirectedException) {
$response = $exception->getResponse();
$event->setResponse($response);
}
...
}
}
4. Register the service at service.yml
...
appBundle.exception_listener:
class: AppBundle\EventListener\ExceptionListener
tags:
- { name: kernel.event_listener, event: kernel.exception }
...
For more information:
Symfony Documantation about Events
Related
I've a personal application. I use design pattern CQRS/DDD for a API.
Schema:
User --> Controller (dispatch command) --> Command handler --> some services...
In my Rest API controller
$this->dispatch($cmd);
If a throw a exception in services or specification classes for example, ok, I've a listener to catch exception and create JSON response error.
But if I want to develop an interface module with TWIG, I think I will not use my listener because I don't want a JSON response.
Should I used try/catch in my controller of my new interface module ?
SomeController extends AbstractController
{
public function getObject($id)
{
try {
$this->dispatch($cmd);
catch(SomeException $ex) {
$this->render(....)
}
}
}
Where is the best place to catch exception for TWIG ?
Thanks.
Edit:
#Cid
if (some conditions && $form->handleRequest($request)->isValid()) --> My handler don't return bool or values.
Imagine this code. Imagine I want share a service between an API and web view app.
class ApiController
{
public function register()
{
$this->dispatch($cmd);
}
}
class WebController
{
public function register()
{
$this->dispatch($cmd);
}
}
class SomeHandler implements CommandHandlerInterface
{
/** #required */
public RegisterService $service;
public function __invoke(SomeCommand $command)
{
$this->service->register($command->getEmail())
}
}
class RegisterService
{
public function register(string $email)
{
// Exception here
}
}
So, I think the best place to handle Exception is EventSubscriber, see here: https://symfony.com/doc/current/reference/events.html#kernel-exception
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
public function onKernelException(ExceptionEvent $event)
{
$exception = $event->getThrowable();
$response = new Response();
// setup the Response object based on the caught exception
$event->setResponse($response);
// you can alternatively set a new Exception
// $exception = new \Exception('Some special exception');
// $event->setThrowable($exception);
}
My situation: I have a NavigatorController which is triggered by AJAX requests, and will
$this->forward("controllername")
the request. But how can I check if the controller exists based on controller name? Of course, BEFORE the actual forward happens and throws an error when the page controller does not exists.
You can actually use the
controller_resolver
service that Symfony uses in order to check if controller exists.
public function indexAction(Request $request)
{
$request->attributes->set('_controller', 'AppBundle\Controller\ExampleController::exampleAction');
try{
$this->get('debug.controller_resolver')->getController($request);
} catch (\Exception $e) {
$x = $e->getCode();
}
}
Hope it helps!
Also You can check by using Service:
namespace AppBundle\Service;
class ExampleService
{
/**
* #param string $controller
* #return bool
*/
public function has($controller)
{
list($class, $action) = explode('::', $controller, 2);
return class_exists($class);
}
}
In app/config/services.yml :
services:
app.controller.check:
class: AppBundle\Service\ExampleService
In Controller:
public function indexAction(Request $request)
{
$controller = 'AppBundle\Controller\DefaultController';
if($this->get('app.controller.check')->has($controller))
{
echo 'Exists';
}
else
{
echo "Doesn't exists";
}
}
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 have implemented following code to run a code on before any action of any controller. However, the beforeFilter() function not redirecting to the route I have specified. Instead it takes the user to the location where the user clicked.
//My Listener
namespace Edu\AccountBundle\EventListener;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class BeforeControllerListener
{
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
if (!is_array($controller))
{
//not a controller do nothing
return;
}
$controllerObject = $controller[0];
if (is_object($controllerObject) && method_exists($controllerObject, "beforeFilter"))
//Set a predefined function to execute Before any controller Executes its any method
{
$controllerObject->beforeFilter();
}
}
}
//I have registered it already
//My Controller
class LedgerController extends Controller
{
public function beforeFilter()
{
$commonFunction = new CommonFunctions();
$dm = $this->getDocumentManager();
if ($commonFunction->checkFinancialYear($dm) == 0 ) {
$this->get('session')->getFlashBag()->add('error', 'Sorry');
return $this->redirect($this->generateUrl('financialyear'));//Here it is not redirecting
}
}
}
public function indexAction() {}
Please help, What is missing in it.
Thanks Advance
I would suggest you follow the Symfony suggestions for setting up before and after filters, where you perform your functionality within the filter itself, rather than trying to create a beforeFilter() function in your controller that is executed. It will allow you to achieve what you want - the function being called before every controller action - as well as not having to muddy up your controller(s) with additional code. In your case, you would also want to inject the Symfony session to the filter:
# app/config/services.yml
services:
app.before_controller_listener:
class: AppBundle\EventListener\BeforeControllerListener
arguments: ['#session', '#router', '#doctrine_mongodb.odm.document_manager']
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
Then you'll create your before listener, which will need the Symony session and routing services, as well as the MongoDB document manager (making that assumption based on your profile).
// src/AppBundle/EventListener/BeforeControllerListener.php
namespace AppBundle\EventListener;
use Doctrine\ODM\MongoDB\DocumentManager;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use AppBundle\Controller\LedgerController;
use AppBundle\Path\To\Your\CommonFunctions;
class BeforeControllerListener
{
private $session;
private $router;
private $documentManager;
private $commonFunctions;
public function __construct(Session $session, Router $router, DocumentManager $dm)
{
$this->session = $session;
$this->router = $router;
$this->dm = $dm;
$this->commonFunctions = new CommonFunctions();
}
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
if (!is_array($controller)) {
return;
}
if ($controller[0] instanceof LedgerController) {
if ($this->commonFunctions->checkFinancialYear($this->dm) !== 0 ) {
return;
}
$this->session->getFlashBag()->add('error', 'Sorry');
$redirectUrl= $this->router->generate('financialyear');
$event->setController(function() use ($redirectUrl) {
return new RedirectResponse($redirectUrl);
});
}
}
}
If you are in fact using the Symfony CMF then the Router might actually be ChainRouter and your use statement for the router would change to use Symfony\Cmf\Component\Routing\ChainRouter;
There are a few additional things here you might want to reconsider - for instance, if the CommonFunctions class needs DocumentManager, you might just want to make your CommonFunctions class a service that injects the DocumentManager automatically. Then in this service you would only have to inject your common functions service instead of the document manager.
Either way what is happening here is that we are checking that we are in the LedgerController, then checking whether or not we want to redirect, and if so we overwrite the entire Controller via a callback. This sets the redirect response to your route and performs the redirect.
If you want this check on every single controller you could simply eliminate the check for LedgerController.
.
$this->redirect() controller function simply creates an instance of RedirectResponse. As with any other response, it needs to be either returned from a controller, or set on an event. Your method is not a controller, therefore you have to set the response on the event.
However, you cannot really set a response on the FilterControllerEvent as it is meant to either update the controller, or change it completely (setController). You can do it with other events, like the kernel.request. However, you won't have access to the controller there.
You might try set a callback with setController which would call your beforeFilter(). However, you wouldn't have access to controller arguments, so you won't really be able to call the original controller if beforeFilter didn't return a response.
Finally you might try to throw an exception and handle it with an exception listener.
I don't see why making things this complex if you can simply call your method in the controller:
public function myAction()
{
if ($response = $this->beforeFilter()) {
return $response;
}
// ....
}
public function onKernelController(FilterControllerEvent $event)
{
$request = $event->getRequest();
$response = new Response();
// Matched route
$_route = $request->attributes->get('_route');
// Matched controller
$_controller = $request->attributes->get('_controller');
$params = array(); //Your params
$route = $event->getRequest()->get('_route');
$redirectUrl = $url = $this->container->get('router')->generate($route,$params);
$event->setController(function() use ($redirectUrl) {
return new RedirectResponse($redirectUrl);
});
}
Cheers !!
In our CakePHP 3 application we found a different behaviour. We're sure that it worked well in CakePHP 2, so I suppose something changed in new version.
When user visits this url: /b2controller/myMethod, these methods run:
AppController::beforeFilter()
BController::beforeFilter()
B2Controller::beforeFilter()
B2Controller::myMethod()
B2Controller::myMethod2()
then user is redirected to this url /ccontroller/myMethod10/
But we need this:
When user visits
/b2controller/myMethod and $isOk condition is true, then redirect user to /ccontroller/myMethod10/, without running BController::beforeFilter(), B2Controller::beforeFilter(), B2Controller::myMethod() and BController::MyMethod2().
Our minimal code is like this:
class AppController {
function beforeFilter(Event $event) {
// set $isOk variable
if ($isOk == TRUE) {
return $this->redirect('/ccontroller/myMethod10/');
}
$aa=1;
$ab=2;
}
}
class BController extends AppController {
function beforeFilter(Event $event) {
parent::beforeFilter($event);
$a=1;
$b=2;
}
function myOtherMethod() {
myOtherMethod2();
}
function myOtherMethod2() {
...
...
}
}
class B2Controller extends BController {
function beforeFilter(Event $event) {
parent::beforeFilter($event);
$m1=1;
$m2=2;
}
function myMethod() {
myMethod2();
}
function myMethod2() {
...
...
}
}
class CController extends AppController {
function beforeFilter(Event $event) {
parent::beforeFilter($event);
}
function myMethod10() {
...
...
...
}
}
How can I make user to redirect to another controller action, from the beforeFilter of main class ? Note that redirect occurs. But user is redirected after calling myMethod() and myMethod2().
Also note that there is other controllers like CController that uses beforeFilter redirect behaviour.
Here are 3 methods that works:
Method 1 - Override startupProcess in your controller(s)
Override the startupProcess method of AppController:
// In your AppController
public function startupProcess() {
// Compute $isOk
if ($isOk) {
return $this->redirect('/c/myMethod10');
}
return parent::startupProcess();
}
This is a short and quite clean method, so I would go for this one if you can. If this does not fit your needs, see below.
Note: If you use this method, your components may not be initialized when you compute $isOk since the initialization is done by parent::startupProcess.
Method 2 - Send the response from AppController:
One easy but not really clean way may be to send the response from AppController::beforeFilter:
public function beforeFilter(\Cake\Event\Event $event) {
// Compute $isOk
if ($isOk) {
$this->response = $this->redirect('/c/myMethod10');
$this->response->send();
die();
}
}
Method 3 - Use Dispatcher Filters
A more "clean" way would be to use Dispatcher Filters:
In src/Routing/Filter/RedirectFilter.php:
<?php
namespace App\Routing\Filter;
use Cake\Event\Event;
use Cake\Routing\DispatcherFilter;
class RedirectFilter extends DispatcherFilter {
public function beforeDispatch(Event $event) {
// Compute $isOk
if ($isOk) {
$response = $event->data['response'];
// The code bellow mainly comes from the source of Controller.php
$response->statusCode(302);
$response->location(\Cake\Routing\Router::url('/c/myMethod10', true));
return $response;
}
}
}
In config/bootstrap.php:
DispatcherFactory::add('Redirect');
And you can remove the redirection in your AppController. This may be the cleanest way if you are able to compute $isOk from the DispatcherFilter.
Note that if you have beforeRedirect event, these will not be triggered with this method.
Edit: This was my previous answer which does not work very well if you have multiple B-like controllers.
You need to return the Response object returned by $this->redirect(). One way of achieving this is by doing the following:
class BController extends AppController {
public function beforeFilter(\Cake\Event\Event $event) {
$result = parent::beforeFilter($event);
if ($result instanceof \Cake\Network\Response) {
return $result;
}
// Your stuff
}
}
The code bellow the if is executed only if there was no redirection (parent::beforeFilter($event) did not return a Response object).
Note: I do not know how you compute isOk, but be careful of infinite redirection loop if you call $this->redirect() when calling /ccontroller/mymethod10.