Continuamos con otra publicación perteneciente a la serie de artículos dedicados a Object-Relational Mapping (ORM). Esta vez nos centraremos en algo más práctico, creando un aplicación de ejemplo usando Doctrine como motor de ORM.
¿Que es Doctrine?
Doctrine es una librería para PHP que nos permite trabajar con un esquema de base de datos como si fuese un conjunto de objetos, y no de tablas y registros. Si no sabes todavía que significa ORM y que ventajas / desventajas tiene, te recomiendo que leas el tutorial anterior: Introducción a Object-Relational Mapping (ORM).
Doctrine está inspirado en Hibernate, que es uno de los ORM más populares y grandes que existen y nos brinda una capa de abstracción de la base de datos muy completa. La característica más importante es que te da la posibilidad de escribir consultas de base de datos en un lenguaje propio llamado Doctrine Query Language (DQL).
Características principales
Doctrine es una librería muy completa y muy configurable, por lo que casi me resulta complicado seleccionar que detalles destacar. Os pongo las características más globales, ya que este tutorial pretende ser una introducción, por lo que dejamos de lado las cosas más “complejas”.
Generación automática del modelo
Cuando se trabaja con ORM, necesitas crear el conjunto de clases que representa el modelo de la aplicación, luego estas clases serán vinculadas al esquema de la base de datos de forma automática con un motor ORM.
Aunque son cosas diferentes, cuando diseñas un modelo relacional y un modelo de clases, suelen ser muy parecidos. Doctrine se aprovecha de esta similitud y nos permite generar de forma automática el modelo de clases basándose en el modelo relacional de tablas.
Es decir, si tenemos una tabla llamada usuarios, se autogenerará una clase llamada Usuarios cuyas propiedades son las columnas de dicha tabla.
Posibilidad de trabajar con YAML
Como se comenta en el apartado anterior, Doctrine puede generar de forma automática el modelo, pero también deja la posibilidad (como es lógico) que puedas definir tu mismo el mapeo de tablas y sus relaciones.
Esto se puede hacer con código PHP o con YAML, que es un formato de serialización de datos legible por humanos muy usado para este fin. En el ejemplo siguiente se muestra como definir dos tablas relacionadas:
---
User:
columns:
username:
type: string(255)
password:
type: string(255)
contact_id:
type: integer
relations:
Contact:
class: Contact
local: contact_id
foreign: id
foreignAlias: User
foreignType: one
type: one
Contact:
columns:
first_name:
type: string(255)
last_name:
type: string(255)
phone:
type: string(255)
email:
type: string(255)
address:
type: string(255)
relations:
User:
class: User
local: id
foreign: contact_id
foreignAlias: Contact
foreignType: one
type: one
En el ejemplo, se muestra una relación entre dos tablas a través de los campos User.contact_id y Contact.id.
Doctrine_Record y Doctrine_Table
Practicamente todo nuestro modelo heredará de estas dos clases. Doctrine_Record representa una entidad con sus propiedades (columnas) y nos facilita métodos para insertar, actualizar o eliminar registros entre otros. Por ejemplo:
<php $user = new User(); $user->name = “Iván”; $user->save();
La clase Doctrine_Table representa el esquema de una tabla. A través de esta clase podemos, por ejemplo, obtener información sobre las columnas o buscar registros específicos:
<?php
$userTable = Doctrine_Core::getTable('Users');
$user = $userTable->find(1);
Buscadores mágicos (Magic finders)
En Doctrine, puedes buscar registros basándote en cualquier campo de una tabla. En el partado anterior podíamos ver que existe el método find() para buscar un determinado id en una tabla…pues bien, se podría hacer findByX() en donde X es el nombre de la columna.
Si existen los campos llamados name y email, podemos hacer findByName() y findByEmail(). Tambien es importante decir que existe el método findAll(), que obtiene todos los registros de la tabla.
Relaciones entre entidades
En Doctrine, una vez que hemos definido nuestro modelo (o se ha creado de forma automática) con las tablas y sus relaciones, resulta fácil acceder y moverse por entidades relacionadas.
Vamos a ver un ejemplo de como acceder a valores que en nuestro sistema relacional se encuentran en tablas separadas. Teniendo en cuenta que tenemos dos tablas, User y Comments, con relación 1-N:
<?php
$commentsTable = Doctrine_Core::getTable('Comments');
$comments = $commentsTable->findAll();
foreach($comments as $comment){
echo $comment->User->name;
}
A través de $comment->User estamos accediendo a un nuevo objeto del tipo User que se carga de forma dinámica, de esta forma podemos acceder a las propiedades y métodos de dicha clase olvidándonos de tener que lanzar código SQL.
Lenguaje DQL
DQL es un lenguaje creado para ayudar al programador a extraer objetos de la base de datos. Entre las ventajas de usar este lenguaje se encuentran:
- Está diseñado para extraer objetos, no filas, que es lo que nos interesa.
- Entiende las relaciones, por lo que no es necesario escribir los joins a mano.
- Portable con diferentes bases de datos.
Es importante considerar el uso de DQL para obtener la información a cargar en lugar de usar la “forma automática” de Doctrine para mejorar el rendimiento. En el ejemplo anterior, cuando se accede a $comment->User, hemos dicho que se está cargando un nuevo objeto de forma dinámica, pues bien, esto no es óptimo porque realiza consultas SQL de más.
Si tenemos 100 comentarios, ejecutará 100 consultas SQL para cargar la información del objeto User, lo cual es una pérdida de rendimiento enorme. En lugar de eso, es necesario usar DQL para que traiga toda la información de un solo paso:
<?php
$q = Doctrine_Query::create()
->select('c.*,u.name')
->from('Comments c')
->innerJoin('c.User u');
$comments = $q->execute();
El código anterior obtendrá la información de los comentarios cruzados con los datos de usuario, por lo que obtendrá toda la información en un solo paso.
El lenguaje DQL es algo tan extenso que necesitaría varios artículos para explicarlo en profundidad, por eso te recomiendo que mires la documentación oficial que está bien explicado y completo
Construyendo un ejemplo real
Para darle algo de vida al tutorial, vamos a crear una implementación muy sencilla de Doctrine en la que veremos todo lo comentado hasta ahora. La aplicación consiste en un listado de comentarios escritos por usuarios.
Cada vez que se desee insertar un nuevo comentario, se deberá rellenar un formulario con: nombre, e-mail y texto, siendo los dos últimos obligatorios. Si el usuario no deja su nombre, se mostrará como “Desconocido”.
Paso 1: crear el esquema de base de datos
El ejemplo es tan sencillo que solamente tendrá dos tablas relacionadas entre sí: users y users_comments.
CREATE SCHEMA ontuts_doctrine CHARSET UTF8 COLLATE utf8_general_ci; use ontuts_doctrine; CREATE TABLE users( id INT(11) PRIMARY KEY auto_increment, name VARCHAR(30), email VARCHAR(60) )ENGINE=INNODB; CREATE TABLE users_comments( id INT(11) PRIMARY KEY auto_increment, id_user INT(11) NOT NULL, text VARCHAR(255), CONSTRAINT fk_users_comments_users FOREIGN KEY (id_user) REFERENCES users(id) )ENGINE=INNODB;
Paso 2: crear el modelo
Esto, como ya habrás aprendido, se puede hacer de forma manual (con PHP o YAML) y de forma automática. En este caso lo haremos de forma automática para explicar como funciona. Vamos a crear el script que se encargará de generar los ficheros, create_orm_model.php:
<?php
require_once(dirname(__FILE__) . '/lib/Doctrine.php');
spl_autoload_register(array('Doctrine', 'autoload'));
$conn = Doctrine_Manager::connection('mysql://root:password@localhost/ontuts_doctrine', 'doctrine');
Doctrine_Core::generateModelsFromDb('models', array('doctrine'), array('generateTableClasses' => true));
Nos aseguramos de que existe la carpeta models en la raíz de nuestro proyecto:
mkdir /path/myproject/models
Ahora llamamos al script, bien vía Apache o vía CLI:
php5 -f create_orm_model.php
Una vez se ha ejecutado con éxito, vamos a la carpeta models y vemos que se han generado varios ficheros:
- Users.php
- UsersTable.php
- UserComments.php
- UserCommentsTable.php
- generated/BaseUsers.php
- generated/BaseUsersComments.php
Si te fijas, se han creado las clases que se extienden de Doctrine_Record y Doctrine_Table que ya hemos comentado antes.
BaseUsers.php y BaseUsersComments.php representan clases base y que no deberías modificar. Cualquier método o propiedad que desees añadir, debes hacerlo sobre Users.php o UsersComments.php.
Paso 3: extender el modelo
Analizando la aplicación llegamos a la conclusión de que la clase Users necesita dos nuevos métodos:
- getName: que devuelva el nombre del usuario o “Desconocido” si no ha rellenado esa información.
- addComment: que inserte un nuevo comentario asignado a dicho usuario.
Editamos entonces el fichero Users.php y lo dejamos así:
<?php
class Users extends BaseUsers
{
public function addComment($text){
$comment = new UsersComments();
$comment->id_user = $this->id;
$comment->text = $text;
$comment->save();
}
public function getName(){
if(empty($this->name) || is_null($this->name)){
return "Desconocido";
}else{
return $this->name;
}
}
}
Tambien necesitamos extender la clase UsersCommentsTable para sobreescribir el método findAll(), de forma que obtenga la información cruzada con la tabla users y así mejorar el rendimiento. Editamos el fichero UsersCommentsTable.php y lo dejamos así:
<?php
class UsersCommentsTable extends Doctrine_Table
{
/**
* Returns an instance of this class.
*
* @return object UsersCommentsTable
*/
public static function getInstance()
{
return Doctrine_Core::getTable('UsersComments');
}
public function findAll($hydrationMode = null){
$q = Doctrine_Query::create()
->select('c.*,u.name')
->from('UsersComments c')
->innerJoin('c.Users u');
return $q->execute();
}
}
Listo, ya tenemos nuestro modelo ampliado
.
Paso 4: crear la lógica
A continuación creamos el fichero index.php que contendrá la lógica principal de la aplicación. Nos queda algo así: index.php
<?php
//Carga Doctrine
require_once(dirname(__FILE__) . '/lib/Doctrine.php');
spl_autoload_register(array('Doctrine', 'autoload'));
$conn = Doctrine_Manager::connection('mysql://root@localhost/ontuts_doctrine', 'doctrine');
$conn->setCharset('utf8');
Doctrine_Core::loadModels('models');
//Variable en la plantilla html
$tpl = array("comments"=> array(), "error"=>false);
//Comprueba si se ha enviado el formulario
if(!empty($_POST) && isset($_POST['create_comment'])){
$email = filter_input(INPUT_POST, "email", FILTER_SANITIZE_STRING);
$name = filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING);
$text = filter_input(INPUT_POST, "text", FILTER_SANITIZE_STRING);
//Comprueba que se hayan rellenado los campos obligatorios
if(!empty($email) && !is_null($email) &&
!empty($text) && !is_null($text)){
$userTable = Doctrine_Core::getTable('Users');
$users = $userTable->findByEmail($email);
$user = null;
//Si el usuario no existe, lo crea
if($users->count()==0){
$user = new Users();
$user->name = $name;
$user->email = $email;
$user->save();
}else{
$user = $users[0];
}
//Inserta el comentario
$user->addComment($text);
}else{
//Si no se se han rellenado todos los valores obligatorios
//mostrará un error
$tpl['error'] = true;
}
}
//Carga los comentarios
$commentsTable = Doctrine_Core::getTable('UsersComments');
$tpl['comments'] = $commentsTable->findAll();
//Envia la información
require_once('template.phtml');
Paso 5: crear la plantilla
Por último ya solo nos queda dar formato visual al contenido de template.phtml:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> </head> <body> <h2>Añadir comentario</h2> <?if($tpl['error']):?><p>Por favor, rellena las cajas marcadas con asterisco (*)</p><?endif?> <form action="index.php" method="post"> <label>Dirección e-mail *</label><br/> <input type="text" name="email"/><br/> <label>Nombre</label><br/> <input type="text" name="name"/><br/> <label>Comentario *</label><br/> <textarea name="text" cols="80" rows="5"></textarea><br/> <input type="submit" name="create_comment" value="Enviar"/> </form> <h2>Comentarios de los usuarios</h2> <ul> <?foreach($tpl['comments'] as $comment):?> <li><a href="mailto:<?=$comment->Users->email?>"><?=$comment->Users->getName()?></a> - <?=$comment->text?></li> <?endforeach?> </ul> </body> </html>
Reflexión final
Como has visto, en cuestión de minutos tenemos una mini aplicación en PHP que nos permite añadir y listar registros de una base de datos de una forma muy ordenada y sin tocar ni una letra de código SQL.
No llevaría mucho tiempo añadir un sistema de login de usuarios y que pudiesen editar y borrar los comentarios, gracias a los métodos save() y delete() de la clase Doctrine_Record.
¡Nos vemos en la próxima publicación!
Te sugerimos otras entradas relacionadas...
¿Necesitas desarrollar un proyecto web o para móviles? ¡Estamos disponibles!

Cokidoo, los creadores de Ontuts, desarrollamos proyectos tecnológicos centrados en redes sociales y aplicaciones web, aplicaciones móviles y consultoría web y bases de datos.
Somos jóvenes, inquietos, versátiles, apasionados por la innovación y enfocados en las nuevas tecnologías. Con Ontuts tratamos de compartir nuestro conocimiento adquirido en los distintos proyectos, ayudando a la comunidad y mostrando nuestra capacidad tecnológica.
Si necesitas un presupuesto sin compromiso, estamos disponibles, no dudes en contactar con nosotros.


















Comentarios en esta publicación (7 comentarios)
¿Te ha gustado esta publicación? ¡Puedes compartir tu opinión con todos nosotros! Simplemente pincha aquí mismo.