Ver índice de contenidos del libro

8.7. Sintaxis extendida del esquema

Un archivo schema.yml puede ser tan sencillo como el mostrado en el listado 8-3. Sin embargo, los modelos relacionales suelen ser complejos. Este es el motivo por el que existe una sintaxis extendida del esquema para que se pueda utilizar en cualquier caso.

8.7.1. Atributos

Se pueden definir atributos específicos para las conexiones y las tablas, tal y como se muestra en el listado 8-24. Estas opciones se establecen bajo la clave _attributes.

Listado 8-24 - Atributos de las conexiones y las tablas

propel:
  _attributes:   { noXsd: false, defaultIdMethod: none, package: lib.model }
  blog_articulo:
    _attributes: { phpName: Articulo }

Si se quiere validar el esquema antes de que se genere el código asociado, se debe desactivar en la conexión el atributo noXSD. La conexión también permite que se le indique el atributo defaultIdMethod. Si no se indica, se utilizará el método nativo de generación de IDs --por ejemplo, autoincrement en MySQL o sequences en PostgreSQL. El otro valor permitido es none.

El atributo package es como un namespace; indica la ruta donde se guardan las clases generadas automáticamente. Su valor por defecto es lib/model/, pero se puede modificar para organizar el modelo en una estructura de subpaquetes. Si por ejemplo no se quieren mezclar en el mismo directorio las clases del núcleo de la aplicación con las clases de un sistema de estadísticas, se pueden definir dos esquemas diferentes con los paquetes lib.model.business y lib.model.stats.

Ya se ha visto el atributo de tabla phpName, que se utiliza para establecer el nombre de la clase generada automáticamente para manejar cada tabla de la base de datos.

Las tablas que guardan contenidos adaptados para diferentes idiomas (es decir, varias versiones del mismo contenido en una tabla relacionada, para conseguir la internacionalización) también pueden definir dos atributos adicionales (explicados detalladamente en el Capítulo 13), tal y como se muestra en el listado 8-25.

Listado 8-25 - Atributos para las tablas de internacionalización

propel:
  blog_articulo:
    _attributes: { isI18N: true, i18nTable: db_group_i18n }

8.7.2. Detalles de las columnas

La sintaxis básica ofrece dos posibilidades: dejar que Symfony deduzca las características de una columna a partir de su nombre (indicando un valor vacío para esa columna) o definir el tipo de columna con uno de los tipos predefinidos. El listado 8-26 muestra estas 2 opciones.

Listado 8-26 - Atributos básicos de columna

propel:
  blog_articulo:
    id:                 # Symfony se encarga de esta columna
    titulo: varchar(50)  # Definir el tipo explícitamente

Se pueden definir muchos más aspectos de cada columna. Si se definen, se utiliza un array asociativo para indicar las opciones de la columna, tal y como muestra el listado 8-27.

Listado 8-27 - Atributos avanzados de columna

propel:
  blog_articulo:
    id:       { type: integer, required: true, primaryKey: true, autoIncrement: true }
    name:     { type: varchar(50), default: foobar, index: true }
    group_id: { type: integer, foreignTable: db_group, foreignReference: id, onDelete: cascade }

Los parámetros de las columnas son los siguientes:

  • type: Tipo de columna. Se puede elegir entre boolean, tinyint, smallint, integer, bigint, double, float, real, decimal, char, varchar(tamano), longvarchar, date, time, timestamp, bu_date, bu_timestamp, blob y clob.
  • required: valor booleano. Si vale true la columna debe tener obligatoriamente un valor.
  • default: el valor por defecto.
  • primaryKey: valor booleano. Si vale true indica que es una clave primaria.
  • autoIncrement: valor booleano. Si se indica true para las columnas de tipo integer, su valor se auto-incrementará.
  • sequence: el nombre de la secuencia para las bases de datos que utilizan secuencias para las columnas autoIncrement (por ejemplo PostgreSQL y Oracle).
  • index: valor booleano. Si vale true, se construye un índice simple; si vale unique se construye un índice único para la columna.
  • foreignTable: el nombre de una tabla, se utiliza para crear una clave externa a otra tabla.
  • foreignReference: el nombre de la columna relacionada si las claves externas se definen mediante foreignTable.
  • onDelete: determina la acción que se ejecuta cuando se borra un registro en una tabla relacionada. Si su valor es setnull, la columna de la clave externa se establece a null. Si su valor es cascade, se borra el registro relacionado. Si el sistema gestor de bases de datos no soporta este comportamiento, el ORM lo emula. Esta opción solo tiene sentido para las columnas que definen una foreignTable y una foreignReference.
  • isCulture: valor booleano. Su valor es true para las columnas de tipo culture en las tablas de contenidos adaptados a otros idiomas (más detalles en el Capítulo 13).

8.7.3. Claves externas

Además de los atributos de columna foreignTable y foreignReference, es posible añadir claves externas bajo la clave _foreignKeys: de cada tabla. El esquema del listado 8-28 crea una clave externa en la columna usuario_id, que hace referencia a la columna id de la tabla blog_usuario.

Listado 8-28 - Sintaxis alternativa para las claves externas

propel:
  blog_articulo:
    id:
    titulo:     varchar(50)
    usuario_id: { type: integer }
    _foreignKeys:
      -
        foreignTable: blog_usuario
        onDelete:     cascade
        references:
          - { local: usuario_id, foreign: id }

La sintaxis alternativa es muy útil para las claves externas múltiples y para indicar un nombre a cada clave externa, tal y como muestra el listado 8-29.

Listado 8-29 - La sintaxis alternativa de las claves externas aplicada a una clave externa múltiple

_foreignKeys:
  mi_clave_externa:
    foreignTable:  db_usuario
    onDelete:      cascade
    references:
      - { local: usuario_id, foreign: id }
      - { local: post_id, foreign: id }

8.7.4. Índices

Además del atributo de columna index, es posible añadir claves índices bajo la clave _indexes: de cada tabla. Si se quieren crean índices únicos, se debe utilizar la clave _uniques:. En las columnas que requieren un tamaño, por ejemplo por ser columnas de texto, el tamaño del índice se indica entre paréntesis, de la misma forma que se indica el tamaño de cualquier columna. El listado 8-30 muestra la sintaxis alternativa para los índices.

Listado 8-30 - Sintaxis alternativa para los índices y los índices únicos

propel:
  blog_articulo:
    id:
    titulo:            varchar(50)
    created_at:
    _indexes:
      mi_indice:      [titulo(10), usuario_id]
    _uniques:
      mi_otro_indice: [created_at]

La sintaxis alternativa solo es útil para los índices que se construyen con más de una columna.

8.7.5. Columnas vacías

Cuando Symfony se encuentra con una columna sin ningún valor, utiliza algo de magia para determinar su valor. El listado 8-31 muestra los detalles del tratamiento de las columnas vacías.

Listado 8-31 - Los detalles deducidos para las columnas vacías en función de su nombre

// Las columnas vacías llamadas "id" se consideran claves primarias
id:         { type: integer, required: true, primaryKey: true, autoIncrement: true }
 
// Las columnas vacías llamadas "XXX_id" se consideran claves externas
loquesea_id:  { type: integer, foreignTable: db_loquesea, foreignReference: id }
 
// Las columnas vacías llamadas created_at, updated at, created_on y updated_on
// se consideran fechas y automáticamente se les asigna el tipo "timestamp"
created_at: { type: timestamp }
updated_at: { type: timestamp }

Para las claves externas, Symfony busca una tabla cuyo phpName sea igual al principio del nombre de la columna; si se encuentra, se utiliza ese nombre de tabla como foreignTable.

8.7.6. Tablas i18n

Symfony permite internacionalizar los contenidos mediante tablas relacionadas. De esta forma, cuando se dispone de contenido que debe ser internacionalizado, se guarda en 2 tablas distintas: una contiene las columnas invariantes y otra las columnas que permiten la internacionalización.

Todo lo anterior se considera de forma implícita cuando en el archivo schema.yml se dispone de una tabla con el nombre cualquiernombre_i18n. Por ejemplo, el esquema que muestra el listado 8-32 se completa automáticamente con los atributos de columna y de tabla necesarios para que funcione el mecanismo de internacionalización. De forma interna, Symfony entiende ese listado como si se hubiera escrito tal y como se muestra en el listado 8-33. El Capítulo 13 explica en detalle la internacionalización.

Listado 8-32 - Mecanismo i18n implícito

propel:
  db_group:
    id:
    created_at:

  db_group_i18n:
    name:        varchar(50)

Listado 8-33 - Mecanismo i18n explícito

propel:
  db_group:
    _attributes: { isI18N: true, i18nTable: db_group_i18n }
    id:
    created_at:

  db_group_i18n:
    id:       { type: integer, required: true, primaryKey: true, foreignTable: db_group, foreignReference: id, onDelete: cascade }
    culture:  { isCulture: true, type: varchar(7), required: true, primaryKey: true }
    name:     varchar(50)

8.7.7. Comportamientos

Los comportamientos son plugins que modifican el modelo de datos para añadir nuevas funcionalidades a las clases de Propel. El capítulo 17 explica los comportamientos en detalle. Desde la versión 1.1 de Symfony también es posible definir los comportamientos directamente en el esquema. Para ello, se añade su nombre y sus opciones bajo la clave _behaviors de cada tabla. El listado 8-34 muestra un ejemplo que extiende la clase BlogArticulo con el comportamiento llamado paranoid.

Listado 8-34 - Declarando los comportamientos

propel:
  blog_articulo:
    titulo:         varchar(50)
    _behaviors:
      paranoid:     { column: deleted_at }

8.7.8. Más allá del schema.yml: schema.xml

En realidad, el formato de schema.yml es propio de Symfony. Cuando se ejecuta un comando que empieza por propel-, Symfony transforma ese archivo en otro archivo llamado generated-schema.xml, que es el tipo de archivo que necesita Propel para realizar sus tareas sobre el modelo.

El archivo schema.xml contiene la misma información que su equivalente en formato YAML. Por ejemplo, el listado 8-3 se convierte en el archivo XML del listado 8-35.

Listado 8-35 - Ejemplo de schema.xml, que se corresponde con el listado 8-3

<?xml version="1.0" encoding="UTF-8"?>
 <database name="propel" defaultIdMethod="native" noXsd="true" package="lib.model">
    <table name="blog_articulo" phpName="Articulo">
      <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
      <column name="titulo" type="varchar" size="255" />
      <column name="contenido" type="longvarchar" />
      <column name="created_at" type="timestamp" />
    </table>
    <table name="blog_comentario" phpName="Comentario">
      <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
      <column name="articulo_id" type="integer" />
      <foreign-key foreignTable="blog_articulo">
        <reference local="articulo_id" foreign="id"/>
      </foreign-key>
      <column name="autor" type="varchar" size="255" />
      <column name="contenido" type="longvarchar" />
      <column name="created_at" type="timestamp" />
    </table>
 </database>

La descripción del formato schema.xml se puede consultar en la documentación y la sección "Getting started" del sitio web del proyecto Propel (http://propel.phpdb.org/docs/usuario_guide/chapters/appendices/AppendixB-SchemaReference.html).

El formato del esquema en YAML se diseñó para que los esquemas fueran fáciles de leer y escribir, pero su inconveniente es que los esquemas más complejos no se pueden describir solamente con un archivo schema.yml. Por otra parte, el formato XML permite describir completamente el esquema, independientemente de su complejidad e incluye la posibilidad de incluir opciones propias de algunas bases de datos, herencia de tablas, etc.

Symfony también puede trabajar con esquemas escritos en formato XML. Así que no es necesario utilizar el formato YAML propio de Symfony si el esquema es demasiado complejo, si ya dispones de un esquema en formato XML o si estás acostumbrado a trabajar con la sintaxis XML de Propel. Solamente es necesario crear el archivo schema.xml en el directorio config/ del proyecto y construir el modelo.