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