I am trying with no luck to find a "referrer" object for use in my
controller. I expected there would be an object similar to the request
object with parameters specifying the _controller, _route and
arguments.
What I am trying to do is a language switcher action that redirects
the user to the same page in the new language. Something along the
lines of:
public function switchLangAction($_locale)
{
$args = array();
$newLang = ($_locale == 'en') ? 'fr' : 'en';
// this is how I would have hoped to get a reference to the referrer request.
$referrer = $this->get('referrer');
$referrerRoute = $referrer->parameters->get('_route');
$args = $referrer->parameters->get('args'); // not sure how to get the route args out of the params either!
$args['_locale'] = $newLang;
$response = new RedirectResponse( $this->generateUrl(
$referrerRoute,
$args
));
return $response;
}
It's also possible that there is another way to do this - I know in
rails there is the "redirect_to :back" method for example.
Any help would be greatly appreciated.
Why not changing the locale in the user's session?
First, define your locales in the router
my_login_route:
pattern: /lang/{_locale}
defaults: { _controller: AcmeDemoBundle:Locale:changeLang }
requirements:
_locale: ^en|fr$
Then, set the session
namespace Acme\DemoBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class LocaleController extends Controller
{
public function switchLangAction($_locale, Request $request)
{
$session = $request->getSession();
$session->setLocale($_locale);
// ... some other possible actions
return $this->redirect($session->get('referrer'));
}
}
In all other controllers you should set the session variable yourself by calling
$session->set('referrer', $request->getRequestUri());
You could also probably make an event listener to set the session variable for every page automatically.
It's my controller
class LocaleController extends Controller {
public function indexAction()
{
if(null === $this->getRequest()->getLocale()){
$locale = $this->getRequest()->getPreferredLanguage($this->getLocales());
$this->getRequest()->setLocale($locale);
}
else{
$locale = $this->getRequest()->getLocale();
}
return $this->redirect($this->generateUrl('corebundle_main_index', array('_locale' => $locale)));
}
public function changeLocaleAction($_locale)
{
$request = $this->getRequest();
$referer = $request->headers->get('referer');
$locales = implode('|',$this->getLocales());
$url = preg_replace('/\/('.$locales.')\//', '/'.$_locale.'/', $referer, 1);
return $this->redirect($url);
}
private function getLocales()
{
return array('ru', 'uk', 'en');
}
/**
* #Template()
*/
public function changeLocaleTemplateAction()
{
return array();
}
}
Related
App::bind('App\Http\Repositories\languageRepository',
function( $app, array $parameters)
{
return new App\Http\Repositories\languageRepository($parameters[0]);
} );
Route::get('/test/{id}', 'testController#getme');
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Repositories\languageRepository;
class test extends Controller
{
//
protected $language;
public function __construct(languageRepository $rep){
$this->language = $rep;
}
public function getme(){
$this->language->getMe();
}
}
When user accesses the route /test/5 for example, it goes to test Controller. what I'd like to do is that it should automatically pass my route parameter to App:bind function and automatically create languageRepository class with the constructor value passed as my route paramter. what happens is the code actually tells me $parameters[0] is undefined offset. why is that? I've tried App::make but then how do I pass the parameter from route to App::make?
You can accomplish this using the container's request instance, for query parameters:
App::bind('App\Http\Repositories\languageRepository',function($app)
{
$request = $app['request'];
$parameters = $request->all();
return new App\Http\Repositories\languageRepository($parameters[0]);
});
You can accomplish this using the container's request instance, for a route parameter:
App::bind('App\Http\Repositories\languageRepository',function($app)
{
$request = $app['request'];
$segment = $request->segment(1);
return new App\Http\Repositories\languageRepository($segment);
});
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 !!
I have a site which is localized into several languages. Every public route is prefixed with the locale key (e.g. /{locale}/foo/bar), which gets caught and applied by middleware.
When generating URLs to point to other pages, I end up needing to feed the current locale into every url, like so:
Foo Bar
Otherwise the output url will contain %7Blocale%7D, which breaks it. This strikes me as needlessly verbose. Is there not a way to specify a default value for a given named parameter, such that if no value is explicitly provided for 'locale' it can be defaulted to whatever the current locale is?
I've inspected the UrlGenerator class, but I don't see anything to that effect.
The Route class has a defaults property, but that only appears to be used as part of binding the route to the current request.
Ultimately, not a huge issue, just wondering if anyone has any ideas for ways to save a bit of sanity.
You can use URL defaults as well at a middleware:
use Illuminate\Support\Facades\URL;
URL::defaults(
[
'locale' => $locale
]
);
When you define your routes, use optional variables with defaults:
Routes:
Route::get('{locale?}/foo/bar', 'Controller#fooBar');
Controller:
public function __construct()
{
$this->locale = session()->has('locale') ? session('locale') : 'en';
}
public function fooBar($locale = null)
{
$locale = $locale ?: $this->locale;
}
OR:
public function fooBar($locale = 'en')
{
$locale = $locale ?: $this->locale;
}
Wherever you call your route:
Foo Bar
You could optionally put the constructor in a BaseController class that all your other controllers extend.
There may be better ways to do this, but this would keep you from having to include the locale wherever you call a route.
There isn't any built in means of doing this, but I managed to achieve the desired result by extending the UrlGenerator
<?php
namespace App\Services;
use Illuminate\Routing\UrlGenerator as BaseGenerator;
use Illuminate\Support\Arr;
class UrlGenerator extends BaseGenerator
{
protected $default_parameters = [];
public function setDefaultParameter($key, $value){
$this->default_parameters[$key] = $value;
}
public function removeDefaultParameter($key){
unset($this->default_parameters[$key]);
}
public function getDefaultParameter($key){
return isset($this->default_parameters[$key]) ? $this->default_parameters[$key] : null;
}
protected function replaceRouteParameters($path, array &$parameters)
{
if (count($parameters) || count($this->default_parameters)) {
$path = preg_replace_sub(
'/\{.*?\}/', $parameters, $this->replaceNamedParameters($path, $parameters)
);
}
return trim(preg_replace('/\{.*?\?\}/', '', $path), '/');
}
protected function replaceNamedParameters($path, &$parameters)
{
return preg_replace_callback('/\{(.*?)\??\}/', function ($m) use (&$parameters) {
return isset($parameters[$m[1]]) ? Arr::pull($parameters, $m[1]) : ($this->getDefaultParameter($m[1]) ?: $m[0]);
}, $path);
}
}
Then rebinding our subclass into the service container
class RouteServiceProvider extends ServiceProvider
{
public function register(){
parent::register();
//bind our own UrlGenerator
$this->app['url'] = $this->app->share(function ($app) {
$routes = $app['router']->getRoutes();
$url = new UrlGenerator(
$routes, $app->rebinding(
'request', function ($app, $request) {
$app['url']->setRequest($request);
}
)
);
$url->setSessionResolver(function () {
return $this->app['session'];
});
$app->rebinding('routes', function ($app, $routes) {
$app['url']->setRoutes($routes);
});
return $url;
});
}
//...
}
Then all I needed to do was inject the default locale into the UrlGenerator from the Locale middleware
public function handle($request, Closure $next, $locale = null) {
//...
$this->app['url']->setDefaultParameter('locale', $locale);
return $next($request);
}
Now route('foo.bar') will automatically bind the current locale to the route, unless another is explicitly provided.
I'm doing Acl in my Module.php and i'd like to redirect users to a specific route if they are unauthorized.
My route is Literal and is parameterless (i.e., has default controller and action params set in config).
I just can't find a way to do so.
Actually i know i can set controller and action params of my RouteMatch (i get it from the MvcEvent) but i want to set a new route by its name and without setting params.
Any help is appreciated, thanks
It depends on the function definition where you want to redirect from.
One could be a simple and direct onBootstrap -> on Dispatch, a function definition -
Eg:
use Zend\Mvc\MvcEvent;
.....
........
class Module {
public function onBootstrap(MvcEvent $e)
{
$e->getApplication()->getEventManager()->getSharedManager()->attach('SOME_CONTROLLER OR * (-> all controllers)', 'dispatch', function($e) {
$controller = $e->getTarget();
if (unauthorized_user) {
$controller->plugin('redirect')->toRoute('ROUTE_NAME');
}
}, 100);
}
......
........
}
Other could be onBootstrap -> on Route -
This gets tricky because the function gets executed even before the route is finalized and so you don't have a $e->getTarget() i.e. Controller object yet.
public function onBootstrap(MvcEvent $e) {
$e->getApplication()
->getEventManager()
->getSharedManager()
->attach('*', 'route', function($e) {
if (unauthorized_user) {
return $this->customRedirect($e, 'ROUTE_NAME');
} else {
return $this->customRedirect($e, 'ROUTE_NAME', 'ACTION_NAME');
}
}, 100);
}
public function customRedirect($event, $route, $action = '') {
$url = $event->getRouter()->assemble(array('action' => $action), array('name' => $route));
$response = $event->getResponse();
$response->getHeaders()->addHeaderLine('Location', $url);
$response->setStatusCode(302);
$response->sendHeaders();
exit();
}
Just in-case to get the current route_name, controller_name and action_name.
Can use this -
$sm = $e->getApplication()->getServiceManager();
$router = $sm->get('router');
$request = $sm->get('request');
$matchedRoute = $router->match($request);
$params = $matchedRoute->getParams();
$route_name = $matchedRoute->getMatchedRouteName();
$controller_name = $params['controller'];
$action_name = $params['action'];
I hope it helps someone.
i made in frontend nice url with symfony routing.yml. In frontend i can use for example:
url_for('#news', $news);
this generate for me:
http://mysite.com/frontend_dev.php/news/nice-title/1
but if i use this in backend i have:
http://mysite.com/backend_dev.php/news/nice-title/1
is possible generate these link in backend for frontend?
In apps/frontend/config/frontendConfiguration.class.php use something like this:
class frontendConfiguration extends sfApplicationConfiguration
{
protected $backendRouting = null;
public function generateBackendUrl($name, $parameters = array(), $absolute = false)
{
return sfConfig::get('app_site_url').$this->getBackendRouting()->generate($name, $parameters, $absolute);
}
public function getBackendRouting()
{
if (!$this->backendRouting )
{
$this->backendRouting = new sfPatternRouting(new sfEventDispatcher());
$config = new sfRoutingConfigHandler();
$routes = $config->evaluate(array(sfConfig::get('sf_apps_dir').'/backend/config/routing.yml'));
$this->backendRouting->setRoutes($routes);
}
return $this->backendRouting;
}
}
use it like this:
$sf_context->getConfiguration()->generateBackendUrl('my_custom_route', array('id' => 1), true)
More information here: http://symfony.com/blog/cross-application-links