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