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.

enter image description here

<?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
demandé sur Community 2016-01-11 00:37:52

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.

8
répondu Joseph Astrahan 2016-01-11 11:53:58

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.

7
répondu Susana Santos 2016-08-16 16:36:17

fonction finale smallResumeOfResearching($localeRewrite, $avis = 'mon humble avis') :)

  1. 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.

  2. 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.

  3. 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');

  4. merci à Susana Santos pour pointer du doigt la méthode simple de configuration:)
2
répondu Vitalijus Trainys 2018-04-19 20:19:29

petite amélioration pour Symfony 3.4:

  1. 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

  2. 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 }

1
répondu Vitalijus Trainys 2018-04-17 19:06:23