Symfony - Check if controller exists - php

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";
}
}

Related

Symfony SessionBag not working with native file handler

I can't get my Symfony 5.2 session bag working with native file handling. Here is my bag...
class TestSessionBag extends AttributeBag implements SessionBagInterface
{
public const NAME = 'TestSessionBag';
public function __construct() {
}
public function getName() : string {
return self::NAME;
}
public function getStorageKey() : string {
return self::NAME;
}
public function setSomeText(string $text) {
$this->set('some-text',$text);
}
public function getSomeText() {
return $this->get('some-text');
}
}
Here is my controller...
class SessionBenchController extends AbstractController
{
public $requestStack;
public $sessionBag;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
try {
$this->sessionBag = $this->getSession()->getBag(TestSessionBag::NAME);
error_log('found existing bag');
} catch(\Exception $ex) {
error_log('constructing new bag');
$this->getSession()->registerBag(new TestSessionBag());
$this->sessionBag = $this->getSession()->getBag(TestSessionBag::NAME);
}
}
/**
* #Route("/session", name="app_session_bench")
*/
public function index(): Response
{
// $text = $this->getSession()->get('some-text');
$text = $this->sessionBag->get('some-text');
error_log('in index, text = '.$text);
return $this->render('session_bench/index.html.twig', [
'some_text' => $text
]);
}
/**
* #Route("/session/some-text", name="app_session_some_text")
*/
public function someText(Request $request): Response
{
$text = $request->request->get('text');
error_log('in someText, text = '.$text);
// $this->getSession()->set('some-text',$text);
$this->getSession()->getBag(TestSessionBag::NAME)->get('some-text');
return new JsonResponse(['success' => 1]);
}
public function getSession() : Session
{
return $this->requestStack->getSession();
}
}
The bag is never found in the constructor. It is constructed fine on the first request, but on the second request I get "Cannot register a bag when the session is already started".
In framework.yml I am using the native file handler...
session:
# handler_id: null
handler_id: 'session.handler.native_file'
save_path: '%kernel.project_dir%/var/sessions/%kernel.environment%'
If I use the default handler, it seems to work. However it still does not find the bag in the constructor...the bag must be registered for every request. But the session is already started! So I am wondering why I don't get the same error as I did with native file handling.
I would assume the problem with the native file handling is the serialization but I can't find any documentation on it. I tried adding JSON serialization to the bag to no effect. What am I missing here?

How to provide Symfony routing parameter programatically?

In this Symfony route
/**
* #Route("/board/{board}/card/{card}", name="card_show", methods={"GET"}, options={})
*/
public function show(Board $board, Card $card): Response
{
$card->getLane()->getBoard(); // Board instance
// ...
}
How is it possible to add the {board} parameter programatically, since it is already available in {card}? Now, I always need to add two parameters, when generating links to show action.
After some research I've found the RoutingAutoBundle (https://symfony.com/doc/master/cmf/bundles/routing_auto/introduction.html#usage) which would provide the functions I need, but it's not available for Symfony 5 anymore.
Thanks.
Okay, after some investigation I've found this question
Which lead me to this helpful answer.
My controller action (with #Route annotation) looks like this:
/**
* #Route("/board/{board}/card/{card}", name="card_show", methods={"GET"})
*/
public function show(Card $card): Response
{
}
We just have one argument ($card) in method signature, but two arguments in route.
This is how to call the route in twig:
path("card_show", {card: card.id})
No board parameter required, thanks to a custom router.
This is how the custom router looks like:
<?php // src/Routing/CustomCardRouter.php
namespace App\Routing;
use App\Repository\CardRepository;
use Symfony\Component\Routing\RouterInterface;
class CustomCardRouter implements RouterInterface
{
private $router;
private $cardRepository;
public function __construct(RouterInterface $router, CardRepository $cardRepository)
{
$this->router = $router;
$this->cardRepository = $cardRepository;
}
public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH)
{
if ($name === 'card_show') {
$card = $this->cardRepository->findOneBy(['id' => $parameters['card']]);
if ($card) {
$parameters['board'] = $card->getLane()->getBoard()->getId();
}
}
return $this->router->generate($name, $parameters, $referenceType);
}
public function setContext(\Symfony\Component\Routing\RequestContext $context)
{
$this->router->setContext($context);
}
public function getContext()
{
return $this->router->getContext();
}
public function getRouteCollection()
{
return $this->router->getRouteCollection();
}
public function match($pathinfo)
{
return $this->router->match($pathinfo);
}
}
Now, the missing parameter board is provided programatically, by injecting and using the card repository. To enable the custom router, you need to register it in your services.yaml:
App\Routing\CustomCardRouter:
decorates: 'router'
arguments: ['#App\Routing\CustomCardRouter.inner']

Sending additional parameters to callback uri in socialite package for laravel

Im trying to use Socialite package for laravel and I would like to know how to pass additional parameters to callback url. It seems that OAuth allows additional params, but I haven't found any solution for laravel on how to pass them. Currently my methods look like this
public function login($provider)
{
return Socialite::with($provider)->redirect();
}
public function callback(SocialAccountService $service, $provider)
{
$driver = Socialite::driver($provider);
$user = $service->createOrGetUser($driver, $provider);
$this->auth()->login($user, true);
return redirect()->intended('/');
}
Suppose I want to get $user_role variable in my callback method. How do I pass it there?
You need to use state param if you want to pass some data.
Example for your provider
$provider = 'google';
return Socialite::with(['state' => $provider])->redirect();
Your callback function:
$provider = request()->input('state');
$driver = Socialite::driver($provider)->stateless();
gist example
For some reason, Optional Parameters didn't work for me, so i ended up by using session to pass variables from redirect method to the callback method. it's not the best way to do it, but it does the trick.
Simplified example
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\User;
use Socialite;
class FacebookController extends Controller {
/**
* Create a new controller instance.
*
* #return void
*/
public function redirect($my_variable)
{
session(['my_variable' => $my_variable]);
return Socialite::driver('facebook')->redirect();
}
/**
* Create a new controller instance.
*
* #return void
*/
public function callback(Request $request)
{
try {
$facebookAccount = Socialite::driver('facebook')->stateless()->user();
$my_variable = session('my_variable');
// your logic...
return redirect()->route('route.name');
} catch (Exception $e) {
return redirect()->route('auth.facebook');
}
}
}
You can update on the fly callback URL as like:
public function redirectToProvider($providerName)
{
config([
"services.$providerName.redirect" => config("services.$providerName.redirect").'?queryString=test'
]);
try {
return Socialite::driver($providerName)->redirect();
} catch (\Exception $e) {
return redirect('login');
}
}
It return queryString (request->all()) in callback URL function.
Changing redirect_uri in config on fly will redirect back to the intended domain but it does not return the user when tried to get user with following
Socialite::driver($social)->user()

Symfony 2 redirecting out of private function

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

Symfony2: Execute some code after every action

I recently started a project in Symfony2 and I need to run some methods before and after every action to avoid code redundancy (like preDispatch/postDispatch from Zend Framework and PreExecute/PostExecute from Symfony1).
I created a base class from which all the controllers are inherited,
and registered an event listener to run controller's preExecute() method before running requested action, but after reading tons of documentation and questions from here I still can't find how to run postExecute().
Foo/BarBundle/Controller/BaseController.php:
class BaseController extends Controller {
protected $_user;
protected $_em;
public function preExecute() {
$user = $this->get('security.context')->getToken()->getUser();
$this->_user = $user instanceof User ? $user : null;
$this->_em = $this->getDoctrine()->getEntityManager();
}
public function postExecute() {
$this->_em->flush();
}
}
Foo/BarBundle/Controller/FooController.php:
class FooController extends BaseController {
public function indexAction() {
$this->_user->setName('Eric');
$this->_em->persist($this->_user);
}
}
Foo/BarBundle/EventListener/PreExecute.php:
class PreExecute {
public function onKernelController(FilterControllerEvent $event) {
if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) {
$controllers = $event->getController();
if (is_array($controllers)) {
$controller = $controllers[0];
if (is_object($controller) && method_exists($controller, 'preExecute')) {
$controller->preExecute();
}
}
}
}
}
There is a discussion of this here and this particular example by schmittjoh may lead you in the right direction.
<?php
class Listener
{
public function onKernelController($event)
{
$currentController = $event->getController();
$newController = function() use ($currentController) {
// pre-execute
$rs = call_user_func_array($currentController, func_get_args());
// post-execute
return $rs;
};
$event->setController($newController);
}
}

Categories