How to configure Symfony2 sticky locale during session - php

I would like to translate my website thanks to an link on the right top.
I found out that, since Symfony 2.1, the locale is not stored in the session anymore.
So, I followed this Symfony documentation: Making the Locale "Sticky" during a User's Session
...Bundle/Service/LocaleListener.php
class LocaleListener implements EventSubscriberInterface
{
private $defaultLocale;
public function __construct($defaultLocale)
{
$this->defaultLocale = $defaultLocale;
}
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if (!$request->hasPreviousSession()) {
return;
}
$locale = $request->attributes->get('_locale');
var_dump($locale);
if ($locale) {
$request->getSession()->set('_locale', $locale);
} else {
$request->setLocale($request->getSession()->get('_locale', $this->defaultLocale));
}
}
static public function getSubscribedEvents()
{
return array(
// must be registered before the default Locale listener
KernelEvents::REQUEST => array(array('onKernelRequest', 17)),
);
}
}
...Bundle/Resources/config/services.yml
locale_listener:
class: ..Bundle\Service\LocaleListener
arguments: ["%kernel.default_locale%"]
tags:
- { name: kernel.event_subscriber }
./app/config/config.yml
framework:
translator: { fallback: en }
And, I add two links to translate my website on the parent twig template, shown below (Symfony2 locale languages whole page event listener).
base.html.twig
<li><a href="{{-
path(app.request.get('_route'),
app.request.get('_route_params')|merge({'_locale' : 'fr'}))
-}}">FR</a></li>
<li><a href="{{-
path(app.request.get('_route'),
app.request.get('_route_params')|merge({'_locale' : 'en'}))
-}}">EN</a></li>
Problem and Question
When I click on one of these links, the parameter _locale is added.
For instance:
satisfaction?_locale=fr
So, the value of the _locale parameter is fr. Consequently, my website should be translated in french.
Nevertheless, that
var_dump($locale)
in the listener is displayed three times:
null
en
null
I don't understand why the _locale parameter is not found when it display null and why the en?

With your listener, you will catch all request and subrequest that is not needed. This explain the three times apparition.
Try to add this following code to your onKernelRequest method:
if (HttpKernel::MASTER_REQUEST != $event->getRequestType()) {
return;
}
This will avoid subRequests and possibly resolve your problem.

Related

Symfony 2: Disable Doctrine event listener in ContainerAwareCommand

I am using several Doctrine listeners registered in configuration file for some automatic updates (created_on, updated_on timestamps etc.).
Currently I have implemented additional functionality that requires stashing prepared values in the database for easier searching.
I am thinking about update Symfony command that would prepare these values instead of SQL update script (actually any sort of change or update in the way the value is crated would than require just running this single command). However this would also trigger the EventListeners mentioned earlier.
Is there a way how to disable particular EventLister for single Command?
something like this should do the trick :
$searchedListener = null;
$em = $this->getDoctrine()->getManager();
foreach ($em->getEventManager()->getListeners() as $event => $listeners) {
foreach ($listeners as $key => $listener) {
if ($listener instanceof ListenerClassYouLookFor) {
$searchedListener = $listener;
break 2;
}
}
}
if ($searchedListener) {
$evm = $em->getEventManager();
$evm->removeEventListener(array('onFlush'), $searchedListener);
}
else { //listener not found
}
In services.yaml I have my listener defined this way:
services:
App\EventListener\DoctrinePostUpdateListener:
tags:
- { name: doctrine.event_listener, event: postUpdate }
To my listener class I added private variable $enabled with condition to stop execution:
class DoctrinePostUpdateListener
{
private $enabled = true;
public function postUpdate(LifecycleEventArgs $args)
{
if ($this->enabled === false) { //stop execution
return;
}
.. your code to execute ..
return;
}
public function setEnabled(bool $enabled)
{
$this->enabled = $enabled;
}
}
and then in my service/controller code where I don't want to execute this listener I just set this $enable variable to false:
$listeners = $this->em->getEventManager()->getListeners('postUpdate');
foreach ($listeners as $key => $listener) {
if ($listener instanceof DoctrinePostUpdateListener) {
$listener->setEnabled(false);
break;
}
}
It make more sense to wrap the logic inside Doctrine listener around a:
if ($this->enabled) {
So everyone can understand that the logic can be disabled or not.
You could use a parameter to enable or not the code (see http://symfony.com/doc/current/service_container/parameters.html).
my_doctrine_listener_enabled: true
You set it to false in your command:
$container->setParameter('my_doctrine_listener_enabled', false);
Since the parameter is modified at runtime, I recommend you to not use it via DIC but via
$container->getParameter('my_doctrine_listener_enabled')
Or another approach, could be:
Create variable "enabled" inside Doctrine listener
Inject Doctrine listener in command
Set $this->myListener->enabled = false

How should one redirect locale-less URIs to locale-full ones in Symfony-CMF?

Background
We have a (fairly typical?) arrangement for a multilingual Symfony CMF website, where resource paths are prefixed by the desired locale—for example:
http://www.example.com/en/path/to/english-resource.html; and
http://www.example.com/fr/voie/à/ressource-française.html.
We are using RoutingAutoBundle to store such routes in the content repository, and DynamicRouter to utilise them: simple and easy.
If a GET request arrives without a locale prefix, we would like to:
determine the most appropriate locale for the user; and then
redirect1 the user to the same path but with locale prefix added.
Current Approach
The first part is an obvious candidate for LuneticsLocaleBundle, with router higher in its guessing order than our desired fallback methods: again, simple and easy.
However, how best to implement the second part is a little less obvious. Currently we have configured Symfony's default/static router to have a lower priority in the routing chain than DynamicRouter, and have therein configured a controller as follows:
/**
* #Route("/{path}", requirements={"path" = "^(?!(en|fr)(/.*)?$)"})
* #Method({"GET"})
*/
public function localeNotInUriAction()
{
$request = this->getRequest();
$this->redirect(
'/'
. $request->getLocale() // set by Lunetics
. $request->getRequestUri()
);
}
But this feels rather hacky and I'm on the search for something "cleaner".
A better way?
Initially I thought to modify LuneticsLocaleBundle so that it would fire an event whenever a guesser determines the locale, thinking that if it was not the RouterLocaleGuesser then we could infer that the requested URI did not contain a locale. However this clearly isn't the case, since the RouterLocaleGuesser will only determine the locale if there was a route in the first place—so I'd not have made any progress.
I'm now a bit stuck for any other ideas. Perhaps I'm already doing the right thing after all? If so, then all I need to do is find some way to inject the permitted locales (from the config) into the requirement regex…
External redirection, i.e. via a response with HTTP 302 status.
we use a custom 404 handler and lunetics:
exception_listener:
class: AppBundle\EventListener\ExceptionListener
arguments:
container: "#service_container"
tags:
- { name:"kernel.event_listener", event:kernel.exception, handler:onKernelException }
and the php class
class ExceptionListener
{
/**
* #var ContainerInterface
*/
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
if ($this->container->getParameter('kernel.debug')) {
// do not interfere with error handling while debugging
return;
}
$exception = $event->getException();
if ($exception instanceof NotFoundHttpException) {
$this->handle404($event);
return;
}
// ...
}
public function handle404(GetResponseForExceptionEvent $event)
{
$request = $event->getRequest();
if (preg_match('#^\/(de|fr|en)\/#', $request->getPathInfo())) {
// a real 404, these are nicely handled by Twig
return;
}
// i *think* that the locale is not set on the request, as lunetics comes after routing, and the routing will raise the 404
$bestLang = $this->container->get('lunetics_locale.guesser_manager')->runLocaleGuessing($request);
if (! $bestLang) {
$bestLang = 'de';
}
$qs = $request->getQueryString();
if (null !== $qs) {
$qs = '?'.$qs;
}
$url = $request->getSchemeAndHttpHost() . $request->getBaseUrl() . '/' . $bestLang . $request->getPathInfo() . $qs;
$this->redirect($event, $url);
}
it would be nicer to also check if the target path actually exists - as is, we will redirect /foobar to /de/foobar and display a 404 for that one, which is not that elegant.

A language switcher in Symfony 2.4

I have an object Language, and I can add, delete and update languages from the admin page.
What I want to do, is to add a language switcher, I put this html/twig code:
{% for language in languages %}
<li>{{ language.language | capitalize }} ({{ language.code }})</li>
{% endfor %}
And an action the route for the action is evr_footer_switch_language, the one I used in the switcher above:
public function switchlanguageAction($locale = 'en') {
$this->get('session')->set('_locale', $locale);
$request = $this->getRequest();
$request->setLocale($locale);
return $this->redirect($request->headers->get('referer'));
}
This is the route I defined for the action/controller switchlanguageAction()
evr_footer_switch_language:
pattern: /language/switch/{locale}
defaults: { _controller: EvrHomeBundle:Footer:switchlanguage, locale: en }
It seems to me very simple in principle, you click on the link of the language (got from the database), send the code of the language (exemple : 'fr', 'en', 'zh' etc...) to the action as a $locale variable, then set the Locale of the session/request to this value.
The problem is that none of this works, and the language is still 'EN' (default value).
Note According to the requirements of this project, The language can't be mentioned in the URL (like fr/articles, en/articles), but the same URL (/articles/) can show in different languages, this is why I didn't use the pre-defined slug (_locale).
Thanks
While in search for some more details in order to write an answer I stumbled upon this Symfony cookbook entry: Making the Locale "Sticky" during a User's Session
I think that's exactly what you need ;)
Symfony 2.6:
I used the LocaleListener mentioned in "Making the Locale Sticky", but also had to use this to get things working properly:
/** from Controller
*
* #Route("/changeLanguage/{changeToLocale}", name="changeLanguage")
*
*/
public function changeLanguageAction($changeToLocale){
$this->get('request')->attributes->set('_locale', null);
$this->get('session')->set('_locale', $changeToLocale);
return $this->redirect($this->generateUrl('index'));
}
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if (!$request->hasPreviousSession()) {
return;
}
// try to see if the locale has been set as a _locale routing parameter
if ($locale = $request->query->get('swich_language')) {
$request->getSession()->set('_locale', $locale);
$routing = $this->router->match($request->getPathInfo());
$route_params = array();
foreach ($routing as $key => $value) {
if($key[0] !== "_")
{
$route_params[$key] = $value;
}
}
$parameters = \array_merge($route_params, array("_locale" => $locale));
$url = $this->urlGenerator->generate($routing['_route'], $parameters);
$response = new RedirectResponse($url);
$event->setResponse($response);
}
}
You can add as a kernel request and with querystring swich_language you can change it

Zend Framework 2: How to place a redirect into a module, before the application reaches a controller

Let's say we have a module named Cart and want to redirect users if some condition is met.
I want to place a redirect at the module bootstrapping stage, before the application reaches any controller.
So here is the module code:
<?php
namespace Cart;
class Module
{
function onBootstrap() {
if (somethingIsTrue()) {
// redirect
}
}
}
?>
I wanted to use the Url controller plugin, but it seems the controller instance is not available at this stage, at least I don't know how to get it.
Thanks in advance
This should do the necessary work:
<?php
namespace Cart;
use Zend\Mvc\MvcEvent;
class Module
{
function onBootstrap(MvcEvent $e) {
if (somethingIsTrue()) {
// Assuming your login route has a name 'login', this will do the assembly
// (you can also use directly $url=/path/to/login)
$url = $e->getRouter()->assemble(array(), array('name' => 'login'));
$response=$e->getResponse();
$response->getHeaders()->addHeaderLine('Location', $url);
$response->setStatusCode(302);
$response->sendHeaders();
// When an MvcEvent Listener returns a Response object,
// It automatically short-circuit the Application running
// -> true only for Route Event propagation see Zend\Mvc\Application::run
// To avoid additional processing
// we can attach a listener for Event Route with a high priority
$stopCallBack = function($event) use ($response){
$event->stopPropagation();
return $response;
};
//Attach the "break" as a listener with a high priority
$e->getApplication()->getEventManager()->attach(MvcEvent::EVENT_ROUTE, $stopCallBack,-10000);
return $response;
}
}
}
?>
Of course it gives you an error because you must attach your listener to an event. In the folllowing example i use SharedManager and i attach the listener to AbstractActionController.
Of course you can attach your listener to another event. Below is just a working example to show you how it works. For mor info visit http://framework.zend.com/manual/2.1/en/modules/zend.event-manager.event-manager.html.
public function onBootstrap($e)
{
$e->getApplication()->getEventManager()->getSharedManager()->attach('Zend\Mvc\Controller\AbstractActionController', 'dispatch', function($e) {
$controller = $e->getTarget();
if (something.....) {
$controller->plugin('redirect')->toRoute('yourroute');
}
}, 100);
}
The page isn't redirecting properly on error
public function onBootstrap($e) {
$e->getApplication()->getEventManager()->getSharedManager()->attach('Zend\Mvc\Controller\AbstractActionController', 'dispatch', function($e) {
if(someCondition==true) {
$controller->plugin('redirect')->toRoute('myroute');
}
}
Can you try this.
$front = Zend_Controller_Front::getInstance();
$response = new Zend_Controller_Response_Http();
$response->setRedirect('/profile');
$front->setResponse($response);

Symfony 2 load different template depending on user agent properties

Is it possible (and how) to
determine if a user is using a mobile device
force symfony 2 to load different template in that case
(and fall back the default html template)
What id like to do is, to load different templates without modifying any controller.
UPDATE
It wasn't the detection part the real issue here, it's really nothing to do with symfony. It can be done (load different template) on a controller level:
public function indexAction()
{
$format = $this->isMobile() ? 'mob' : 'html';
return $this->render('AcmeBlogBundle:Blog:index.'.$format.'.twig');
}
But can it be done globally? Like a service, or something that execute before every request, and make changes in the templating rules.
Ok, so I don't have a full solution but a little more than where to look for one :)
You can specify loaders (services) for templating item in app/config/config.yml
framework:
esi: { enabled: true }
#translator: { fallback: %locale% }
secret: %secret%
router:
resource: "%kernel.root_dir%/config/routing.yml"
strict_requirements: %kernel.debug%
form: true
csrf_protection: true
validation: { enable_annotations: true }
templating:
engines:
- twig
loaders: [moby.loader]
default_locale: %locale%
trust_proxy_headers: false
session: ~
Then define the mentioned loader service:
services:
moby.loader:
class: Acme\AppBundle\Twig\Loader\MobyFilesystemLoader
arguments: ["#templating.locator", "#service_container"]
After that define your loader service class:
namespace Acme\AppBundle\Twig\Loader;
use Symfony\Bundle\FrameworkBundle\Templating\Loader\FilesystemLoader;
use Symfony\Component\Templating\Storage\FileStorage;
class MobyFilesystemLoader extends FilesystemLoader
{
protected $container;
public function __construct($templatePathPatterns, $container)
{
parent::__construct($templatePathPatterns);
$this->container = $container;
}
public function load(\Symfony\Component\Templating\TemplateReferenceInterface $template)
{
// Here you can filter what you actually want to change from html
// to mob format
// ->get('controller') returns the name of a controller
// ->get('name') returns the name of the template
if($template->get('bundle') == 'AcmeAppBundle')
{
$request = $this->container->get('request');
$format = $this->isMobile($request) ? 'mob' : 'html';
$template->set('format', $format);
}
try {
$file = $this->locator->locate($template);
} catch (\InvalidArgumentException $e) {
return false;
}
return new FileStorage($file);
}
/**
* Implement your check to see if request is made from mobile platform
*/
private function isMobile($request)
{
return true;
}
}
As you can see this isn't the full solution, but I hope that this, at least, points you to the right direction.
EDIT: Just found out that there is a bundle with mobile detection capabilities, with custom twig engine that renders template file depending on a device that sent request
ZenstruckMobileBundle, although I never used it so... :)
Well, you can use LiipThemeBundle.
You can utilize kernel.view event listener. This event comes to action when controller returns no response, only data. You can set reponse according to user agent property. For example
In your controller,
public function indexAction()
{
$data = ... //data prepared for view
$data['template_name'] = "AcmeBlogBundle:Blog:index";
return $data;
}
And the in your kernel.view event listener,
<?php
namespace Your\Namespace;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Templating\EngineInterface;
Class ViewListener
{
/**
* #var EngineInterface
*/
private $templating;
public function __construct(EngineInterface $templating)
{
$this->templating = $templating;
}
public function onKernelView(GetResponseForControllerResultEvent $event)
{
$data = $event->getControllerResult(); //result returned by the controller
$templateName = $data['template_name'];
$format = $this->isMobile() ? 'mob' : 'html'; //isMobile() method may come from a injected service
$response = $this->templating->renderResponse($templateName . "." . $format . "twig", $data);
$event->setResponse($response);
}
}
Service definition,
your_view_listener.listener:
class: FQCN\Of\Listener\Class
arguments: [#templating]
tags:
- { name: kernel.event_listener, event: kernel.view, method: onKernelView }
This is what did the trick for me in Symfony 2.0:
Override twig.loader service so we can set our custom class:
twig.loader:
class: Acme\AppBundle\TwigLoader\MobileFilesystemLoader
arguments:
locator: "#templating.locator"
parser: "#templating.name_parser"
And create our custom class, that just sets "mob" format to the templates in case the client is a mobile device:
namespace Acme\AppBundle\TwigLoader;
use Symfony\Bundle\TwigBundle\Loader\FilesystemLoader;
class MobileFilesystemLoader extends FilesystemLoader
{
public function findTemplate($template)
{
if ($this->isMobile()) {
$template->set('format', 'mob');
}
return parent::findTemplate($template);
}
private function isMobile()
{
//do whatever to detect it
}
}
I would suggest that this is not best handled by the controller but by CSS media queries, and serving a separate stylesheet to different classes of devices based on the results of that CSS media query.
A good intro here:
http://www.adobe.com/devnet/dreamweaver/articles/introducing-media-queries.html
and I would try reading http://www.abookapart.com/products/responsive-web-design in great detail. Some thinking has been done since the book was published, but it will get you headed the right direction.
From my experiences, you can but by specifying a format in the first place - check these docs, they may be able to assist you
I think is nothing to do with symfony. Templates are for the VIEW. You may achieve this by using different CSS for the same template to get different layout (template). I am using jQuery and CSS to handle different devices. You may want to look at some source code of the UI from http://themeforest.net/; specifically this template. This is one handles different device.
Alternative: https://github.com/suncat2000/MobileDetectBundle
I found it quite good compared to https://github.com/kbond/ZenstruckMobileBundle and https://github.com/liip/LiipThemeBundle

Categories