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 entreboolean
,tinyint
,smallint
,integer
,bigint
,double
,float
,real
,decimal
,char
,varchar(tamano)
,longvarchar
,date
,time
,timestamp
,bu_date
,bu_timestamp
,blob
yclob
.required
: valor booleano. Si valetrue
la columna debe tener obligatoriamente un valor.default
: el valor por defecto.primaryKey
: valor booleano. Si valetrue
indica que es una clave primaria.autoIncrement
: valor booleano. Si se indicatrue
para las columnas de tipointeger
, su valor se auto-incrementará.sequence
: el nombre de la secuencia para las bases de datos que utilizan secuencias para las columnasautoIncrement
(por ejemplo PostgreSQL y Oracle).index
: valor booleano. Si valetrue
, se construye un índice simple; si valeunique
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 medianteforeignTable
.onDelete
: determina la acción que se ejecuta cuando se borra un registro en una tabla relacionada. Si su valor essetnull
, la columna de la clave externa se establece anull
. Si su valor escascade
, 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 unaforeignTable
y unaforeignReference
.isCulture
: valor booleano. Su valor estrue
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.