Ver índice de contenidos del libro

13.3. Información textual en la base de datos

Una aplicación que soporta la localización ofrece diferentes contenidos en función de la cultura del usuario. Una tienda online podría ofrecer los mismos productos al mismo precio en todo el mundo, pero con una descripción personalizada para cada país. De esta forma, la base de datos tiene que ser capaz de almacenar diferentes versiones de una misma información y el esquema de la base de datos debe diseñarse de una forma especial, además de indicar la cultura cada vez que se manipulan los objetos del modelo.

13.3.1. Creando un esquema para una aplicación localizada

Cada una de las tablas que contiene información localizada, se debe dividir en 2 partes: una tabla que no contiene ninguna información relativa a la i18n y otra tabla con todas las columnas relacionadas con la i18n. Las dos tablas tienen una relación de tipo "uno a muchos". De esta forma, es posible añadir más idiomas sin tener que modificar el modelo. Como ejemplo se va a considerar una tabla llamada Producto.

En primer lugar, se crean las tablas en el archivo schema.yml, tal y como muestra el listado 13-6.

Listado 13-6 - Esquema de ejemplo para datos i18n con Propel, en config/schema.yml

mi_conexion:
  mi_producto:
    _attributes: { phpName: Producto, isI18N: true, i18nTable: mi_producto_i18n }
    id:          { type: integer, required: true, primaryKey: true, autoincrement: true }
    precio:      { type: float }

  mi_producto_i18n:
    _attributes: { phpName: ProductoI18n }
    id:          { type: integer, required: true, primaryKey: true, foreignTable: mi_producto, foreignReference: id }
    culture:     { isCulture: true, type: varchar, size: 7, required: true, primaryKey: true }
    nombre:      { type: varchar, size: 50 }

Lo más importante del listado anterior son los atributos isI18N y i18nTable de la primera tabla y la columna especial culture en la segunda. Todos estos atributos son mejoras de Propel creadas por Symfony.

Listado 13-7 - Esquema de ejemplo para datos i18n con Doctrine, en config/doctrine/schema.yml

Producto:
  actAs:
    I18n:
      fields: [nombre]
  columns:
    precio: { type: float }
    nombre: { type: string(50) }

Symfony puede automatizar aun más este proceso. Si la tabla que contiene los datos internacionalizados tiene el mismo nombre que la tabla principal seguido de un sufijo _i18n y ambas están relacionadas con una columna llamada id, se pueden omitir las columnas id y culture en la tabla _i18n y los atributos específicos para i18n en la tabla principal. Si se siguen estas convenciones, Symfony es capaz de inferir toda esta información. Así, para Symfony es equivalente el esquema del listado 13-8 al listado 13-6 mostrado anteriormente.

Listado 13-7 - Versión abreviada del esquema de ejemplo para datos i18n, en config/schema.yml

mi_conexion:
  mi_producto:
    _attributes: { phpName: Producto }
    id:
    precio:      float
  mi_producto_i18n:
    _attributes: { phpName: ProductoI18n }
    nombre:      varchar(50)

13.3.2. Usando los objetos i18n generados

Una vez construido el modelo de objetos (ejecutando la tarea propel:build --model después de cada modificación del archivo schema.yml), se puede utilizar la clase Producto con soporte de i18n como si fuera una sola tabla, tal y como muestra el listado 13-9.

Listado 13-9 - Trabajando con objetos i18n

$producto = ProductoPeer::retrieveByPk(1);
$producto->setName('Nom du produit'); // La cultura por defecto es la del usuario actual
$producto->save();
 
echo $producto->getName();
 => 'Nom du produit'
 
$producto->setName('Product name', 'en'); // Modificamos el valor para la cultura 'en' (inglés)
$producto->save();
 
echo $producto->getName('en');
 => 'Product name'

Si no se quiere modificar la cultura cada vez que se utiliza un objeto i18n, es posible modificar el método hydrate() en la clase del objeto. El listado 13-9 muestra un ejemplo.

Listado 13-9 - Redefiniendo el método hydrate() para establecer la cultura, en miproyecto/lib/model/Producto.php

public function hydrate(ResultSet $rs, $startcol = 1)
{
  parent::hydrate($rs, $startcol);
  $this->setCulture(sfContext::getInstance()->getUser()->getCulture());
}

Las consultas realizadas mediante los objetos peer se pueden restringir para que solo obtengan los objetos que disponen de una traducción para la cultura actual, mediante el método doSelectWithI18n en lugar del habitual doSelect, como muestra el listado 13-10. Además, crea los objetos i18n relacionados a la vez que los objetos normales, de forma que se reduce el número de consultas necesarias para obtener el contenido completo (el Capítulo 18 incluye más información sobre las ventajas de este método sobre el rendimiento de la aplicación).

Listado 13-10 - Obteniendo objetos con un Criteria de tipo i18n

$c = new Criteria();
$c->add(ProductoPeer::PRECIO, 100, Criteria::LESS_THAN);
$productos = ProductoPeer::doSelectWithI18n($c, $cultura);
// El argumento $cultura es opcional
// Si no se indica, se utiliza la cultura actual

Así que no es necesario trabajar directamente con los objetos i18n, sino que se pasa la cultura al modelo (o se deja que el modelo la obtenga automáticamente) cada vez que se quiere realizar una consulta con un objeto normal.