Aunque la herencia de tablas no es muy conocida ni utilizada por la mayoría de programadores, se trata de una de las características más interesantes de Doctrine. La herencia de tablas permite al programador crear tablas de base de datos que heredan de otras tablas de la misma forma que las clases pueden heredar de otras clases en los lenguajes orientados a objetos. La herencia de tablas es una forma sencilla de que dos o más tablas compartan información en una única tabla padre. Observa el siguiente diagrama para comprender mejor el funcionamiento de la herencia de tablas.
Doctrine incluye tres estrategias diferentes para gestionar la herencia de tablas
en función de las necesidades de la aplicación (rendimiento, atomicidad,
simplicidad...): __simple__
, __column aggregation__
y __concrete__
. Aunque
todas estas estrategias se describen en el
libro de Doctrine, las
siguientes secciones explican cada una de ellas y las circunstancias en las que
son útiles.
9.1.1. La estrategia simple de herencia de tablas
La estrategia simple de herencia de tablas es la más básica ya que guarda todas
las columnas, incluso las de las tablas hijo, dentro de la tabla padre. Si el
esquema del modelo es como el siguiente código YAML, Doctrine genera una tabla
llamada Person
que incluye tanto las columnas de la tabla Professor
como
las de la tabla Student
.
Person:
columns:
first_name:
type: string(50)
notnull: true
last_name:
type: string(50)
notnull: true
Professor:
inheritance:
type: simple
extends: Person
columns:
specialty:
type: string(50)
notnull: true
Student:
inheritance:
type: simple
extends: Person
columns:
graduation:
type: string(20)
notnull: true
promotion:
type: integer(4)
notnull: true
En la estrategia simple de herencia de tablas, las columnas specialty
, graduation
y promotion
se añaden automáticamente en el modelo Person
aunque Doctrine
genera una clase de modelo tanto para Student
como para Professor
.
El inconveniente de esta estrategia es que la tabla padre Person
no incluye
ninguna columna que identifique el tipo de cada registro. En otras palabras,
no es posible obtener solamente los objetos de tipo Professor
o Student
.
La siguiente instrucción de Doctrine devuelve un objeto Doctrine_Collection
con todos los registros de la tabla (registros Student
y Professor
).
$professors = Doctrine_Core::getTable('Professor')->findAll();
La estrategia estrategia simple de herencia de tablas no suele ser muy útil en los ejemplos del mundo real, ya que normalmente es necesario seleccionar objetos de un determinado tipo. Así que no se usará más esta estrategia en este capítulo.
9.1.2. La estrategia de agregación de columnas en la herencia de tablas
La ~estrategia de agregación de columnas~ en la herencia de tablas es similar a
la estrategia simple excepto por el hecho de que añade una columna llamada type
que identifica los diferentes tipos de registros. De esta forma, cuando se
guarda un objeto en la base de datos, se añade automáticamente un valor a la
columna type
que indica el tipo de clase del objeto.
Person:
columns:
first_name:
type: string(50)
notnull: true
last_name:
type: string(50)
notnull: true
Professor:
inheritance:
type: column_aggregation
extends: Person
keyField: type
keyValue: 1
columns:
specialty:
type: string(50)
notnull: true
Student:
inheritance:
type: column_aggregation
extends: Person
keyField: type
keyValue: 2
columns:
graduation:
type: string(20)
notnull: true
promotion:
type: integer(4)
notnull: true
En el esquema YAML anterior se ha modificado el tipo de herencia al valor
column_aggregation
y se han añadido dos nuevos atributos. El primer
atributo se denomina keyField
y especifica la columna que se crea para guardar
el tipo de información del registro. El atributo keyField
es una columna de
de texto llamada type
, que es el nombre por defecto cuando no se
especifica el atributo keyField
. El segundo atributo (keyValue
) define el
valor del tipo de cada registro que pertenece a la clase Professor
o Student
.
La estrategia de agregación de columnas es una forma de herencia de tablas muy
interesante porque crea una única tabla (Person
) que contiene todos los campos
definidos además del campo type
adicional. De esta forma, no es necesario crear
varias tablas para unirlas después mediante una consulta SQL. A continuación
se muestran algunos ejemplos de cómo realizar consultas en las tablas y el tipo
de resultados devueltos:
// Devuelve un Doctrine_Collection de objetos Professor
$professors = Doctrine_Core::getTable('Professor')->findAll();
// Devuelve un Doctrine_Collection de objetos Student
$students = Doctrine_Core::getTable('Student')->findAll();
// Devuelve un objeto Professor
$professor = Doctrine_Core::getTable('Professor')->findOneBySpeciality('physics');
// Devuelve un objeto Student
$student = Doctrine_Core::getTable('Student')->find(42);
// Devuelve un onjeto Student
$student = Doctrine_Core::getTable('Person')->findOneByIdAndType(array(42, 2));
Cuando se obtienen datos de una subclase (Professor
, Student
), Doctrine
añade automáticamente la cláusula WHERE
necesaria de SQL para realizar la
consulta con el valor correspondiente de la columna type
.
No obstante, en algunos casos la agregación de columnas presenta inconvenientes.
En primer lugar, la agregación de columnas impide que los campos de cada sub-tabla
puedan ser configurados como required
. Dependiendo de cuantos campos haya, la
tabla Person
puede contener registros con varios campos vacíos.
El segundo inconveniente está relacionado con el número de sub-tablas y campos. Si el esquema declara muchas sub-tablas y cada una declara a su vez muchos campos, la tabla padre final contendrá un gran número de columnas. Por tanto, esta tabla padre puede ser difícil de mantener.
9.1.3. La estrategia concreta de herencia de tablas
La estrategia concreta de herencia de tablas es un compromiso entre las ventajas de la agregación de columnas, el rendimiento de la aplicación y su facilidad de mantenimiento. Efectivamente, esta estrategia crea una tabla independiente por cada subclase conteniendo todas las columnas: tanto las columnas compartidas como las columnas exclusivas de cada modelo.
Person:
columns:
first_name:
type: string(50)
notnull: true
last_name:
type: string(50)
notnull: true
Professor:
inheritance:
type: concrete
extends: Person
columns:
specialty:
type: string(50)
notnull: true
Student:
inheritance:
type: concrete
extends: Person
columns:
graduation:
type: string(20)
notnull: true
promotion:
type: integer(4)
notnull: true
El esquema anterior genera una tabla Professor
con los siguientes campos:
id
, first_name
, last_name
y specialty
.
Esta estrategia tiene varias ventajas respecto de las anteriores. La primera es
que cada tabla permanece aislada y es independiente de las otras tablas. Además,
ya no se guardan columnas vacías y tampoco se incluye la columna adicional type
.
El resultado es que cada tabla es mucho más pequeña y está aislada del resto.
Nota El hecho de que las columnas comunes estén duplicadas en las sub-tablas es una mejora del rendimiento y de la escalabilidad, ya que Doctrine no tiene que hacer uniones SQL automáticas con la tabla padre para obtener los datos compartidos que pertenecen a un registro de una sub-tabla.
Las únicas dos desventajas de la herencia concreta de tabla son la duplicación
de las columnas compartidas (aunque la duplicación es buena para mejorar el
rendimiento) y el hecho de que la tabla padre generada siempre estará vacía.
Efectivamente, Doctrine genera una tabla Person
aunque nunca guarde información
en ella ni la utilice en las consultas. Como toda la información se guarda en
las sub-tablas, esta tabla padre no se utilizará en ninguna consulta.
Hasta ahora sólo se han presentado las tres estrategias de herencias de tablas de Doctrine, pero todavía no se han utilizado en ningún proyecto real desarrollado con Symfony. La siguiente parte de este capítulo explica cómo aprovechar la herencia de tablas de Doctrine en Symfony 1.3, sobre todo en el modelo y en el framework de formularios.