Introduccin Doctrine es una mapeador de objetos-relacional (ORM) escrito en PHP que proporciona una capa de persistencia para objetos de PHP. Es una capa de abstraccin que se sita justo encima de un SGBD.
Qu es Doctrine?
Doctrine es una librera 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. Introduccin a Object-Relational Mapping (ORM). Doctrine est inspirado en Hibernate, que es uno de los ORM ms populares y grandes que existen y nos brinda una capa de abstraccin de la base de datos muy completa. La caracterstica ms importante es que te da la posibilidad de escribir consultas de base de datos en un lenguaje propio llamado Doctrine Query Language (DQL).
Antecedentes
El proyecto Doctrine empez con Konsta Vesterinen. Tambin conocido como zYne-. El 13 de Abril de 2006 se hizo el primer envo al repositorio svn. Doctrine tiene influencias de docenas de proyectos de personas muy diferentes. Las mayores influencias son de Hibernate (el ORM de Java) y de ActiveRecord (de Ruby on Rails). Ambos tienen una implementacin completa tanto en Java como en Ruby. El propsito de Doctrine es construir una solucin igual de potente para PHP.
Caractersticas principales
Doctrine es una librera muy completa y muy configurable, por lo que casi nos resulta complicado seleccionar que detalles destacar. Os ponemos las caractersticas ms globales, ya que este tutorial pretende ser una introduccin, por lo que dejamos de lado las cosas ms complejas.
Generacin automtica del modelo Cuando se trabaja con ORM, necesitas crear el conjunto de clases que representa el modelo de la aplicacin, luego estas clases sern vinculadas al esquema de la base de datos de forma automtica con un motor ORM. Aunque son cosas diferentes, cuando diseas un modelo relacional y un modelo de clases, suelen ser muy parecidos. Doctrine se aprovecha de esta similitud y nos permite generar de forma automtica el modelo de clases basndose 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 automtica el modelo, pero tambin deja la posibilidad (como es lgico) que puedas definir tu mismo el mapeo de tablas y sus relaciones. Esto se puede hacer con cdigo PHP o con YAML, que es un formato de serializacin de datos legible por humanos muy usado para este fin. En el ejemplo siguiente se muestra como definir dos tablas relacionadas: 1. --- 2. User: 3. columns: 4. username: 5. type: string(255) 6. password: 7. type: string(255) 8. contact_id: 9. type: integer 10. relations: 11. Contact: 12. class: Contact 13. local: contact_id 14. foreign: id 15. foreignAlias: User 16. foreignType: one 17. type: one 18. 19. Contact: 20. columns: 21. first_name: 22. type: string(255) 23. last_name: 24. type: string(255) 25. phone: 26. type: string(255) 27. email: 28. type: string(255) 29. address: 30. type: string(255) 31. relations: 32. User: 33. class: User 34. local: id 35. foreign: contact_id 36. foreignAlias: Contact 37. foreignType: one 38. type: one En el ejemplo, se muestra una relacin entre dos tablas a travs 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 mtodos para insertar, actualizar o eliminar registros entre otros. Por ejemplo:
1. <php 2. $user = new User(); 3. $user->name = Ivn; 4. $user->save(); La clase Doctrine_Table representa el esquema de una tabla. A travs de esta clase podemos, por ejemplo, obtener informacin sobre las columnas o buscar registros especficos:
1. <?php 2. $userTable = Doctrine_Core::getTable('Users'); 3. $user = $userTable->find(1); Buscadores mgicos (Magic finders) En Doctrine, puedes buscar registros basndote en cualquier campo de una tabla. En el prrafo anterior podamos ver que existe el mtodo find() para buscar un determinado id en una tablapues bien, se podra hacer findByX() en donde X es el nombre de la columna. Si existen los campos llamados name y email, podemos hacer findByName() y findByEmail(). Tambin es importante decir que existe el mtodo 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 automtica) con las tablas y sus relaciones, resulta fcil 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 relacin 1-N:
1. <?php 2. $commentsTable = Doctrine_Core::getTable('Comments'); 3. $comments = $commentsTable->findAll(); 4. foreach($comments as $comment){ 5. echo $comment->User->name; 6. } A travs de $comment->User estamos accediendo a un nuevo objeto del tipo User que se carga de forma dinmica, de esta forma podemos acceder a las propiedades y mtodos de dicha clase olvidndonos de tener que lanzar cdigo 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 diseado 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 informacin a cargar en lugar de usar la forma automtica 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 dinmica, pues bien, esto no es ptimo porque realiza consultas SQL de ms. Si tenemos 100 comentarios, ejecutar 100 consultas SQL para cargar la informacin del objeto User, lo cual es una prdida de rendimiento enorme. En lugar de eso, es necesario usar DQL para que traiga toda la informacin de un solo paso:
1. <?php 2. $q = Doctrine_Query::create() 3. ->select('c.*,u.name') 4. ->from('Comments c') 5. ->innerJoin('c.User u'); 6. $comments = $q->execute(); El cdigo anterior obtendr la informacin de los comentarios cruzados con los datos de usuario, por lo que obtendr toda la informacin en un solo paso. El lenguaje DQL es algo tan extenso que necesitaramos ms tiempo para explicarlo en profundidad, por eso recomendamos mirar la documentacin oficial, donde est bien explicado y completo
Problema Estamos frente a muchos problemas en la construccin de aplicaciones web. En lugar de tratar de explicar todo lo mejor es leer lo que Wikipedia tiene que decir acerca de objetos mappers relacionales. Tareas de gestin de datos en (OO) la programacin orientada a objetos se implementan normalmente mediante la manipulacin de objetos, que son casi siempre los valores no escalares. Por ejemplo, considere una entrada de libreta de direcciones que representa una sola persona, junto con cero o ms nmeros de telfono y cero o ms direcciones. Esto podra ser modelado en una aplicacin orientada a objetos por un "objeto persona" con "slots" para contener los datos que conforman la entrada: el nombre de la persona, una lista (o matriz) de nmeros de telfono, y una lista de direcciones. La lista de nmeros de telfono podra contener en s "objetos Mviles" y as sucesivamente. La entrada de la libreta de direcciones se trata como un nico valor por el lenguaje de programacin (se puede hacer referencia a una sola variable, por ejemplo). Varios mtodos se pueden asociar con el objeto, tal como un mtodo para devolver el nmero preferido de telfono, la direccin de su casa, y as sucesivamente.
Sin embargo, muchos productos de bases de datos populares, tales como SQL DBMS slo pueden almacenar y manipular valores escalares como enteros y cadenas organizadas en tablas. El programador debe ya sea convertir los valores de los objetos en grupos de valores ms simples para el almacenamiento en la base de datos (y convertirlos de nuevo en la recuperacin), o slo utilizar valores escalares simples dentro del programa. Mapeo objeto-relacional se utiliza para implementar el primer enfoque. La altura del problema es traducir esos objetos a las formas que se pueden almacenar en la base de datos para su fcil recuperacin, preservando al mismo tiempo las propiedades de los objetos y sus relaciones; estos objetos se dice que son persistentes.
Requerimientos mnimos
Como se ha expresado, el Doctrine, sera una librera en el servidor que se este usando, haciendo de su uso algo simple, pues se descarga como cualquier librera y se crea una carpeta llamada mapeador, donde va a estar contenida la informacin del Doctrine.
Construyendo un ejemplo real
Para darle algo de vida al tutorial, vamos a crear una implementacin muy sencilla de Doctrine en la que veremos todo lo comentado hasta ahora. La aplicacin 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_comment
1. CREATE SCHEMA ontuts_doctrine CHARSET UTF8 COLLATE utf8_general_ci; 2. use ontuts_doctrine; 3. CREATE TABLE users( 4. id INT(11) PRIMARY KEY auto_increment, 5. name VARCHAR(30), 6. email VARCHAR(60) 7. )ENGINE=INNODB; 8. CREATE TABLE users_comments( 9. id INT(11) PRIMARY KEY auto_increment, 10. id_user INT(11) NOT NULL, 11. text VARCHAR(255), 12. CONSTRAINT fk_users_comments_users FOREIGN KEY (id_user) REFERENCES users(id) 13. )ENGINE=INNODB;
Paso 2: crear el modelo Esto, como ya habrs aprendido, se puede hacer de forma manual (con PHP o YAML) y de forma automtica. En este caso lo haremos de forma automtica para explicar cmo funciona. Vamos a crear el script que se encargar de generar los ficheros, create_orm_model.php:
Nos aseguramos de que existe la carpeta models en la raz de nuestro proyecto: 1. mkdir /path/myproject/models Ahora llamamos al script, bien va Apache o va CLI: 1. 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 deberas modificar. Cualquier mtodo o propiedad que desees aadir, debes hacerlo sobre Users.php o UsersComments.php.
Paso 3: extender el modelo Analizando la aplicacin llegamos a la conclusin de que la clase Users necesita dos nuevos mtodos:
getName: que devuelva el nombre del usuario o Desconocido si no ha rellenado esa informacin. addComment: que inserte un nuevo comentario asignado a dicho usuario. Editamos entonces el fichero Users.php y lo dejamos as:
1. <?php 2. class Users extends BaseUsers 3. { 4. public function addComment($text){ 5. $comment = new UsersComments(); 6. $comment->id_user = $this->id; 7. $comment->text = $text; 8. $comment->save(); 9. } 10. public function getName(){ 11. if(emptyempty($this->name) || is_null($this->name)){ 12. return "Desconocido"; 13. }else{ 14. return $this->name; 15. } 16. } 17. } Tambin necesitamos extender la clase UsersCommentsTable para sobreescribir el mtodo findAll(), de forma que obtenga la informacin cruzada con la tabla users y as mejorar el rendimiento. Editamos el fichero UsersCommentsTable.php y lo dejamos as:
1. <?php 2. class UsersCommentsTable extends Doctrine_Table 3. { 4. /** 5. * Returns an instance of this class. 6. * 7. * @return object UsersCommentsTable 8. */ 9. public static function getInstance() 10. { 11. return Doctrine_Core::getTable('UsersComments'); 12. } 13. public function findAll($hydrationMode = null){ 14. $q = Doctrine_Query::create() 15. ->select('c.*,u.name') 16. ->from('UsersComments c') 17. ->innerJoin('c.Users u'); 18. return $q->execute(); 19. } 20. } Listo, ya tenemos nuestro modelo ampliado
Paso 4: crear la lgica A continuacin creamos el fichero index.php que contendr la lgica principal de la aplicacin. Nos queda algo as: index.php
1. <?php 2. //Carga Doctrine 3. require_once(dirname(__FILE__) . '/lib/Doctrine.php'); 4. spl_autoload_register(array('Doctrine', 'autoload')); 5. $conn = Doctrine_Manager::connection('mysql://root@localhost/ontuts_doctrine', 'doctrine'); 6. $conn->setCharset('utf8'); 7. Doctrine_Core::loadModels('models'); 8. 9. //Variable en la plantilla html 10. $tpl = array("comments"=> array(), "error"=>false); 11. 12. //Comprueba si se ha enviado el formulario 13. if(!emptyempty($_POST) && isset($_POST['create_comment'])){ 14. $email = filter_input(INPUT_POST, "email", FILTER_SANITIZE_STRING); 15. $name = filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING); 16. $text = filter_input(INPUT_POST, "text", FILTER_SANITIZE_STRING); 17. //Comprueba que se hayan rellenado los campos obligatorios 18. if(!emptyempty($email) && !is_null($email) && 19. !emptyempty($text) && !is_null($text)){ 20. $userTable = Doctrine_Core::getTable('Users'); 21. $users = $userTable->findByEmail($email); 22. $user = null; 23. //Si el usuario no existe, lo crea 24. if($users->count()==0){ 25. $user = new Users(); 26. $user->name = $name; 27. $user->email = $email; 28. $user->save(); 29. }else{ 30. $user = $users[0]; 31. } 32. //Inserta el comentario 33. $user->addComment($text); 34. }else{ 35. //Si no se se han rellenado todos los valores obligatorios 36. //mostrar un error 37. $tpl['error'] = true; 38. } 39. } 40. //Carga los comentarios 41. $commentsTable = Doctrine_Core::getTable('UsersComments'); 42. $tpl['comments'] = $commentsTable->findAll(); 43. //Envia la informacin 44. require_once('template.phtml'); Paso 5: crear la plantilla Por ltimo ya solo nos queda dar formato visual al contenido de template.phtml:
1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 2. <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> 3. <head> 4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5. </head> 6. <body> 7. <h2>Aadir comentario</h2> 8. <?if($tpl['error']):?><p>Por favor, rellena las cajas marcadas con asterisco (*)</p><?endif?> 9. <form action="index.php" method="post"> 10. <label>Direccin e-mail *</label><br/> 11. <input type="text" name="email"/><br/> 12. <label>Nombre</label><br/> 13. <input type="text" name="name"/><br/> 14. <label>Comentario *</label><br/> 15. <textarea name="text" cols="80" rows="5"></textarea><br/> 16. <input type="submit" name="create_comment" value="Enviar"/> 17. </form> 18. <h2>Comentarios de los usuarios</h2> 19. <ul> 20. <?foreach($tpl['comments'] as $comment):?> 21. <li><a href="mailto:<?=$comment->Users->email?>"><?=$comment->Users->getName()?></a> - <?=$comment->text?></li> 22. <?endforeach?> 23. </ul> 24. </body> 25. </html> Conclusin Doctrine es una herramienta muy til, con la cual se simplifica la indexacin y se mejora el tiempo de bsqueda. Lo cual es solamente gracias a los proyectos de un gran nmero de personas que hacen hoy posible, su uso. Su aplicacin es muy fcil, pues slo requiere una pequea leda o explicacin; el detalle es que al ser programadores inicindose, no se recomendara usarlo, pues se pierde ese aprendizaje que es bsico y necesario.