Symfony2.2 : default_locale always applying in twig translations - symfony

I'm having a strange issue with Symfony2.2. I have a project using two languages : en/fr. So I create as usual (like Symfony2.0) two translation files "messages.en.yml" and "messages.fr.yml" in Ressources/Views/translations/. But Translations in twig could not change even if we set the request object and the locale session. Translation is always set by the default_locale (config.php).
Example : if default_locale = en, all my website (in twig) is translated in en, even if i set the _locale object in fr (request and session). Of course if I manually change the default_locale to fr the website is naturally in fr...
However, _locale session works but I don't know if locale request works, and of course translation works in controllers too...
There is my files :
config.yml:
framework:
#esi: ~
translator: { fallback: %locale% } # = en
# ...
default_locale: %locale% # = en
Controller :
public function indexAction()
{
$this->get('session')->set('_locale', 'fr');
$this->getRequest()->setLocale($lang);
exit($this->getRequest()->getLocale()); // = fr
exit($this->get('translator')->trans('Symfony2 is great')); // = Symfony2 est génial
return $this->render('TestBundle:Controller:test.html.twig');
View :
{% block content %}
<p>lang : {{ app.request.locale }}</p> {#} = "fr", OK{#}
<p>{{ 'Symfony2 is great'|trans }}</p> {#} = "Symfony2 is great", WAIT WHAT?{#}
I must resign myself to force the locale at the beginning of the method controller to have the requested locale (stored in session) like that :
Controller:
if($this->get('session')->get('_locale')){
$lang = $this->get('session')->get('_locale');
$this->getRequest()->setLocale($lang);
}
In other words, I do have a problem with the registration of the request object... Because the last code works well in the controller, and shows well the locale in twig page with app.request.locale, but not the translations... (sorry for my bad english and thanks for helping)

I had the same issue due to the low priority of my event listener. The locale would be overridden by the Translator's TranslatorListener. Increasing the priority of my event listener did the trick for me:
services:
app.locale_listener:
class: AppBundle\EventListener\LocaleListener
tags:
- { name: kernel.event_listener, priority: 11, ... }
Source: https://github.com/symfony/symfony/issues/12878#issuecomment-68628808

Parameter _locale in routing holds your locale value.
Look here on this page
Symfony - Book - Translation - Local and the URL
From Symfony 2.1 they have this kind of logic:
Since you can store the locale of the user in the session, it may be tempting to use the same URL to display a resource in many different languages based on the user's locale. For example, http://www.example.com/contact could show content in English for one user and French for another user. Unfortunately, this violates a fundamental rule of the Web: that a particular URL returns the same resource regardless of the user. To further muddy the problem, which version of the content would be indexed by search engines?
A better policy is to include the locale in the URL. This is fully-supported by the routing system using the special _locale parameter:
Now when you want to sel local, this doesn't work any more
$this->get('session')->set('_locale', 'fr');
You can use request insted of session now but you can not have session-logic with _local from Symfony 2.0 unless you simulate it with event listener on kernel request.

Related

symfony routing, use value from Request as default

I've got the following route definition
my_route:
path: /actual-path/
defaults:
_controller: MyBundle:MyController:detail
id: application_id
requirements:
methods: GET
id: \d+
The controller requires a parameter called $id.
But I don't want to use the $id in the url, I want to use a value that is available in $request->attributes->get('application_id')
There is a listener that will inject two parameters (application_id and application) into the request object as attributes prior to the routing process, so this value is in there. (It would be easy to also inject it into the RequestContext).
Is there a way I can use attributes values from the Request or RequestContext object in my routing as defaults?
Now I could simply do $request->attributes->get('application_id') in my controller. But this controller will be used in several cases. In other cases the $id is to be passed through the url. I find it cleaner to set the id in the routing than build a if-else clause in the controller somewhere.
It appears not to be possible to do "mapping" between variables in the request object and the parameters you need to be required in your route/controller-action. I think this would be a good thing, as it would become quite complex otherwise.
I went with the solution to extend the controller and build in a small switch there.
Basically, if the route specifies id === null, it will do a fallback to application_id that is in the Request object. Otherwise it will use the value provided. I just need to set a requirement on id on the routes that must not use the fallback.
All without running an additional Listener which might be a bit "expensive" in processing time (for each request).
Example of routes:
my_route:
path: /actual-path/
defaults:
_controller: MyBundle:MyController:detail
id: null
requirements:
methods: GET
my_other_route:
path: /other-path/{id}
defaults:
_controller: MyBundle:MyController:detail
requirements:
methods: GET
id: \d+
And how to handle this in your controller:
// fallback to system id
if ($id === null) {
$id = $request->attributes->get('administration_id', null);
}
Because you extend the controller/action, you could also change the name of the parameter in the action (as long as the type does not change).
I did not do this, as I could quite easily put the switch between the provided id and the fallback from the listener in another method.

How to use anchors in Symfony routing?

I have defined a route as followed in my routing.yml file :
route_name:
path: "/dashboard#messages/{id}"
However when I ask Symfony to generate that route, I get :
/dashboard%23messages/12345
How can I skip the encoding part of the route generation? Or how can I escape the # char in the path definition?
PS : Working with a (big) legacy system, I cannot change the urls.
Available from Symfony 3.2.
Support for anchors has been announced for the routing component using the fragment variable :
$this->get('router')->generate('user_settings', ['_fragment' => 'password']);
Will generate an url : /user/settings#password
For more information view the announcement.
You cannot easily - route parts are encoded unconditionally:
$url = strtr(rawurlencode($url), $this->decodedChars);
see at https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Routing/Generator/UrlGenerator.php#L192
Technically you might have extended the class UrlGenerator class and swap them using router.options.generator_class parameter. Then you could override the doGenerate method and replace %23 -> #.
In twig
<a href="{{ path('user_settings', { '_fragment': 'password' }) }}">

Symfony - using twig trans filter on 404 page

I have a 404 error page set up through an event listener triggered by Kernel exceptions:
public function onKernelException(GetResponseForExceptionEvent $event)
{
if ($event->isMasterRequest()) {
$exception = $event->getException();
if ($exception instanceof NotFoundHttpException) {
$response = new Response();
$event->setResponse(
$response->setContent($this->templating->render(
'LandingPageBundle:Error:error404.html.twig',
['welcome_url' => $this->router->generate("welcome")]
))
);
}
}
}
kernel.kernel_exception_listener:
class: S\Project\LandingPageBundle\EventListener\KernelExceptionListener
arguments: [ "#router", "#logger", "#translator", #templating ]
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
This works, but when I try to use the trans filter on the twig error404.html.twig template, it does nothing. My locale is being set in a cookie and read by an event listener to Kernel Requests, so I tried adding the following to onKernelException:
$request = $event->getRequest();
$locale = $request->cookies->get('_locale');
$request->setLocale($locale);
After that, including {{ app.request.locale }} in the template displayed the correct locale, however, the trans filter does not seem to be picking this up.
It seems like my problem may be related to Symfony 2.1 set locale and yet that question does not quite fit my exact problem, and I'm not sure what can be done to fix the problem. Ideally my kernel request listener could trigger before the onKernelException, so that it would properly set the locale beforehand, but currently it seems that the kernel request event is not triggered during a 404. I took a look at http://symfony.com/doc/current/components/http_kernel/introduction.html to better understand Symfony's request sequence, but I'm not really clear on the sequence that happens in a bad request, but it looks like in the case of an exception, it skips most of the request flow and goes straight to the response, and as I recall from http://symfony.com/doc/current/book/translation.html#handling-the-user-s-locale
"Setting the locale using $request->setLocale() in the controller is too late to affect the translator. Either set the locale via a listener (like above), the URL (see next) or call setLocale() directly on the translator service."
Is there a way to use the trans filter on a twig templated 404 page?
Try to inject the #translator and use its method setLocale.
['welcome_url' => $this->router->generate("welcome")]
And why did you create link in the listener? You should do it in the template using twig function called path.

Symfony2 multi-level dynamic router

I have a current project that has to displays both defined pages with specific entities, what is very easy to manage with Symfony2, and content pages on different layouts, what is - I guess - a bit less common.
I get in trouble trying to build the routing system.
For instance, if I have to display a page with some news,
I would like to update the router of my bundle with a new route like :
my_bundle_news_page:
pattern: /news
defaults:
_controller: MyBundle:NewsController:indexAction
But how to manage a dynamic router that could have a totally custom URL on many levels ?
Let's imagine I've got a "Page" Entity, that is self-references for an optionnal "parent-child" relation.
I don't think I can just use any config YAML file for this specific routing ?!
my_bundle_custom_page:
pattern: /{slug}
defaults:
_controller: MyBundle:PageController:showAction
This would bind all the first-level pages:
/projects
/about
/contact
/our-project
What about a page that would be displayed with, for instance, a slug like:
/our-project/health
In fact any URL...
/{slug-level1}/{slug-level2}/{slug-level3} etc.
Cause the pages are supposed to change and be updated from webmastering.
I guess the best way would be to have a router that compare the {slug} with a database field (entity property)
I read in the Symfony-CMF doc that it is possible to write a service based a route provider:
namespace MyBundle\Routing;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route as SymfonyRoute;
use MyBundle\Entity\PageRepository;
class RouteProvider extends PageRepository {
public function findPageBySlug($slug)
{
// Find a page by slug property
$page = $this->findOneBySlug($slug);
if (!$page) {
// Maybe any custom Exception
throw $this->createNotFoundException('The page you are looking for does not exists.');
}
$pattern = $page->getUrl(); // e.g. "/first-level/second-level/third-level"
$collection = new RouteCollection();
// create a new Route and set our page as a default
// (so that we can retrieve it from the request)
$route = new SymfonyRoute($pattern, array(
'page' => $page,
));
// add the route to the RouteCollection using a unique ID as the key.
$collection->add('page_'.uniqid(), $route);
return $collection;
}
}
But how to set it up as a service ? Are there some requirements ?
How could this kind of thing work, does it add a route to the RouteCollection when request is called ?
And will I be able to bind any route in this way ?
EDIT : services.yml of my bundle
parameters:
cmf_routing.matcher.dummy_collection.class: Symfony\Component\Routing\RouteCollection
cmf_routing.matcher.dummy_context.class: Symfony\Component\Routing\RequestContext
cmf_routing.generator.class: Symfony\Cmf\Bundle\RoutingBundle\Routing\ContentAwareGenerator
cmf_routing.nested_matcher.class: Symfony\Cmf\Component\Routing\NestedMatcher\NestedMatcher
cmf_routing.url_matcher.class: Symfony\Cmf\Component\Routing\NestedMatcher\NestedMatcher
fsbcms.chain_router.class: Symfony\Cmf\Component\Routing\ChainRouter
fsbcms.route_provider.class: FSB\CMSBundle\Routing\RouteProvider
fsbcms.dynamic_router.class: Symfony\Cmf\Component\Routing\DynamicRouter
fsbcms.route_entity.class: null
services:
fsbcms.router:
class: %fsbcms.chain_router.class%
arguments:
- "#logger"
calls:
- [setContext, ["router.request_context"]]
fsbcms.route_provider:
class: "%fsbcms.route_provider.class%"
arguments:
- "#doctrine"
cmf_routing.matcher.dummy_collection:
class: "%cmf_routing.matcher.dummy_collection.class%"
public: "false"
cmf_routing.matcher.dummy_context:
class: "%cmf_routing.matcher.dummy_context.class%"
public: false
cmf_routing.generator:
class: "%cmf_routing.generator.class%"
arguments:
- "#fsbcms.route_provider"
- "#logger"
calls:
- [setContainer, ["service_container"]]
- [setContentRepository, ["cmf_routing.content_repository"]]
cmf_routing.url_matcher:
class: "%cmf_routing.url_matcher.class%"
arguments: ["#cmf_routing.matcher.dummy_collection", "#cmf_routing.matcher.dummy_context"]
cmf_routing.nested_matcher:
class: "%cmf_routing.nested_matcher.class%"
arguments: ["#fsbcms.route_provider"]
calls:
- [setFinalMatcher, ["cmf_routing.url_matcher"]]
fsbcms.dynamic_router:
class: "%fsbcms.dynamic_router.class%"
arguments:
- "#router.request_context"
- "#cmf_routing.nested_matcher"
- "#cmf_routing.generator"
tags:
- { name: router, priority: 300 }
I suggest taking a look at the Symfony CMF routing component and the CmfRoutingBundle (to implement the component in symfony).
The Routing component uses a chain router, which is irrelevant for this question but it's good to know. The chain router chains over a queue of routers. The component provides a DynamicRouter that uses a NestedMatcher. That's exactly what you want.
The NestedMatcher uses a Route provider to get the routes from a dynamic source (e.g. a database). You are showing an example of a Route provider in your question.
Furthermore, it uses a FinalMatcher to match the route. You can just pass an instance of Symfony\Cmf\Component\Routing\NestedMatcher\UrlMatcher, as you are doing not too difficult things.
Take a look at the docs of the RoutingBundle to learn how to activate the chain router and then create a route provider which loads the routes, make a service:
acme_routing.route_provider:
class: Acme\RoutingBundle\Provider\DoctrineOrmProvider
arguments: ["#doctrine"]
Now, you can create a NestedMatcher service:
acme_routing.url_matcher:
class: Symfony\Cmf\Component\Routing\NestedMatcher\UrlMatcher
arguments: ["#cmf_routing.matcher.dummy_collection", "#cmf_routing.matcher.dummy_context"]
acme_routing.nested_matcher:
class: Symfony\Cmf\Component\Routing\NestedMatcher
arguments: ["#acme_routing.route_provider"]
calls:
- [setFinalMatcher, ["acme_routing.url_matcher"]]
Now, register the DynamicRouter and put it in the chain:
acme_routing.dynamic_router:
class: Symfony\Cmf\Component\Routing\DynamicRouter
arguments:
- "#router.request_context"
- "#acme_routing.nested_matcher"
- "#cmf_routing.generator"
tags:
- { name: router, priority: 300 }
Now, it should work and should load the routes from the database and match them against the request.

Symfony2 Set _locale in home page base on browser's configuration

My local works everywhere on my website but I cant tell the application the set the proper local on the root of the application.
For exemple :
If my website is http://mycompany.com I want that, when I enter this address the application guess what is my local and set it at the end of the url
http://mycompany.com/en if the local exist on my application. Here en or fr and if not the default en
For now when i go to home page I always get english version but my browser is set in french
routing.yml :
_welcome:
pattern: /{_locale}
defaults: { _controller: MyBundle:Default:landing, _locale: en }
requirements:
_locale: en|fr
See this question asked a few days ago and take a look at the first method in the language listener. It does exactly what you need. And take care of the right settings in config.yml
With the help of many other questions related to locale and symfony2 I finally get want I want ! I guess it is not the perfect answer but at least it works !
My landing action is defined in my routing as _welcome route. It is this action that is loaded when I go to the url's root.
/**
* #Route("/landing")
* #Template()
*/
public function landingAction() {
$localeAvailable = array('en', 'fr');
//user language
$localeUser = substr($this->getRequest()->getPreferredLanguage(), 0, 2);
//application language
$localeApp = $this->getRequest()->attributes->get('_locale');
//Redirect to the good locale page
//only if the locale user is on our manager locale and it is not the current locale of the application
if(in_array($localeUser, $localeAvailable) && $localeApp!=$localeUser){
return $this->redirect($this->generateUrl("_welcome", array('_locale' => $localeUser)));
}
return array();
}

Resources