Más con Symfony

9.1. Herencia de tablas de Doctrine

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.

Esquema de la herencia de tablas de Doctrine

Figura 9.1 Esquema de la herencia de tablas de Doctrine

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.

Esquema de la herencia simple de tablas

Figura 9.2 Esquema de la herencia simple de tablas

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.

Esquema de la herencia de tablas basada en la agregación de columnas

Figura 9.3 Esquema de la herencia de tablas basada en la agregación de columnas

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.

Esquema de la herencia concreta de tablas

Figura 9.4 Esquema de la herencia concreta de tablas

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.