SlideShare uma empresa Scribd logo
1 de 76
Baixar para ler offline
¿Quién soy?


• Cristina Quintana (@jautu)
• Soy de Córdoba
• Desarrolladora web en Acilia
Repasando
                  conceptos



Internacionalización
           y
     localización
Repasando
                          conceptos

Se podría decir:
          I18n = Contenedor
                  y
           L10n = contenido
Nuestro proyecto de
                     internacionalización

En sus orígenes

Servidores Windows
Tecnología .NET                  Con Acilia
SQL Server
                     Servidores Linux
                     Tecnología Open Source
                     MySQL
Nuestro proyecto de
internacionalización
Nuestro proyecto de
internacionalización
Nuestro proyecto de
internacionalización
Nuestro proyecto de
internacionalización

Grecia
Nuestro proyecto de
internacionalización

Grecia


         Reino Unido
Nuestro proyecto de
internacionalización

Grecia


         Reino Unido


                Turquía
Nuestro proyecto de
internacionalización

Grecia


         Reino Unido


                Turquía
Nuestro proyecto de
internacionalización




              Israel
Nuestro proyecto de
         internacionalización




Israel


                         Israel

                Italia
Nuestro proyecto de
         internacionalización




Israel


                         Israel

                Italia

 E. Árabes
Nuestro proyecto de
                    internacionalización


              <html dir="">

            •  ltr (left-to-right)
            •  rtl (right-to-left)


Por supuesto, cada caso necesita unas css
distintas.
Planificación del
                            proyecto


•  Migración de la base de datos
•  Construcción del backend
•  Construcción del frontend
•  Nueva infraestructura
Planificación del
                             proyecto


•  Migración de la base de datos
•  Construcción del backend
•  Construcción del frontend (sf2)
•  Nueva infraestructura
¿Cómo hicimos el
      frontend?




 Mode translation: On
Flujo de trabajo

Petición


           Controlador



                              Respuesta
              Vista
Flujo de trabajo


_locale


    SI




existe    SI   Procesar          Construir
 ruta           acción             vista
Flujo de trabajo


_locale


    SI




existe    SI   Procesar          Construir
 ruta           acción             vista
Buscando el
                                       país
Utilizamos un listener:

                   ¿Qué hace?

•  Ejecutar sólo en peticiones maestras.
•  Parsear url.
•  Chequear país.
•  Establecer valores predeterminados del
  idioma (l10n).
Buscando el
                                                       país
                          src/bndl/Resources/config/services.xml

<services>
 ...
 <service id="bndl.lang_listener" class="%bndl.lang_listener.class%">
   <tag name="kernel.event_listener" event="kernel.request"
        method="onUrlParse"/>
   <argument type="service" id="router"/>
   <argument type="service" id="doctrine.orm.entity_manager"/>
 </service>
 ...
</services>


                                      Declaración del listener
Buscando el
                                                       país
                                  src/bnd/Listener/LangListener.php
class LanguageListener
{
   public function __construct(RouterInterface $router, EntityManager $em)
   {
     $this->_router = $router;
     $this->_em = $em;
   }

    public function onUrlParse(Event $event)
    {
      ...
    }
}
                                         Contenido del listener
public function onUrlParse(Event $event)
  {
      // Ejecutar la lógica sólo si es una petición maestra
     if ($event->getRequestType() !== SymfonyComponentHttpKernel
HttpKernel::MASTER_REQUEST) {
         return;
     }
     ...

      // Busqueda del código de país en la url
      $parameters = $this->router->match($request->getPathInfo());
      ...
      // Si existe el código del país
      ...
      // Si el código del país no exite
      ...
  }
                                        Contenido del listener
Detección de IP
                                de usuario
Librería Maxmind:
 •  Ofrece una detección más que razonable en
    su versión gratuita (GeoLite)

X-Forwarded-For header:
 •  Al pasar por Varnish, load balancers…. La IP
    original del usuario se pierde.
Necesitamos que nuestra aplicación utilice esa IP

                                   Curiosidades
Detección de IP
                                   de usuario
       SymfonyComponentHttpFoundationRequest.php



// Consulta el parámetro REMOTE_ADDR
$this->request->getClientIp();

// Consulta tanto HTTP_CLIENT_IP
// como HTTP_X_FORWARDED_FOR
$this->request->getClientIp( true );



                                       Curiosidades
Flujo de trabajo


_locale


    SI




existe    SI   Procesar          Construir
 ruta           acción             vista
Flujo de trabajo


_locale


    SI




existe    SI   Procesar          Construir
 ruta           acción             vista
¿Rutas i18n?
¿Qué rutas i18n
                             podemos encontrar?
Peticiones de los usuarios:

  http://natgeotv.com/uk/listings
  http://natgeotv.com/fr/grille
  http://natgeotv.com/nl/weekoverzicht

Acción de esas peticiones:

  Controlador:     Schedule
  Acción:          listings
                                    Análisis del problema
¿Cómo podemos
                               generar esas rutas?
                   src/mybundle/Resources/config/routing.yml
Bndl_listings_uk:
  pattern: /{_locale}/listings
  defaults: { _controller: "Bndl:Schedule:listings" }
  requirements:
     _locale: uk

Bndl_listings_nl:
  pattern: /{_locale}/weekoverzicht
  defaults: { _controller: "Bndl:Schedule:listings" }
  requirements:
    _locale: nl
                         ¿Generación manual de rutas?
¿Cómo podemos
                               generar esas rutas?
                   src/mybundle/Resources/config/routing.yml
Bndl_listings_uk:
  pattern: /{_locale}/listings
  defaults: { _controller: "Bndl:Schedule:listings" }
  requirements:
     _locale: uk
                                            ¿Estamos locos?
Bndl_listings_nl:
  pattern: /{_locale}/weekoverzicht
  defaults: { _controller: "Bndl:Schedule:listings" }
  requirements:
    _locale: nl
                         ¿Generación manual de rutas?
Generación dinámica
                                      de rutas
1.  Definir la ruta genérica:

 Bndl_listings:
   pattern: /{_locale}/listings
   defaults: { _controller: "Bndl:Schedule:listings" }



2.  Modificar el routing para la generación
    automática de dicha ruta para todos los países.

                                            Plan de acción
Generación dinámica
                        de rutas
COUNTRY
id                  URL_WORD_TRANS
name
url                  id
...                  country_id
time_zone            url_word_id
                     value

URL_WORD

id
value

            Un poquito de base de datos
Generación dinámica
                                      de rutas
    COUNTRY
id    url

15 uk                       URL_WORD_TRANS
6     fr        id   country_id   url_word_id   value

25 nl           1    15           7             listings
                2    6            7             grille
URL_WORD
                3    25           7             weekoverzicht
id   value

7    listings

                          Un poquito de base de datos
Generación dinámica
                                      de rutas
    COUNTRY
id    url

15 uk                       URL_WORD_TRANS
6     fr        id   country_id   url_word_id   value

25 nl           1    15           7             listings
                2    6            7             grille
URL_WORD
                3    25           7             weekoverzicht
id   value

7    listings

                          Un poquito de base de datos
¿Qué rutas i18n
                             podemos encontrar?
Peticiones de los usuarios:

  http://natgeotv.com/uk/listings
  http://natgeotv.com/fr/grille
  http://natgeotv.com/nl/weekoverzicht

Acción de esas peticiones:

  Controlador:     Schedule
  Acción:          listings
                                    Análisis del problema
Modificando la
                                   generación de rutas
Interceptar el momento en el que se lee la
  configuración de las rutas:
<services>
   ...
   <service id="bndl.routing.loader.yml"
             class="%bndl.routing.loader.yml.class%" public="false">
       <tag name="routing.loader" />
       <argument type="service" id="service_container" />
       <argument type="service" id="bndl.file_locator" />
  </service>
  <service id="routing.loader.yml" alias="bndl.routing.loader.yml" />
  ...
</services>
                                 Adaptación de la estructura
Modificando la
                                   generación de rutas
Interceptar el momento en el que se lee la
  configuración de las rutas:
<services>
   ...
   <service id="bndl.routing.loader.yml"
             class="%bndl.routing.loader.yml.class%" public="false">
       <tag name="routing.loader" />
       <argument type="service" id="service_container" />
       <argument type="service" id="bndl.file_locator" />
  </service>
  <service id="routing.loader.yml" alias="bndl.routing.loader.yml" />
  ...
</services>
                                 Adaptación de la estructura
Modificando la
                                  generación de rutas
¿Qué hace la clase original?
namespace SymfonyComponentRoutingLoader;

class YamlFileLoader extends FileLoader
{
   protected function parseRoute(RouteCollection $collection, $name,
                                $config, $file)
   {
     ...
     $route = new Route($config['pattern'], $defaults, $requirements,
                         $options);
     $collection->add($name, $route);
   }
}
                                           Aprendiendo de sf2
Modificando la
                                  generación de rutas
Extendemos el servicio original:
use SymfonyComponentRoutingLoaderYamlFileLoader as BaseYamlFileLoader;

class YamlFileLoader extends BaseYamlFileLoader
{
   protected function parseRoute(RouteCollection $collection, $name,
                                  $config, $file)
   {
     $url_words_trans = ...; // Consulta a la tabla url_words
     $country = ...;        // Consulta a la tabla country
      ...
      $route = new I18nRoute(..., $url_words_trans, $country);
      $collection->addCollection($route->getCollection());
   }
}                                     Adaptando la arquitectura
Modificando la
                                        generación de rutas
class I18nRoute {
   public function __construct($original_pattern, $defaults = array(), $requirements =
            array(), $options = array(), $translations = array(), $regions = array()) {
     // Construccion a partir del patrón original, sus equivalentes por país
           $patterns = ...; // Formato: array('_locale' => 'pattern')
     ...
     //Construcción de una ruta para cada patrón nuevo
     foreach ($patterns as $country_url => $i18n_pattern) {
         $requirements['_locale'] = $country_url;
         $this->collection->add($name . '_' . country_url,
            new Route($i18n_pattern, $defaults, $requirements, $options, true));
     }
     $this->collection->add($name, new Route($original_pattern,
         $defaults, $requirements, $options, true));
   }
}
                                        Adaptando la arquitectura
Resultado
Bndl_listings_uk:
  pattern: /{_locale}/listings
  defaults: { _controller: ”Bndl:Schedule:listings" }
  requirements:
     _locale: uk

Bndl_listings_nl:
  pattern: /{_locale}/weekoverzicht
  defaults: { _controller: ”Bndl:Schedule:listings" }
  requirements:
    _locale: nl

Bndl_listings:
  pattern: /{_locale}/listings
  defaults: { _controller: ”Bndl:Schedule:listings" }
                                                Rutas disponibles
Flujo de trabajo


_locale


    SI




existe    SI   Procesar          Construir
 ruta           acción             vista
Flujo de trabajo


_locale


    SI




existe    SI   Procesar          Construir
 ruta           acción             vista
Flujo de trabajo


_locale


    SI




existe    SI   Procesar          Construir
 ruta           acción             vista
¿Vistas i18n?
¿Dónde pueden estar
                               mis vistas?

 app/Resources/Bndl/views

 src/Bndl/Resources/views

¿Qué hacer si tenemos requisitos específicos
 para un país?:

   Llenar el código de
        {% if %}

                            Análisis del problema
¿Dónde pueden estar
                               mis vistas?

 app/Resources/Bndl/views

 src/Bndl/Resources/views

¿Qué hacer si tenemos requisitos específicos
 para un país?:

   Llenar el código de
                            ¡Acaben ya conmigo!
        {% if %}

                            Análisis del problema
¿Dónde pueden estar
                                    mis vistas?
¿Alguna idea mejor?:
•    Se puede utilizar la plantilla que se encuentre primero
     en este nuevo árbol de directorios


     src/Bndl/Resources/views/{_locale}
     src/Bndl/Resources/views/%DIR_BASE%
     src/Bndl/Resources/views



                                    Análisis del problema
Modificando la
                         localización de las vistas
Interceptar el momento cuando crear el árbol de
  directorios:

<services>
   ...
   <service id="bnbl.file_locator" class="%bnbl.file_locator.class%">
       <argument type="service" id="kernel" />
       <argument>%kernel.root_dir%/Resources</argument>
   </service>
   <service id="file_locator" alias="bnbl.file_locator" />
   ...
</services>

                               Adaptación de la estructura
Modificando la
                         localización de las vistas
Interceptar el momento cuando crear el árbol de
  directorios:

<services>
   ...
   <service id="bnbl.file_locator" class="%bnbl.file_locator.class%">
       <argument type="service" id="kernel" />
       <argument>%kernel.root_dir%/Resources</argument>
   </service>
   <service id="file_locator" alias="bnbl.file_locator" />
   ...
</services>

                               Adaptación de la estructura
Modificando la
                       localización de las vistas
¿Qué hace la clase original?:
namespace SymfonyComponentHttpKernelConfig;

class FileLocator extends BaseFileLocator
{
   public function locate($file, $currentPath = null, $first = true)
   {
     ...
     return $this->kernel->locateResource($file, $this->path, $first);
     ...
   }
}
                                 Adaptando la arquitectura
Modificando la
                           localización de las vistas
Extendemos el servicio original:

use SymfonyComponentHttpKernelConfigFileLocator as BaseFileLocator;

class FileLocator extends BaseFileLocator
{
   public function locate($file, $currentPath = null, $first = true)
   {
     ...
         return $this->locateResource($file, $this->path, $first);
     ...
   }
}

                                      Adaptando la arquitectura
Modificando la
                           localización de las vistas
Extendemos el servicio original:

use SymfonyComponentHttpKernelConfigFileLocator as BaseFileLocator;

class FileLocator extends BaseFileLocator
{
   public function locate($file, $currentPath = null, $first = true)
   {
     ...
         return $this->locateResource($file, $this->path, $first);
     ...
   }
}

                                      Adaptando la arquitectura
Modificando la
                             localización de las vistas
class FileLocator extends BaseFileLocator
{
   // Lógica de la clase SymfonyComponentHttpKernelKernel
   public function locateResource($name, $dir = null, $first = true)
   {
       $myDir = ...; // Nombre del directorio para las vistas del país
       foreach ( $bundles as $bundle ) {
           $checkPaths = ...; // Rutas relativas a chequear
           foreach ( $checkPaths as $checkPath) {
             if ( file_exists($file = $checkPath)) { // Verificación de la plantilla }
           }
       }
       ...
   }
}                                         Adaptando la arquitectura
Resultado
El árbol de directorios en el que se chequean la
  existencia de las plantillas es:

 app/Resources/Bndl/views/{_locale}
 app/Resources/Bndl/views/%DIR_BASE%
 app/Resources/Bndl/views

 src/Bndl/Resources/views/{_locale}
 src/Bndl/Resources/views/%DIR_BASE%
 src/Bndl/Resources/views

                    Rutas de directorios disponibles
Resultado


                Hong Kong




Rutas de directorios disponibles
Resultado


                Hong Kong




              Reino Unido




Rutas de directorios disponibles
Resultado


                Hong Kong




               Reino Unido



Rutas de directorios disponibles
¿Quebraderos de cabeza?
Lo que no hay que
                               dejar pasar

•  Establecer la zona horaria correcta.
•  Formatos de fechas.
•  Conversión de texto a mayúscula.
•  Cachear lo no cacheable.
•  Especificar el encoding en la configuración de
   los servicios que acceden a base de datos.
•  ...                     Pequeñas soluciones
Lo que no hay que
                              dejar pasar

Conversión de texto a mayúscula:

     {{ '                   ' | upper }}




                          Pequeñas soluciones
Lo que no hay que
                              dejar pasar

Conversión de texto a mayúscula:

     {{ '                   ' | upper }}


Debería imprimir:




                          Pequeñas soluciones
Lo que no hay que
                               dejar pasar

Conversión de texto a mayúscula:

     {{ '                    ' | upper }}


Debería imprimir:

   ¡PERO NO!        Imprime: SIBER SIRLAR

                           Pequeñas soluciones
Lo que no hay que
                               dejar pasar

Conversión de texto a mayúscula:

     {{ '                    ' | upper }}


Debería imprimir:

   ¡PERO NO!        Imprime: SIBER SIRLAR

                           Pequeñas soluciones
Lo que no hay que
                                                  dejar pasar
Solución: Sobreescribir el método upper de twig.
class TextExtension extends Twig_Extension
{
   public function upper(Twig_Environment $env, $sentence)
   {
     $value = str_replace('ı', 'I', str_replace('i', 'İ', $sentence));

        if ( null !== ( $charset = $env->getCharset() ) ) {
            return mb_strtoupper($value, $charset);
        }
        return strtoupper($value);
    }
}
                                                Pequeñas soluciones
Infraestructura
Load Balancer


            Varnish (n)


Media      Frontend (n)



        Backend (n) | BBDD
Deploy y más deploy
¿Cómo manejamos
                            los entornos?

•  Tenemos múltiples servidores que coordinar y
     monitorizar.
•    Necesitamos una herramienta que actúe sobre
     todos.
•    Nuestra elección Magallanes, que sería como
                  el “Capistrano de PHP”, creado
                  por @andres_montanez, miembro
                  del equipo.

                         Análisis de la situación
Magallanes,
                           ¡hay que probarlo!

•  Posibilidad de configurar múltiples “targets”
•  Posibilidad de crear “pre-tasks”
•  Integración con git
•  Rollback instantáneo
•  Posibilidad de post-tasks
•  Configuración de número de releases a
     archivar.
•    Posibilidad de sobreescribir realeases.

                            ¡Feliz deploy a todos!
Muchas gracias


                     @jautu
           quintana.cano@gmail.com
http://es.linkedin.com/in/cristinaquintanacano


                                    Contacto

Mais conteúdo relacionado

Semelhante a I18n DeSymfony

EasyData: OpenData and easy access
EasyData: OpenData and easy accessEasyData: OpenData and easy access
EasyData: OpenData and easy accessJuan Vazquez Murga
 
Presentacion Ruby on Rails en Universidad Autónoma 2009
Presentacion Ruby on Rails en Universidad Autónoma 2009Presentacion Ruby on Rails en Universidad Autónoma 2009
Presentacion Ruby on Rails en Universidad Autónoma 2009Nelson Rojas Núñez
 
Primeros pasos con neo4j
Primeros pasos con neo4jPrimeros pasos con neo4j
Primeros pasos con neo4jUbaldo Taladriz
 
Presentación Ruby on Rails en Softare Freedom Day 09 Buenos Aires
Presentación Ruby on Rails en Softare Freedom Day 09 Buenos AiresPresentación Ruby on Rails en Softare Freedom Day 09 Buenos Aires
Presentación Ruby on Rails en Softare Freedom Day 09 Buenos Airespeterpunk
 
Introducción a phone gap
Introducción a phone gapIntroducción a phone gap
Introducción a phone gapRodrigo Corral
 
Caixa galicia Enterprise Service Bus
Caixa galicia   Enterprise Service BusCaixa galicia   Enterprise Service Bus
Caixa galicia Enterprise Service BusFélix Mondelo
 
Introduccion al Akelos Php Framework
Introduccion al Akelos Php FrameworkIntroduccion al Akelos Php Framework
Introduccion al Akelos Php FrameworkBermi Ferrer
 
Technology Architect - Coorganizador AWS User Group Palma
Technology Architect - Coorganizador AWS User Group PalmaTechnology Architect - Coorganizador AWS User Group Palma
Technology Architect - Coorganizador AWS User Group PalmaGabriel Fernandez
 
De symfony 2013 dr. jenkins y mr. hyde - slides
De symfony 2013   dr. jenkins y mr. hyde - slidesDe symfony 2013   dr. jenkins y mr. hyde - slides
De symfony 2013 dr. jenkins y mr. hyde - slidesQuique Torras
 
De symfony 2013 dr. jenkins y mr. hyde - slides-842359017
De symfony 2013   dr. jenkins y mr. hyde - slides-842359017De symfony 2013   dr. jenkins y mr. hyde - slides-842359017
De symfony 2013 dr. jenkins y mr. hyde - slides-842359017Eduardo Gulias Davis
 

Semelhante a I18n DeSymfony (20)

R users Galicia 2018
R users Galicia 2018R users Galicia 2018
R users Galicia 2018
 
ASP.NET MVC
ASP.NET MVCASP.NET MVC
ASP.NET MVC
 
Interoperabilidad-iso-ogc-w3c-ietf
Interoperabilidad-iso-ogc-w3c-ietfInteroperabilidad-iso-ogc-w3c-ietf
Interoperabilidad-iso-ogc-w3c-ietf
 
Interoperabilidad-iso-ogc-w3c-ietf
Interoperabilidad-iso-ogc-w3c-ietfInteroperabilidad-iso-ogc-w3c-ietf
Interoperabilidad-iso-ogc-w3c-ietf
 
EasyData: OpenData and easy access
EasyData: OpenData and easy accessEasyData: OpenData and easy access
EasyData: OpenData and easy access
 
S8-DAW-2022S1.pptx
S8-DAW-2022S1.pptxS8-DAW-2022S1.pptx
S8-DAW-2022S1.pptx
 
Introducción a hadoop
Introducción a hadoopIntroducción a hadoop
Introducción a hadoop
 
Linq - Introducción
Linq - IntroducciónLinq - Introducción
Linq - Introducción
 
Presentacion Ruby on Rails en Universidad Autónoma 2009
Presentacion Ruby on Rails en Universidad Autónoma 2009Presentacion Ruby on Rails en Universidad Autónoma 2009
Presentacion Ruby on Rails en Universidad Autónoma 2009
 
S4-PD1-2.2 EF
S4-PD1-2.2 EFS4-PD1-2.2 EF
S4-PD1-2.2 EF
 
Primeros pasos con neo4j
Primeros pasos con neo4jPrimeros pasos con neo4j
Primeros pasos con neo4j
 
Presentación Ruby on Rails en Softare Freedom Day 09 Buenos Aires
Presentación Ruby on Rails en Softare Freedom Day 09 Buenos AiresPresentación Ruby on Rails en Softare Freedom Day 09 Buenos Aires
Presentación Ruby on Rails en Softare Freedom Day 09 Buenos Aires
 
Introducción a phone gap
Introducción a phone gapIntroducción a phone gap
Introducción a phone gap
 
Caixa galicia Enterprise Service Bus
Caixa galicia   Enterprise Service BusCaixa galicia   Enterprise Service Bus
Caixa galicia Enterprise Service Bus
 
Introduccion al Akelos Php Framework
Introduccion al Akelos Php FrameworkIntroduccion al Akelos Php Framework
Introduccion al Akelos Php Framework
 
Kubernetes para developers
Kubernetes para developersKubernetes para developers
Kubernetes para developers
 
Technology Architect - Coorganizador AWS User Group Palma
Technology Architect - Coorganizador AWS User Group PalmaTechnology Architect - Coorganizador AWS User Group Palma
Technology Architect - Coorganizador AWS User Group Palma
 
De symfony 2013 dr. jenkins y mr. hyde - slides
De symfony 2013   dr. jenkins y mr. hyde - slidesDe symfony 2013   dr. jenkins y mr. hyde - slides
De symfony 2013 dr. jenkins y mr. hyde - slides
 
De symfony 2013 dr. jenkins y mr. hyde - slides-842359017
De symfony 2013   dr. jenkins y mr. hyde - slides-842359017De symfony 2013   dr. jenkins y mr. hyde - slides-842359017
De symfony 2013 dr. jenkins y mr. hyde - slides-842359017
 
Backbeam
BackbeamBackbeam
Backbeam
 

I18n DeSymfony

  • 1.
  • 2.
  • 3. ¿Quién soy? • Cristina Quintana (@jautu) • Soy de Córdoba • Desarrolladora web en Acilia
  • 4. Repasando conceptos Internacionalización y localización
  • 5. Repasando conceptos Se podría decir: I18n = Contenedor y L10n = contenido
  • 6. Nuestro proyecto de internacionalización En sus orígenes Servidores Windows Tecnología .NET Con Acilia SQL Server Servidores Linux Tecnología Open Source MySQL
  • 15. Nuestro proyecto de internacionalización Israel Israel Italia
  • 16. Nuestro proyecto de internacionalización Israel Israel Italia E. Árabes
  • 17. Nuestro proyecto de internacionalización <html dir=""> •  ltr (left-to-right) •  rtl (right-to-left) Por supuesto, cada caso necesita unas css distintas.
  • 18. Planificación del proyecto •  Migración de la base de datos •  Construcción del backend •  Construcción del frontend •  Nueva infraestructura
  • 19. Planificación del proyecto •  Migración de la base de datos •  Construcción del backend •  Construcción del frontend (sf2) •  Nueva infraestructura
  • 20. ¿Cómo hicimos el frontend? Mode translation: On
  • 21. Flujo de trabajo Petición Controlador Respuesta Vista
  • 22. Flujo de trabajo _locale SI existe SI Procesar Construir ruta acción vista
  • 23. Flujo de trabajo _locale SI existe SI Procesar Construir ruta acción vista
  • 24. Buscando el país Utilizamos un listener: ¿Qué hace? •  Ejecutar sólo en peticiones maestras. •  Parsear url. •  Chequear país. •  Establecer valores predeterminados del idioma (l10n).
  • 25. Buscando el país src/bndl/Resources/config/services.xml <services> ... <service id="bndl.lang_listener" class="%bndl.lang_listener.class%"> <tag name="kernel.event_listener" event="kernel.request" method="onUrlParse"/> <argument type="service" id="router"/> <argument type="service" id="doctrine.orm.entity_manager"/> </service> ... </services> Declaración del listener
  • 26. Buscando el país src/bnd/Listener/LangListener.php class LanguageListener { public function __construct(RouterInterface $router, EntityManager $em) { $this->_router = $router; $this->_em = $em; } public function onUrlParse(Event $event) { ... } } Contenido del listener
  • 27. public function onUrlParse(Event $event) { // Ejecutar la lógica sólo si es una petición maestra if ($event->getRequestType() !== SymfonyComponentHttpKernel HttpKernel::MASTER_REQUEST) { return; } ... // Busqueda del código de país en la url $parameters = $this->router->match($request->getPathInfo()); ... // Si existe el código del país ... // Si el código del país no exite ... } Contenido del listener
  • 28. Detección de IP de usuario Librería Maxmind: •  Ofrece una detección más que razonable en su versión gratuita (GeoLite) X-Forwarded-For header: •  Al pasar por Varnish, load balancers…. La IP original del usuario se pierde. Necesitamos que nuestra aplicación utilice esa IP Curiosidades
  • 29. Detección de IP de usuario SymfonyComponentHttpFoundationRequest.php // Consulta el parámetro REMOTE_ADDR $this->request->getClientIp(); // Consulta tanto HTTP_CLIENT_IP // como HTTP_X_FORWARDED_FOR $this->request->getClientIp( true ); Curiosidades
  • 30. Flujo de trabajo _locale SI existe SI Procesar Construir ruta acción vista
  • 31. Flujo de trabajo _locale SI existe SI Procesar Construir ruta acción vista
  • 33. ¿Qué rutas i18n podemos encontrar? Peticiones de los usuarios: http://natgeotv.com/uk/listings http://natgeotv.com/fr/grille http://natgeotv.com/nl/weekoverzicht Acción de esas peticiones: Controlador: Schedule Acción: listings Análisis del problema
  • 34. ¿Cómo podemos generar esas rutas? src/mybundle/Resources/config/routing.yml Bndl_listings_uk: pattern: /{_locale}/listings defaults: { _controller: "Bndl:Schedule:listings" } requirements: _locale: uk Bndl_listings_nl: pattern: /{_locale}/weekoverzicht defaults: { _controller: "Bndl:Schedule:listings" } requirements: _locale: nl ¿Generación manual de rutas?
  • 35. ¿Cómo podemos generar esas rutas? src/mybundle/Resources/config/routing.yml Bndl_listings_uk: pattern: /{_locale}/listings defaults: { _controller: "Bndl:Schedule:listings" } requirements: _locale: uk ¿Estamos locos? Bndl_listings_nl: pattern: /{_locale}/weekoverzicht defaults: { _controller: "Bndl:Schedule:listings" } requirements: _locale: nl ¿Generación manual de rutas?
  • 36. Generación dinámica de rutas 1.  Definir la ruta genérica: Bndl_listings: pattern: /{_locale}/listings defaults: { _controller: "Bndl:Schedule:listings" } 2.  Modificar el routing para la generación automática de dicha ruta para todos los países. Plan de acción
  • 37. Generación dinámica de rutas COUNTRY id URL_WORD_TRANS name url id ... country_id time_zone url_word_id value URL_WORD id value Un poquito de base de datos
  • 38. Generación dinámica de rutas COUNTRY id url 15 uk URL_WORD_TRANS 6 fr id country_id url_word_id value 25 nl 1 15 7 listings 2 6 7 grille URL_WORD 3 25 7 weekoverzicht id value 7 listings Un poquito de base de datos
  • 39. Generación dinámica de rutas COUNTRY id url 15 uk URL_WORD_TRANS 6 fr id country_id url_word_id value 25 nl 1 15 7 listings 2 6 7 grille URL_WORD 3 25 7 weekoverzicht id value 7 listings Un poquito de base de datos
  • 40. ¿Qué rutas i18n podemos encontrar? Peticiones de los usuarios: http://natgeotv.com/uk/listings http://natgeotv.com/fr/grille http://natgeotv.com/nl/weekoverzicht Acción de esas peticiones: Controlador: Schedule Acción: listings Análisis del problema
  • 41. Modificando la generación de rutas Interceptar el momento en el que se lee la configuración de las rutas: <services> ... <service id="bndl.routing.loader.yml" class="%bndl.routing.loader.yml.class%" public="false"> <tag name="routing.loader" /> <argument type="service" id="service_container" /> <argument type="service" id="bndl.file_locator" /> </service> <service id="routing.loader.yml" alias="bndl.routing.loader.yml" /> ... </services> Adaptación de la estructura
  • 42. Modificando la generación de rutas Interceptar el momento en el que se lee la configuración de las rutas: <services> ... <service id="bndl.routing.loader.yml" class="%bndl.routing.loader.yml.class%" public="false"> <tag name="routing.loader" /> <argument type="service" id="service_container" /> <argument type="service" id="bndl.file_locator" /> </service> <service id="routing.loader.yml" alias="bndl.routing.loader.yml" /> ... </services> Adaptación de la estructura
  • 43. Modificando la generación de rutas ¿Qué hace la clase original? namespace SymfonyComponentRoutingLoader; class YamlFileLoader extends FileLoader { protected function parseRoute(RouteCollection $collection, $name, $config, $file) { ... $route = new Route($config['pattern'], $defaults, $requirements, $options); $collection->add($name, $route); } } Aprendiendo de sf2
  • 44. Modificando la generación de rutas Extendemos el servicio original: use SymfonyComponentRoutingLoaderYamlFileLoader as BaseYamlFileLoader; class YamlFileLoader extends BaseYamlFileLoader { protected function parseRoute(RouteCollection $collection, $name, $config, $file) { $url_words_trans = ...; // Consulta a la tabla url_words $country = ...; // Consulta a la tabla country ... $route = new I18nRoute(..., $url_words_trans, $country); $collection->addCollection($route->getCollection()); } } Adaptando la arquitectura
  • 45. Modificando la generación de rutas class I18nRoute { public function __construct($original_pattern, $defaults = array(), $requirements = array(), $options = array(), $translations = array(), $regions = array()) { // Construccion a partir del patrón original, sus equivalentes por país $patterns = ...; // Formato: array('_locale' => 'pattern') ... //Construcción de una ruta para cada patrón nuevo foreach ($patterns as $country_url => $i18n_pattern) { $requirements['_locale'] = $country_url; $this->collection->add($name . '_' . country_url, new Route($i18n_pattern, $defaults, $requirements, $options, true)); } $this->collection->add($name, new Route($original_pattern, $defaults, $requirements, $options, true)); } } Adaptando la arquitectura
  • 46. Resultado Bndl_listings_uk: pattern: /{_locale}/listings defaults: { _controller: ”Bndl:Schedule:listings" } requirements: _locale: uk Bndl_listings_nl: pattern: /{_locale}/weekoverzicht defaults: { _controller: ”Bndl:Schedule:listings" } requirements: _locale: nl Bndl_listings: pattern: /{_locale}/listings defaults: { _controller: ”Bndl:Schedule:listings" } Rutas disponibles
  • 47. Flujo de trabajo _locale SI existe SI Procesar Construir ruta acción vista
  • 48. Flujo de trabajo _locale SI existe SI Procesar Construir ruta acción vista
  • 49. Flujo de trabajo _locale SI existe SI Procesar Construir ruta acción vista
  • 51. ¿Dónde pueden estar mis vistas? app/Resources/Bndl/views src/Bndl/Resources/views ¿Qué hacer si tenemos requisitos específicos para un país?: Llenar el código de {% if %} Análisis del problema
  • 52. ¿Dónde pueden estar mis vistas? app/Resources/Bndl/views src/Bndl/Resources/views ¿Qué hacer si tenemos requisitos específicos para un país?: Llenar el código de ¡Acaben ya conmigo! {% if %} Análisis del problema
  • 53. ¿Dónde pueden estar mis vistas? ¿Alguna idea mejor?: •  Se puede utilizar la plantilla que se encuentre primero en este nuevo árbol de directorios src/Bndl/Resources/views/{_locale} src/Bndl/Resources/views/%DIR_BASE% src/Bndl/Resources/views Análisis del problema
  • 54. Modificando la localización de las vistas Interceptar el momento cuando crear el árbol de directorios: <services> ... <service id="bnbl.file_locator" class="%bnbl.file_locator.class%"> <argument type="service" id="kernel" /> <argument>%kernel.root_dir%/Resources</argument> </service> <service id="file_locator" alias="bnbl.file_locator" /> ... </services> Adaptación de la estructura
  • 55. Modificando la localización de las vistas Interceptar el momento cuando crear el árbol de directorios: <services> ... <service id="bnbl.file_locator" class="%bnbl.file_locator.class%"> <argument type="service" id="kernel" /> <argument>%kernel.root_dir%/Resources</argument> </service> <service id="file_locator" alias="bnbl.file_locator" /> ... </services> Adaptación de la estructura
  • 56. Modificando la localización de las vistas ¿Qué hace la clase original?: namespace SymfonyComponentHttpKernelConfig; class FileLocator extends BaseFileLocator { public function locate($file, $currentPath = null, $first = true) { ... return $this->kernel->locateResource($file, $this->path, $first); ... } } Adaptando la arquitectura
  • 57. Modificando la localización de las vistas Extendemos el servicio original: use SymfonyComponentHttpKernelConfigFileLocator as BaseFileLocator; class FileLocator extends BaseFileLocator { public function locate($file, $currentPath = null, $first = true) { ... return $this->locateResource($file, $this->path, $first); ... } } Adaptando la arquitectura
  • 58. Modificando la localización de las vistas Extendemos el servicio original: use SymfonyComponentHttpKernelConfigFileLocator as BaseFileLocator; class FileLocator extends BaseFileLocator { public function locate($file, $currentPath = null, $first = true) { ... return $this->locateResource($file, $this->path, $first); ... } } Adaptando la arquitectura
  • 59. Modificando la localización de las vistas class FileLocator extends BaseFileLocator { // Lógica de la clase SymfonyComponentHttpKernelKernel public function locateResource($name, $dir = null, $first = true) { $myDir = ...; // Nombre del directorio para las vistas del país foreach ( $bundles as $bundle ) { $checkPaths = ...; // Rutas relativas a chequear foreach ( $checkPaths as $checkPath) { if ( file_exists($file = $checkPath)) { // Verificación de la plantilla } } } ... } } Adaptando la arquitectura
  • 60. Resultado El árbol de directorios en el que se chequean la existencia de las plantillas es: app/Resources/Bndl/views/{_locale} app/Resources/Bndl/views/%DIR_BASE% app/Resources/Bndl/views src/Bndl/Resources/views/{_locale} src/Bndl/Resources/views/%DIR_BASE% src/Bndl/Resources/views Rutas de directorios disponibles
  • 61. Resultado Hong Kong Rutas de directorios disponibles
  • 62. Resultado Hong Kong Reino Unido Rutas de directorios disponibles
  • 63. Resultado Hong Kong Reino Unido Rutas de directorios disponibles
  • 65. Lo que no hay que dejar pasar •  Establecer la zona horaria correcta. •  Formatos de fechas. •  Conversión de texto a mayúscula. •  Cachear lo no cacheable. •  Especificar el encoding en la configuración de los servicios que acceden a base de datos. •  ... Pequeñas soluciones
  • 66. Lo que no hay que dejar pasar Conversión de texto a mayúscula: {{ ' ' | upper }} Pequeñas soluciones
  • 67. Lo que no hay que dejar pasar Conversión de texto a mayúscula: {{ ' ' | upper }} Debería imprimir: Pequeñas soluciones
  • 68. Lo que no hay que dejar pasar Conversión de texto a mayúscula: {{ ' ' | upper }} Debería imprimir: ¡PERO NO! Imprime: SIBER SIRLAR Pequeñas soluciones
  • 69. Lo que no hay que dejar pasar Conversión de texto a mayúscula: {{ ' ' | upper }} Debería imprimir: ¡PERO NO! Imprime: SIBER SIRLAR Pequeñas soluciones
  • 70. Lo que no hay que dejar pasar Solución: Sobreescribir el método upper de twig. class TextExtension extends Twig_Extension { public function upper(Twig_Environment $env, $sentence) { $value = str_replace('ı', 'I', str_replace('i', 'İ', $sentence)); if ( null !== ( $charset = $env->getCharset() ) ) { return mb_strtoupper($value, $charset); } return strtoupper($value); } } Pequeñas soluciones
  • 72. Load Balancer Varnish (n) Media Frontend (n) Backend (n) | BBDD
  • 73. Deploy y más deploy
  • 74. ¿Cómo manejamos los entornos? •  Tenemos múltiples servidores que coordinar y monitorizar. •  Necesitamos una herramienta que actúe sobre todos. •  Nuestra elección Magallanes, que sería como el “Capistrano de PHP”, creado por @andres_montanez, miembro del equipo. Análisis de la situación
  • 75. Magallanes, ¡hay que probarlo! •  Posibilidad de configurar múltiples “targets” •  Posibilidad de crear “pre-tasks” •  Integración con git •  Rollback instantáneo •  Posibilidad de post-tasks •  Configuración de número de releases a archivar. •  Posibilidad de sobreescribir realeases. ¡Feliz deploy a todos!
  • 76. Muchas gracias @jautu quintana.cano@gmail.com http://es.linkedin.com/in/cristinaquintanacano Contacto