ORM (Object Relational Mapping)

¿Qué es ORM?

ORM es una técnica de programación para convertir datos entre el lenguaje de programación orientado a objetos utilizado y el sistema de base de datos relacional utilizado en el desarrollo de nuestra aplicación.

Actualmente, las bases de datos relacionales solo pueden guardar datos primitivos, por lo que no podemos guardar objetos que vayamos creando en nuestra aplicación, sino que lo que hacemos es convertir los datos del objeto en datos primitivos que si podremos almacenar en las tablas correspondientes de nuestras bases de datos. Si luego necesitamos ese objeto en alguna parte de nuestra aplicación, debemos de recuperar los datos primitivos de la base de datos y volver a construir el objeto.

El ORM nos ayudará será precisamente a eso, a olvidarnos completamente de como convertir los objetos en datos primitivos  para almacenarlos y viceversa.

Implementando un ORM

Lo primero que necesitamos en nuestro ORM es un gestor de la base de datos que se encargue de hacer muchas de las tareas repetitivas que acostumbramos a hacer con cada consulta.

Este gestor tendrá un esqueleto “base” y un adaptador para MySQL, que es el motor más común para usar con PHP.

El esqueleto del proveedor

Para que podamos abstraernos del motor de base de datos que vayamos a utilizar, necesitamos crear una clase que nos sirva de base para cada proveedor. De esta forma, si vamos a ejecutar una consulta, no nos tenemos que preocupar de llamar a mysqli_query o a mssql_query, llamamos a un método query que se encargue de ponerle luego el nombre que le corresponda.

Para ellos usaremos una clase abstract, que no implica tipo de lógica alguna, solo propiedades, métodos y parámetros que reciben éstos.

En $resource es donde guardaremos el recurso de la conexión que nos permite interactuar con los métodos del motor de la base de datos. El resto de métodos los veremos enseguida, cuando los implementemos en el motor MySQL.

El proveedor MySQL

La clase MySQLProvider extiende a la clase DatabaseProvider que acabamos de crear. En este caso estamos usando los métodos orientados a objetos de MySQL aunque sería equivalente el uso de funciones normales.

Veamos la lista

  • connect : Esta es la función que se conecta a la base de datos, en caso de existir un error en la conexión, lo almacena en el log de errores de PHP.
  • disconnect : Esta función se desconecta de la base de datos. 
  • getErrorNo : Esta función nos devuelve el número de error (en caso de haberlo) al haberse ejecutado una consulta o procedimiento.
  • getError : Esta función nos devuelve el mensaje de error (sin el número).
  • query : Esta función se encarga de realizar una consulta a la base de datos.
  • numRows : Esta función nos devuelve el número de filas seleccionadas, actualizadas, insertadas o eliminadas.

Ahora que ya tenemos todo listo, podemos pasar a la última clase, la que se encarga de realizar todas las acciones, con la que realmente interactuaremos directamente.

La capa de comunicación

 

Vamos paso a paso.

El constructor intenta saber si el proveedor que le estamos pasando, existe. En caso de que no sea así, lanzaremos una excepción, que podrá ser controlada posteriormente. Si todo va bien, crearemos un nuevo proveedor e intentaremos conectarnos a la base de datos. En caso de no poder, tendremos otra excepción.

En este caso, y especialmente con MySQL, cambiamos la codificación a UTF-8 para que todos los nombres y apellidos que vayan con tildes, sean bien tratados por el código, esto no es necesario.

La función getConnection es la encargada de obtener la conexión con la base de datos, siguiendo el patrón Singleton, en caso de que ya haya una conexión establecida, la devuelve, para ahorrar recursos, y en caso de que no, crea una nueva conexión.

replaceParams es llamada por la función que prepara la consulta y se encarga de devolver el parámetro que pasemos en cada interrogante, valiéndose de la función next() que va moviendo el cursor de la matriz por cada coincidencia.  Esto nos convierte una consulta de este tipo:

Con esta matriz

En esto:

La función prepare es quizá la más importante ya que se encarga de limpiar los parámetros y de colocarlos en su lugar, es la función que hace uso de replaceParams, la cual ya hemos definido, con el uso de preg_replace_callback(). Esta función escapa todos los parámetros para evitar los ataques de inyección SQL, añade las comillas a las cadenas, etc.

sendQuery es el método encargado de ejecutar las consultas y comprobar los errores que se pudieran cometer, en mi caso, los envío al log de errores de PHP, se pueden lanzar excepciones o realizar cualquier otra acción.  Esta función es usada por los dos métodos que veremos a continuación.

execute y executeScalar son los dos métodos que se encargan de procesar los datos obtenidos de las consultas y son los únicos que son públicos. Los métodos solo se diferencian en que execute, se encarga de cargar los datos en una matriz, mientras que el segundo devuelve solo la primera columna de la primera fila.

¡Todo a punto!

Ahora que ya lo tenemos todo a punto, ya tenemos todas las herramientas para comenzar a crear nuestro ORM. No obstante, vamos a ver cómo usarlo.

 

Creando las clases del ORM

Ahora que ya tenemos nuestro conector con la base de datos, vamos a crear la clase ORM, que extenderán el resto de clases. El ORM se apoyará totalmente en la clase que acabamos de crear.

Veamos el código

Lo primero que tenemos, son dos propiedades que son obligatorias para el resto de clases. El objeto que tiene la base de datos, y la tabla del modelo. En el caso de que estuviéramos hablando de una tabla de tiendas, ese valor tendría el valor tiendas por ejemplo. La clase, usa métodos de PHP 5.3.

Al construir la clase, lo que hace es obtener la conexión. Recordemos que gracias a que hicimos uso del patrón Singleton, varios objetos reutilizarán la misma clase.

Ahora tenemos dos métodos estáticos, esto es, que no es necesario crear una instancia para poder usarlos. El primero de ellos es find, que lo que hace es encontrar un elemento por su id. Devuelve un objeto de la misma clase, con todos los campos rellenos.

El siguiente método es all, que nos permite traernos todos los elementos de una tabla, y le pasamos un parámetro opcional de ordenación a la consulta. Devuelve una matriz de objetos.

El siguiente método, el método save, ya no es público. Este método está encargado de crear o actualizar un objeto en la base de datos.

¿Qué nos queda?

¡Ahh sí! ¡Implementar una clase!

Implementemos por ejemplo una clase Persona de forma sencilla:

Como podemos ver, la tabla es realmente sencilla. El constructor recibe los datos en una matriz. Llama al constructor padre, el ORM, y rellena los datos realizando validaciones sencillas.

De esta forma, podríamos hacer algo así:

Y obtendríamos algo como esto:

Y así quedaría ya implementado el ORM que podriamos utilizar para las diferentes tablas de nuestro sistema.

 

Librerias de PHP que utilizan ORM

Existen ya en PHP algunas librerias en las que ya esta implementado el ORM, aunque es mas recomendado implementar uno propio. Estas son algunas de esas librerias:

  • PHP Object Generator: es uno de los mas conocidos, y no en vano, ya que el generador online es excelente, uno ingresa el nombre del objeto y las columnas de la tabla a la que se desea mapear el objeto. Tiene soporte para PHP4 y PHP5.
  • Doctrine: esta realizada para correr únicamente en PHP5. Utiliza un sistema de queries personalizados que devuelven el objeto con la información de la base de datos:
  • Junction PHP: es una librería relativamente nueva (que todavía no está muy bien documentada). Se ve interesante, no alcancé a probarla aún.
  • ADOdb Active Record: ADOdb es una librería de abstracción muy reconocida desde hace años, y no podía faltar que alguien realizara un plugin (o algo así) que permina el manejo de ORM usando la misma librería para conexión. ADOdb Active Record, utiliza el método de Active Record y no difiere mucho de las otras alternativas.
  • EZPDO: es una de las mas completas y activamente desarrolladas librerias. Algo curioso de esta librería es que utiliza los comentarios para indicar relaciones y tipos de datos de cada objeto. Vale la pena tenerla en cuenta.
  • DB_DataObject: esta librería es un proyecto dependiente del framework PEAR, posee funciones genéricas para todo tipo de consulta desde los objetos sin tener que manipular demasiado los queries SQL. Es bastante avanzada.
  • Propel: es una de las mas antiguas y mejor documentada. Está muy completa y hasta incluye validación.