Symfony 3 Rediriger Toutes Les Routes Vers La Version Locale Actuelle
je travaille sur une application symfony où mon but est peu importe quelle page l'utilisateur est sur elle va naviguer vers la version locale de la page.
par exemple, si l'utilisateur navigue vers " / "la page d'accueil, il redirigera vers"/ en / "
S'ils se trouvent sur la page "/admin", ils redirigeront vers "/en/admin" , de telle sorte que la propriété _locale
soit définie à partir de la route.
il doit également déterminez la locale s'ils visitent /admin à partir du navigateur des utilisateurs puisqu'aucune locale n'a été déterminée de sorte qu'il sait à quelle page rediriger.
actuellement mon contrôleur par défaut ressemble à ci-dessous depuis que je teste. J'utilise le mode dev & profiler pour tester que les traductions fonctionnent correctement.
<?php
namespace AppBundleController;
use SensioBundleFrameworkExtraBundleConfigurationRoute;
use SymfonyBundleFrameworkBundleControllerController;
use SymfonyComponentHttpFoundationRequest;
class DefaultController extends Controller
{
/**
* @Route("/", name="homepage")
* @Route("/{_locale}/", name="homepage_locale")
*/
public function indexAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
}
cette méthode actuelle maintiendra l'utilisateur à " / " si y naviguer, mais je veux l'avoir rediriger vers "/en/". Cela devrait fonctionner pour d'autres pages, comme /admin, ou /chemin/pathagain/article1 (/fr/admin /fr/chemin/pathagain/article1)
Comment faire?
références j'ai lu qui n'a pas aidé:
Symfony2 Utiliser les paramètres régionaux par défaut dans le service de routage (une URL pour une seule langue)
Symfony2 locale par défaut dans le routage
:: mise à jour::
je n'ai pas résolu mon problème mais j'ai réussi à proximité ainsi que appris quelques astuces pour être plus efficace.
DefaultController.php
<?php
namespace AppBundleController;
use SensioBundleFrameworkExtraBundleConfigurationRoute;
use SymfonyBundleFrameworkBundleControllerController;
use SymfonyComponentHttpFoundationRequest;
class DefaultController extends Controller
{
/**
* @Route("/", name="home", defaults={"_locale"="en"}, requirements={"_locale" = "%app.locales%"})
* @Route("/{_locale}/", name="home_locale", requirements={"_locale" = "%app.locales%"})
*/
public function indexAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
/**
* @Route("/admin", name="admin", defaults={"_locale"="en"}, requirements={"_locale" = "%app.locales%"})
* @Route("/{_locale}/admin", name="admin_locale", requirements={"_locale" = "%app.locales%"})
*/
public function adminAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
}
?>
Config.yml
imports:
- { resource: parameters.yml }
- { resource: security.yml }
- { resource: services.yml }
# Put parameters here that don't need to change on each machine where the app is deployed
# http://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
locale: en
app.locales: en|es|zh
framework:
#esi: ~
translator: { fallbacks: ["%locale%"] }
secret: "%secret%"
router:
resource: "%kernel.root_dir%/config/routing.yml"
strict_requirements: ~
form: ~
csrf_protection: ~
validation: { enable_annotations: true }
#serializer: { enable_annotations: true }
templating:
engines: ['twig']
#assets_version: SomeVersionScheme
default_locale: "%locale%"
trusted_hosts: ~
trusted_proxies: ~
session:
# handler_id set to null will use default session handler from php.ini
handler_id: ~
save_path: "%kernel.root_dir%/../var/sessions/%kernel.environment%"
fragments: ~
http_method_override: true
assets: ~
# Twig Configuration
twig:
debug: "%kernel.debug%"
strict_variables: "%kernel.debug%"
# Doctrine Configuration
doctrine:
dbal:
driver: pdo_mysql
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
charset: UTF8
# if using pdo_sqlite as your database driver:
# 1. add the path in parameters.yml
# e.g. database_path: "%kernel.root_dir%/data/data.db3"
# 2. Uncomment database_path in parameters.yml.dist
# 3. Uncomment next line:
# path: "%database_path%"
orm:
auto_generate_proxy_classes: "%kernel.debug%"
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
# Swiftmailer Configuration
swiftmailer:
transport: "%mailer_transport%"
host: "%mailer_host%"
username: "%mailer_user%"
password: "%mailer_password%"
spool: { type: memory }
Avis en vertu de l'paramètres de la valeur app.locales: en|es|zh
. Ce est maintenant une valeur à laquelle je peux faire référence chaque fois que je crée mes routes si je planifie de supporter plus de locales dans le futur ce que je fais. Ces itinéraires sont anglais, espagnol, chinois dans cet ordre pour les curieux. Dans le DefaultController des annotations, le "%app.locales%"
est la partie qui renvoie au paramètre de configuration.
le problème avec ma méthode actuelle va à / admin par exemple ne redirige pas l'utilisateur vers / {browsers locale} / admin, ce qui serait la solution la plus élégante pour garder tout organisé... mais au moins, les itinéraires de travail. Je cherche toujours une meilleure solution.
****mise à Jour****
je pense que j'ai peut - être trouvé la réponse ici comme la réponse en bas donnée ( ajouter locale et les exigences à toutes les routes-Symfony2 ), la réponse par Athlan. Juste pas sûr comment mettre en œuvre ceci dans symfony 3 que ses directions n'étaient pas assez claires pour moi.
je pense que cet article pourrait aider aussi ( http://symfony.com/doc/current/components/event_dispatcher/introduction.html )
4 réponses
après 12 heures d'étude, j'ai finalement trouvé une solution acceptable. Veuillez publier des versions révisées de cette solution si vous pouvez la rendre plus efficace.
certaines choses à noter, ma solution est particulière à mon besoin. Ce qu'il fait est de forcer N'importe quelle URL à aller à une version localisée si elle existe.
cela nécessite que certaines conventions soient suivies lorsque vous créez des routes.
DefaultController.php
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
/**
* @Route("/{_locale}/", name="home_locale", requirements={"_locale" = "%app.locales%"})
*/
public function indexAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
/**
* @Route("/{_locale}/admin", name="admin_locale", requirements={"_locale" = "%app.locales%"})
*/
public function adminAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
}
?>
remarquez que les deux routes commencent toujours par"/{_locale}/". Pour que cela fonctionne tous les itinéraires dans votre projet doit avoir cela. Tu mets juste le vrai nom de la route après. Pour moi, j'étais d'accord avec ce scénario. Vous pouvez modifier ma solution adaptée à vos besoins assez facilement.
la première étape consiste à créer une écoute sur le httpKernal pour intercepter les requêtes avant qu'elles ne se dirigent vers les routeurs pour les rendre.
LocaleRewriteListener.php
<?php
//src/AppBundle/EventListener/LocaleRewriteListener.php
namespace AppBundle\EventListener;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\RouteCollection;
class LocaleRewriteListener implements EventSubscriberInterface
{
/**
* @var Symfony\Component\Routing\RouterInterface
*/
private $router;
/**
* @var routeCollection \Symfony\Component\Routing\RouteCollection
*/
private $routeCollection;
/**
* @var string
*/
private $defaultLocale;
/**
* @var array
*/
private $supportedLocales;
/**
* @var string
*/
private $localeRouteParam;
public function __construct(RouterInterface $router, $defaultLocale = 'en', array $supportedLocales = array('en'), $localeRouteParam = '_locale')
{
$this->router = $router;
$this->routeCollection = $router->getRouteCollection();
$this->defaultLocale = $defaultLocale;
$this->supportedLocales = $supportedLocales;
$this->localeRouteParam = $localeRouteParam;
}
public function isLocaleSupported($locale)
{
return in_array($locale, $this->supportedLocales);
}
public function onKernelRequest(GetResponseEvent $event)
{
//GOAL:
// Redirect all incoming requests to their /locale/route equivlent as long as the route will exists when we do so.
// Do nothing if it already has /locale/ in the route to prevent redirect loops
$request = $event->getRequest();
$path = $request->getPathInfo();
$route_exists = false; //by default assume route does not exist.
foreach($this->routeCollection as $routeObject){
$routePath = $routeObject->getPath();
if($routePath == "/{_locale}".$path){
$route_exists = true;
break;
}
}
//If the route does indeed exist then lets redirect there.
if($route_exists == true){
//Get the locale from the users browser.
$locale = $request->getPreferredLanguage();
//If no locale from browser or locale not in list of known locales supported then set to defaultLocale set in config.yml
if($locale=="" || $this->isLocaleSupported($locale)==false){
$locale = $request->getDefaultLocale();
}
$event->setResponse(new RedirectResponse("/".$locale.$path));
}
//Otherwise do nothing and continue on~
}
public static function getSubscribedEvents()
{
return array(
// must be registered before the default Locale listener
KernelEvents::REQUEST => array(array('onKernelRequest', 17)),
);
}
}
enfin vous définissez les services.yml pour démarrer l'auditeur.
Services.yml
# Learn more about services, parameters and containers at
# http://symfony.com/doc/current/book/service_container.html
parameters:
# parameter_name: value
services:
# service_name:
# class: AppBundle\Directory\ClassName
# arguments: ["@another_service_name", "plain_value", "%parameter_name%"]
appBundle.eventListeners.localeRewriteListener:
class: AppBundle\EventListener\LocaleRewriteListener
arguments: ["@router", "%kernel.default_locale%", "%locale_supported%"]
tags:
- { name: kernel.event_subscriber }
dans la config.yml vous voulez ajouter ce qui suit sous Paramètres:
config.yml
parameters:
locale: en
app.locales: en|es|zh
locale_supported: ['en','es','zh']
je voulais qu'il n'y ait que un endroit que vous définissez les localesmais j'ai fini par avoir à faire 2...mais au moins ils sont au même endroit si facile à changer.
app.les locales sont utilisées dans le controller par défaut (requirements={"_locale" = "%app.locales%"})
et locale_supported est utilisé dans le LocaleRewriteListener. S'il détecte une locale qui n'est pas dans la liste, il se replie sur les paramètres régionaux par défaut, qui dans ce cas est la valeur de paramètres régionaux:fr.
app.locales est agréable avec la commande requirements parce qu'elle cause a 404 pour toutes les locales qui ne correspondent pas.
Si vous utilisez des formulaires et d'avoir une connexion, vous devez faire ce qui suit pour votre sécurité.yml
de Sécurité.yml
# To get started with security, check out the documentation:
# http://symfony.com/doc/current/book/security.html
security:
encoders:
Symfony\Component\Security\Core\User\User:
algorithm: bcrypt
cost: 12
AppBundle\Entity\User:
algorithm: bcrypt
cost: 12
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
# http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
database:
entity: { class: AppBundle:User }
#property: username
# if you're using multiple entity managers
# manager_name: customer
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
anonymous: true
form_login:
check_path: login_check
login_path: login_route
provider: database
csrf_token_generator: security.csrf.token_manager
remember_me:
secret: '%secret%'
lifetime: 604800 # 1 week in seconds
path: /
httponly: false
#httponly false does make this vulnerable in XSS attack, but I will make sure that is not possible.
logout:
path: /logout
target: /
access_control:
# require ROLE_ADMIN for /admin*
#- { path: ^/login, roles: ROLE_ADMIN }
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/(.*?)/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_USER }
le changement important à noter ici est que (.*?)/login
s'authentifiera de manière anonyme afin que vos utilisateurs puissent toujours se connecter. Cela signifie que les routes, comme..dogdoghere / login pourrait déclencher, mais les exigences que je vais montrez-vous peu de temps sur les routes de connexion empêchent cela et lancera des erreurs 404. J'aime cette solution avec le (.*?)
versus [a-z]{2}
incase vous vouliez utiliser en_US type locales.
SecurityController.php
<?php
// src/AppBundle/Controller/SecurityController.php
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class SecurityController extends Controller
{
/**
* @Route("{_locale}/login", name="login_route", defaults={"_locale"="en"}, requirements={"_locale" = "%app.locales%"})
*/
public function loginAction(Request $request)
{
$authenticationUtils = $this->get('security.authentication_utils');
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render(
'security/login.html.twig',
array(
// last username entered by the user
'last_username' => $lastUsername,
'error' => $error,
)
);
}
/**
* @Route("/{_locale}/login_check", name="login_check", defaults={"_locale"="en"}, requirements={"_locale" = "%app.locales%"})
*/
public function loginCheckAction()
{
// this controller will not be executed,
// as the route is handled by the Security system
}
/**
* @Route("/logout", name="logout")
*/
public function logoutAction()
{
}
}
?>
notez que même ces chemins utilisent {_locale} à l'avant. J'aime cela cependant pour que je puisse donner des logins personnalisés pour différentes locales. Il suffit de garder cela à l'esprit. La seule voie qui n'a pas besoin de l' locale est logout ce qui fonctionne très bien puisque c'est vraiment seulement une route d'interception pour le système de sécurité. Remarquez aussi qu'il utilise les exigences qui sont définies à partir de la configuration.yml, donc vous n'avez qu'à l'éditer en un seul endroit pour toutes les routes à travers vos projets.
Espérons que cela aide quelqu'un qui essaie de faire ce que je faisais!
NOTE:: pour le tester facilement, j'utilise L'extension' Quick Language Switcher ' pour Google Chrome, qui modifie la version accept-language l'en-tête sur toutes les demandes.
Je n'ai pas assez de réputation pour ajouter un commentaire à la bonne solution. Donc j'ajoute une nouvelle réponse
vous pouvez ajouter" prefix: /{_locale} " à app/config/routing.yml comme ceci:
app:
resource: "@AppBundle/Controller/"
type: annotation
prefix: /{_locale}
donc vous n'avez pas besoin de l'ajouter à chaque route à chaque action. Pour les étapes suivantes. Merci beaucoup c'est parfait.
fonction finale smallResumeOfResearching($localeRewrite, $avis = 'mon humble avis') :)
-
La méthode, fournies par le monsieur. Joseph génial de travailler avec des voies comme /{route_name} ou /, mais pas avec de tels axes /article/slug/autres.
-
si nous utilisons mr.Méthode de Joseph, fournie par https://stackoverflow.com/a/37168304/9451542 , nous allons perdre profiler et débogueur dans le dev mode.
-
si nous voulons une solution plus flexible, la méthode onKernelRequest peut être modifiée comme ceci (grâce à mr. Joseph, grâce à https://stackoverflow.com/a/37168304/9451542 ):
public function onKernelRequest(GetResponseEvent $event) { $pathInfo = $event->getRequest()->getPathinfo(); $baseUrl = $event->getRequest()->getBaseUrl(); $checkLocale = explode('/', ltrim($pathInfo, '/'))[0]; //Or some other logic to detect/provide locale if (($this->isLocaleSupported($checkLocale) == false) && ($this->defaultLocale !== $checkLocale)) { if ($this->isProfilerRoute($checkLocale) == false) { $locale = $this->defaultLocale; $event->setResponse(new RedirectResponse($baseUrl . '/' . $locale . $pathInfo)); } /* Or with matcher: try { //Try to match the path with the locale prefix $this->matcher->match('/' . $locale . $pathInfo); //$event->setResponse(new RedirectResponse($baseUrl . '/' . $locale . $pathInfo)); } catch (\Symfony\Component\Routing\Exception\ResourceNotFoundException $e) { } catch (\Symfony\Component\Routing\Exception\MethodNotAllowedException $e) { } */ } }
note: $this - > profilerRoutes = array ('_profiler', '_wdt', '_error');
- merci à Susana Santos pour pointer du doigt la méthode simple de configuration:)
petite amélioration pour Symfony 3.4:
-
assurez-vous que getSubscribedEvents() enregistrera LocaleRewriteListener avant RouterListener::onKernelRequest et avant LocaleListener::onKernelRequest. L'entier 17 doit être plus grand que RouterListener::onkernelrequest priotity. Sinon, vous aurez obtenu 404.
bin/console de debug:événement dispatcher
-
Service définition dans service.yml doit être (dépend de la configuration Symfony):
AppBundle\EventListener\LocaleRewriteListener: arguments: ['@routeur', '%noyau.default_locale%', '%locale_supported%'] balise: - { name: kernel.event_subscriber, de l'événement: le noyau.demande }