Configurando DoctrineExtensions en Symfony 2

    Este artículo forma parte de la serie Symfony2

    Antes de continuar se recomienda leer : Instalando y configurando Symfony2 en un ambiente compartido

    Doctrine ORM es una plataforma que permite trabajar base de datos relacionales de forma orientada a objetos de ahi su nombre (Object Relational Mapping). La version 2.x ha sido rediseñada y reescrita desde cero inspirándose en plataformas Java como Hibernate y Spring e implementando varios de los patrones de diseño descrito por el reconocido ingeniero del software Martín Fowler.

    La version 1.x incluye en su núcleo los llamados comportamientos (behaviors: translatable o i18n, slug, timestampable, …) los cuales fueron removidos en la versión 2.x por lo que si queremos usar algunos de ellos debemos usar DoctrineExtensions

    Este artículo presupone que tiene configurado un proyecto Symfony2 (2.0.12) y Doctrine ORM (2.2.1) aunque el procedimiento que se describe puede usarse con otras versiones.

    Descargar DoctrineExtensions

    – Ir a DoctrineExtensions v2.3.0
    – Descompactar
    – Ir la carpeta lib del directorio resultante del paso anterior
    – Si deseas que estas extension sea usada por varios proyectos entonces puedes copiar la carpeta Gedmo a /usr/share/php

    $ sudo sudo rsync -avz --no-p --no-o --no-g Gedmo /usr/share/php/
    

    sino debes copiar la Carpeta en vendor de tu proyecto.

    Crear DoctrineExtensions Listener

    Ponga la siguiente clase dentro del DIR DependencyInjection de su Bundle

    <?php
    // Ajuste su Namespace
    namespace CompanyName\BundleName\DependencyInjection;
     
    use Symfony\Component\HttpKernel\Event\GetResponseEvent;
    use Symfony\Component\DependencyInjection\ContainerAwareInterface;
    use Symfony\Component\DependencyInjection\ContainerInterface;
    
    class DoctrineExtensionListener implements ContainerAwareInterface
    {
        /**
         * @var ContainerInterface
         */
        protected $container;
    
        public function setContainer(ContainerInterface $container = null)
        {
            $this->container = $container;
        }
    
        public function onLateKernelRequest(GetResponseEvent $event)
        {
            $translatable = $this->container->get('gedmo.listener.translatable');
            $translatable->setTranslatableLocale($event->getRequest()->getLocale());
            $translatable->setDefaultLocale('es_es');
            $translatable->setTranslationFallback(true);
        }
    
        public function onKernelRequest(GetResponseEvent $event)
        {
            $securityContext = $this->container->get('security.context', ContainerInterface::NULL_ON_INVALID_REFERENCE);
            if (null !== $securityContext && null !== $securityContext->getToken() && $securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
                $loggable = $this->container->get('gedmo.listener.loggable');
                $loggable->setUsername($securityContext->getToken()->getUsername());
            }
        }
    }
    

    Crear doctrine_extensions yml

    Ponga este fichero en app/config, se han habilitados todos los comportamientos habilite solo lo que Ud necesite

    # services to handle doctrine extensions
    # import it in config.yml
    services:
        # KernelRequest listener
        extension.listener:
            # Ajustar Namespace
            class: CompanyBundleNameDependencyInjectionDoctrineExtensionListener
            calls:
                - [ setContainer, [ @service_container ] ]
            tags:
                # translatable sets locale after router processing
                - { name: kernel.event_listener, event: kernel.request, method: onLateKernelRequest, priority: -10 }
                # loggable hooks user username if one is in security context
                - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
    
        # Doctrine Extension listeners to handle behaviors
        gedmo.listener.tree:
            class: GedmoTreeTreeListener
            tags:
                - { name: doctrine.event_subscriber, connection: default }
            calls:
                - [ setAnnotationReader, [ @annotation_reader ] ]
    
        gedmo.listener.translatable:
            class: GedmoTranslatableTranslatableListener
            tags:
                - { name: doctrine.event_subscriber, connection: default }
            calls:
                - [ setAnnotationReader, [ @annotation_reader ] ]
                - [ setDefaultLocale, [ %locale% ] ]
                - [ setTranslationFallback, [ false ] ]
    
        gedmo.listener.timestampable:
            class: GedmoTimestampableTimestampableListener
            tags:
                - { name: doctrine.event_subscriber, connection: default }
            calls:
                - [ setAnnotationReader, [ @annotation_reader ] ]
    
        gedmo.listener.sluggable:
            class: GedmoSluggableSluggableListener
            tags:
                - { name: doctrine.event_subscriber, connection: default }
            calls:
               - [ setAnnotationReader, [ @annotation_reader ] ]
    
        gedmo.listener.sortable:
            class: GedmoSortableSortableListener
            tags:
                - { name: doctrine.event_subscriber, connection: default }
            calls:
                - [ setAnnotationReader, [ @annotation_reader ] ]
    
        gedmo.listener.loggable:
            class: GedmoLoggableLoggableListener
            tags:
                - { name: doctrine.event_subscriber, connection: default }
            calls:
                - [ setAnnotationReader, [ @annotation_reader ] ]
    

    Importar doctrine_extensions.yml en el config.yml

    ...
    imports:
        - { resource: parameters.ini }
        - { resource: security.yml }
        - { resource: doctrine_extensions.yml }
    ...
    

    Agregar la sección translatable a la sección doctrine en el config.yml

    Usamos traducciones personales o es lo que lo mismo cada tabla que tienen campos internacionalizados tendrá una tabla de traducciones

    ...
    doctrine:
        dbal:
            driver:   pdo_mysql
            host:     localhost
            port:     3306
            dbname:   DB_NAME
            user:     root
            password: 
            charset:  UTF8
    
        orm:
            auto_mapping: true
            mappings:
                translatable:
                    type: annotation
                    # Camino absoluto
                    dir: /usr/share/php/Gedmo/Translatable/Entity/MappedSuperclass
                    # Configuración por proyectos
                    # dir: %kernel.root_dir%/../vendor/Gedmo/Translatable/Entity/MappedSuperclass
                    prefix: GedmoTranslatableEntity
                    alias: Gedmo
    ...
    

    Comprobar que el comportamiento translatable esté habilitado

    $ php app/console doctrine:mapping:info
    ...
    [OK]   Gedmo/Translatable/Entity/MappedSuperclass/AbstractPersonalTranslation
    [OK]   Gedmo/Translatable/Entity/MappedSuperclass/AbstractTranslation
    ...
    

    Ponemos las siguientes clases en el DIR Entity

    Nótese el uso de las anotaciones Gedmo
    Entidad Category

    <?php
    namespace CompanyName\BundleName\Entity;
     
    use Doctrine\Common\Collections\ArrayCollection;
    use Gedmo\Mapping\Annotation as Gedmo;
    use Doctrine\ORM\Mapping as ORM;
     
    /**
     * @ORM\Entity
     * @ORM\Table(name="category")
     * @Gedmo\TranslationEntity(class="Acme\DemoBundle\Entity\CategoryTranslation")
     */
    class Category
    {
        /**
         * @ORMColumn(type="integer")
         * @ORMId
         * @ORMGeneratedValue
         */
        private $id;
    
        /**
         * @GedmoTranslatable
         * @ORMColumn(length=64)
         */
        private $title;
    
        /**
         * @GedmoTranslatable
         * @ORMColumn(type="text", nullable=true)
         */
        private $description;
    
        /**
         * @ORMOneToMany(
         *   targetEntity="CategoryTranslation",
         *   mappedBy="object",
         *   cascade={"persist", "remove"}
         * )
         */
        private $translations;
    
        public function __construct()
        {
            $this->translations = new ArrayCollection();
        }
    
        public function getTranslations()
        {
            return $this->translations;
        }
    
        public function addTranslation(CategoryTranslation $t)
        {
            if (!$this->translations->contains($t)) {
                $this->translations[] = $t;
                $t->setObject($this);
            }
        }
    
        public function getId()
        {
            return $this->id;
        }
    
        public function setTitle($title)
        {
            $this->title = $title;
        }
    
        public function getTitle()
        {
            return $this->title;
        }
    
        public function setDescription($description)
        {
            $this->description = $description;
        }
    
        public function getDescription()
        {
            return $this->description;
        }
    
        public function __toString()
        {
            return $this->getTitle();
        }
    }
    

    Entidad CategoryTranslations

    <?php
    namespace CompanyName\BundleName\Entity;
     
    use Doctrine\ORM\Mapping as ORM;
    use Gedmo\Translatable\Entity\MappedSuperclass\AbstractPersonalTranslation;
     
    /**
     * @ORM\Entity
     * @ORM\Table(name="category_translations",
     *     uniqueConstraints={@ORM\UniqueConstraint(name="lookup_unique_idx", columns={
     *         "locale", "object_id", "field"
     *     })}
     * )
     */
    class CategoryTranslation extends AbstractPersonalTranslation
    {
        /**
         * Convinient constructor
         *
         * @param string $locale
         * @param string $field
         * @param string $value
         */
        public function __construct($locale, $field, $value)
        {
            $this->setLocale($locale);
            $this->setField($field);
            $this->setContent($value);
        }
    
        /**
         * @ORMManyToOne(targetEntity="Category", inversedBy="translations")
         * @ORMJoinColumn(name="object_id", referencedColumnName="id", onDelete="CASCADE")
         */
        protected $object;
    }
    

    Ver las entidades mapeadas

    $ php app/console doctrine:mapping:info
    ...
    [OK]   Gedmo/Translatable/Entity/MappedSuperclass/AbstractPersonalTranslation
    [OK]   Gedmo/Translatable/Entity/MappedSuperclass/AbstractTranslation
    [OK]   Acme/DemoBundle/EntityCategoryTranslation
    [OK]   Acme/DemoBundle/EntityCategory
    

    Crear BD y generar el schema

    $ php app/console doctrine:database:create
    

    Estructura de las tablas

    Si inspeccionamos las estructuras de las tablas veriamos lo siguiente
    category

    +-------------+-------------+------+-----+---------+----------------+
    | Field       | Type        | Null | Key | Default | Extra          |
    +-------------+-------------+------+-----+---------+----------------+
    | id          | int(11)     | NO   | PRI | NULL    | auto_increment |
    | title       | varchar(64) | NO   |     | NULL    |                |
    | description | longtext    | YES  |     | NULL    |                |
    +-------------+-------------+------+-----+---------+----------------+
    

    category_translations
    object_id = id correspondiente de la tabla category
    field = campos que ha sido declarados como translatable en category (title, description)
    content = valor de los campos translatable

    +-----------+-------------+------+-----+---------+----------------+
    | Field     | Type        | Null | Key | Default | Extra          |
    +-----------+-------------+------+-----+---------+----------------+
    | id        | int(11)     | NO   | PRI | NULL    | auto_increment |
    | object_id | int(11)     | YES  | MUL | NULL    |                |
    | locale    | varchar(8)  | NO   | MUL | NULL    |                |
    | field     | varchar(32) | NO   |     | NULL    |                |
    | content   | longtext    | YES  |     | NULL    |                |
    +-----------+-------------+------+-----+---------+----------------+
    

    Consultas de tablas internacionalizadas

    $dql = 'SELECT c.title, c.descriptioon from BundleName:Category c';
     return $this->_em->createQuery($dql)
                        ->setHint(
                                DoctrineORMQuery::HINT_CUSTOM_OUTPUT_WALKER,
                                'GedmoTranslatableQueryTreeWalkerTranslationWalker'
                        )
                        ->getResult();
    

    Lecturas recomendadas
    Proyecto Symfony
    Proyecto Doctrine
    DoctrineExtensions




    2 Comentarios

    1. Jorge Romeo Salazar

      Hola amigo, lo primero de todo, muchas gracias por compartir tus conocimientos. Lo siguiente, hay alguna manera de indicarle a través de la web que traduzca al idioma elegido? Gracias

      Responder
    2. sedlav

      Sip, en el Routing de tu applicacion le puedes pasar el paramtero locale algo asi como:
      Mi_routing:
      pattern: /{_locale}/traducir
      # Locale por defecto: ES
      defaults: { _controller: MiBundle:MiControlador:miActio, _locale: es}

      Luego puedes llamar a tu app usando una url como: http://midominio/en/traduccir

      Responder

    Dejar un comentario

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *