php - symfony2 - tutoriales symfony 3
Symfony 3 redirige todas las rutas a la versiĆ³n de configuraciĆ³n regional actual (2)
Estoy trabajando en una aplicación de Symfony en la que mi objetivo es que, independientemente de la página en la que se encuentre el usuario, vaya a la versión de la página de la configuración regional.
Por ejemplo, si el usuario navega hacia "/" la página de inicio, se redirigirá a "/ en /"
Si están en la página "/ admin" se redireccionará a "/ en / admin" , de tal manera que la propiedad _locale
se establece desde la ruta.
También necesita determinar la configuración regional si visitan / admin desde el navegador de los usuarios, ya que no se determinó la configuración regional para saber a qué página redirigir.
Actualmente mi controlador predeterminado se ve a continuación desde que estoy probando. Estoy usando el modo de desarrollo y el perfil para probar que las traducciones funcionan correctamente.
<?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("/", 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
]);
}
}
Este método actual mantendrá al usuario en "/" si navega allí, pero quiero que lo redireccione a "/ en /". Esto debería funcionar también para otras páginas, como / admin, o / somepath / pathagain / article1 (/ en / admin, / en / somepath / pathagain / article1)
¿Cómo haría esto?
Referencias que he leído que no ayudaron:
Symfony2 Usar configuración regional predeterminada en el enrutamiento (una URL para un idioma)
Symfony2 configuración regional predeterminada en el enrutamiento
::Actualizar::
No he resuelto mi problema pero me he acercado y he aprendido algunos trucos para ser más eficiente.
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("/", 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 }
Observe bajo parámetros el valor app.locales: en|es|zh
. Este es ahora un valor al que puedo hacer referencia cada vez que creo mis rutas si planeo admitir más configuraciones regionales en el futuro, lo cual hago. Esas rutas son inglesas, españolas, chinas en ese orden para los curiosos. En DefaultController en las anotaciones, "%app.locales%"
es la parte que hace referencia al parámetro de configuración.
El problema con mi método actual es / admin, por ejemplo, no redirecciona al usuario a / {navegadores locale} / admin, que sería la solución más elegante para mantener todo organizado ... pero al menos las rutas funcionan. Todavía estoy buscando una mejor solución.
****Actualizar****
Creo que posiblemente haya encontrado la respuesta aquí como la respuesta más baja dada ( Agregue la configuración regional y los requisitos para todas las rutas - Symfony2 ), la respuesta de Athlan. Simplemente no estoy seguro de cómo implementar esto en Symfony 3 ya que sus instrucciones no eran lo suficientemente claras para mí.
Creo que este artículo también podría ser útil ( http://symfony.com/doc/current/components/event_dispatcher/introduction.html )
Después de 12 horas de investigar esto, finalmente encontré una solución aceptable. Por favor, publique versiones revisadas de esta solución si puede hacerlo más eficiente.
Algunas cosas a tener en cuenta, mi solución es particular a mi necesidad. Lo que hace es forzar cualquier URL para ir a una versión localizada si existe.
Esto requiere algunas convenciones que se deben seguir cuando crea rutas.
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
]);
}
}
?>
Tenga en cuenta que ambas rutas siempre comienzan con "/ {_ locale} /". Para que esto funcione, todas las rutas de tu proyecto deben tener esto. Usted acaba de poner el nombre de la ruta real después. Para mí, estaba bien con este escenario. Puede modificar mi solución para satisfacer sus necesidades con la suficiente facilidad.
El primer paso es crear una escucha en httpKernal para interceptar las solicitudes antes de que vayan a los enrutadores para procesarlas.
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)),
);
}
}
Finalmente, configura services.yml para iniciar el escucha.
Servicios.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 }
También en config.yml deseará agregar lo siguiente debajo de los parámetros:
config.yml
parameters:
locale: en
app.locales: en|es|zh
locale_supported: [''en'',''es'',''zh'']
Quería que hubiera un solo lugar donde definieras las configuraciones regionales, pero terminé teniendo que hacer 2 ... pero al menos están en el mismo lugar y son tan fáciles de cambiar.
app.locales se usa en el controlador predeterminado (requirements={"_locale" = "%app.locales%"})
y locale_supported se usa en LocaleRewriteListener. Si detecta una configuración regional que no está en la lista, recurrirá a la configuración regional predeterminada, que en este caso es el valor de la configuración regional: en.
app.locales es agradable con el comando de requisitos porque generará un 404 para cualquier configuración regional que no coincida.
Si está utilizando formularios y tiene un inicio de sesión, deberá hacer lo siguiente a su security.yml
Security.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 }
El cambio importante a tener en cuenta aquí es que (.*?)/login
se autenticará de forma anónima para que sus usuarios aún puedan iniciar sesión. Esto significa que las rutas como ...dogdoghere / login pueden activarse, pero los requisitos que le mostraré en breve en las rutas de inicio de sesión evitan esto y arrojarán errores 404. Me gusta esta solución con el (.*?)
Frente a [az]{2}
caso de que desee utilizar locales en_US.
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()
{
}
}
?>
Tenga en cuenta que incluso estas rutas usan {_locale} al frente. Sin embargo, esto me gusta, así que puedo dar acceso personalizado a diferentes configuraciones regionales. Solo ten eso en cuenta. La única ruta que no necesita la configuración regional es la desconexión, que funciona bien, ya que realmente es solo una ruta de interceptación para el sistema de seguridad. También tenga en cuenta que utiliza los requisitos que se establecen desde config.yml, por lo que solo debe editarlos en un solo lugar para todas las rutas en sus proyectos.
Espero que esto ayude a alguien a tratar de hacer lo que estaba haciendo!
NOTA :: Para probar esto fácilmente, uso la extensión ''Quick Language Switcher'' para Google Chrome, que cambia el encabezado accept-language en todas las solicitudes.
No tengo suficiente reputación para agregar un comentario a la solución correcta. Así que estoy agregando una nueva respuesta
Puede agregar "prefix: / {_ locale}" en app / config / routing.yml de esta manera:
app:
resource: "@AppBundle/Controller/"
type: annotation
prefix: /{_locale}
Por lo tanto, no es necesario que lo agregue a todas las rutas de cada acción. Para los siguientes pasos. Muchas gracias, es perfecto.