Ir al contenido principal

Patrón DAO en PHP (Data Access Object in PHP)

Este patrón es de gran ayuda a la hora de separar la manipulación de datos y acceso a la DB de la lógica propia de la aplicación.

Una versión simplificada de este patrón posee tres componentes principales:

  • El acceso a los datos
  • El objeto de transferencia de datos (value object)
  • El cliente que consume esos datos
Antes de presentar el diagrama de clases analicemos el problema el cual consiste en encontrar una forma de manipular los datos de manera tal que el modo de acceder a los mismos sea transparente al cliente que los consume. Esto quiere decir que el cliente no tiene la necesidad de conocer cómo se acceden a los datos (SQL, conectores, motor de base de datos, tipo de base de datos, etc.).

El objeto de acceso a los datos debe ser capaz de poder proveer lo que el cliente requiera y además el cliente debe poder utilizar cualquier otro objeto de acceso sin que esto afecte su lógica interna asegurando así que el sistema funcionará aún cuando se cambie de motor de base de datos, de conexión, e inclusive si se modifican las consultas SQL.

En resumen, en una situación trivial tendríamos que el cliente, supongamos una pantalla que lista todos los usuarios del sistema (por cuestiones prácticas asumiremos que al decir pantalla incluimos todos los componentes necesarios para que la misma sea funcional, es decir: vistas, controlladores, modelos, etc.), le solicita al objeto de acceso a datos que le provea todos los usuarios activos del sistema. Este a su vez le solicita a la fuente de datos (puede ser un archivo, una base de datos, o inclusive otro objeto) los datos que cumplen con el criterio establecido por el cliente.

El objeto de acceso a datos (a partir de ahora lo llamaremos DAO) retorna a la pantalla (Cliente) una colección de resultados que cumplieron con el criterio. Donde cada resultado es representado por un objeto de transferencia (Value Object ó Transfer Object. Resumidamente VO) que no es más que una clase donde cada atributo se corresponde con una columna de nuestra tabla (en el más simple de los casos). Esta colección de VOs es retornada al Cliente mediante el DAO y el Cliente manipula cada VO de acuerdo a sus necesidades ya sea de procesamiento (transformación de los datos en información para el usuario) o de presentación (simplemente visualización de los mismos).

Yendo más en profundidad, en el DAO podemos encontrar métodos que cuyo único propósito sea ejecutar una consulta a la base de datos, o una búsqueda dentro de un archivo. Dentro de este método pasará una de dos alternativas, si se obtuvieron resultados se deberá generar por cada uno un VO y añadirlo a una colección. Si no hubieron resultados se puede generar una colección vacía o retornar un valor nulo. Es recomendable generar una colección vacía para que luego el cliente pueda esperar siempre una colección que podrá tener cero o más elementos.

Siguiendo con nuestro ejemplo, tendríamos un DAO que llamaríamos UsersDAO que provee el método findActive(). De esta manera el Cliente podría tener una instancia de UsersDAO que utilizaría para invocar este método que a su vez devolvería una colección de VOs (la colección será un array, un contenedor del tipo Map o List. Dependiendo del lenguaje que se utilice esto puede variar)

Grafiquemos lo que acabamos de ver y luego veamos un ejemplo de código fuente.


Tengan en cuenta que este patrón puede ser implementado en cualquier lenguaje, pero para hacer más sencilla la explicación veremos el código en PHP.


class UsersDAO{

    public function findActive(){

      $users = array();
      $sql = "SELECT * FROM users WHERE status = 'Active'";
      // Asumimos que la conexión existe y que el objeto admite el método execute()
      // execute () devuelve false en caso de error y una colección objetos estándar por cada registro
      $result = $this->connection->execute( $sql );
      if( $result ){
          foreach( $result as $row ){
              $users[] = $this->recordsetToUserObject( $row );
          }
          return $users;
      }
    }

    public recordsetToUserObject( $row ){
      $user = new UserVO();
      $user->setUsername( $row->username );
      $user->setFullname( $row->fullname );
      return $user;
    }

    ...
}


class UserVO{
  
  private $username;
  private $fullname;

  // setters
  public setUsername( $username ){ $this->username = $username; }
  public setFullname( $fullname ){ $this->fullname = $fullname; }

  // getters
  ...
}


class Client{
  public function listActiveUsers(){
    $usersDAO = new UsersDAO();
    $users = $usersDAO->findActive();

    // Visualización de los usuarios ...
    ...
  }
}


Tips!

  1. Aplicar siempre un patrón. Cada problema que se presenta puede ser resuelto aplicando uno o más patrones, e inclusive adaptando los ya existentes. Esto ahorra mucho esfuerzo a la hora de implementar una solución y asegura un buen nivel de calidad en el producto final.
  2. Una tabla un DAO. Por cada tabla o fuente de datos se debería crear un DAO y un VO. Dependiendo de la relación que existan entre cada tabla se podrá crear un solo DAO para varias tablas.
  3. Esto es una guía. Pueden combinar este patrón con otros para cada parte del sistema que estén desarrollando. Habrá veces que este patrón no es muy óptimo y deberán realizar ajustes o inclusive utilizar otro patrón.
  4. Mantener la coherencia. Es fundamental más allá de los patrones que utilicen, mantener ciertos criterios de diseño para que aún implementando diferentes soluciones para el mismo problema el sistema tenga una coherencia razonable, esto ayudará a que todos los desarrolladores puedan seguir el código sin dificultades.
  5. No abusar de los casos excepcionales. Como en todo hay casos excepcionales que deberán ser resueltos de forma excepcional y hasta rompiendo la coherencia que pretendemos seguir. Sin embargo, se puede subsanar este quiebre en la coherencia simplemente agregando comentarios al código que brinden las explicaciones pertinentes de por qué se hizo de esa manera y no de otra. Esto evitará que el próximo que venga se encuentre con algo que no tiene sentido aparente y lo modifique para después darse cuenta que no era necesario cambiarlo. Pero contemplar casos excepcionales debe ser un último recurso siempre hay que indagar lo suficiente para descartar la posibilidad de incorporar dicho caso al resto y así evitar una lógica particular únicamente para ese caso.
  6. Reducir los métodos con demasiados parámetros. Al escribir las consultas SQL a veces se tiende a hacer una única consulta dinámica y según los parámetros pasados se va construyendo el filtro. Esto traten de evitarlo ya que si se produce un error en la consulta durante el uso normal se les hará dificil detectar de donde viene el problema ya que por todos el sistema se llama al mismo método con distintos parámetros. Tener dos queries aparentemente idénticas pero que varía un único parámentros puede ser la diferencia entre un sistema robusto y un sistema inestable.
  7. Usar clases especializadas. Tener pocas clases que hagan todo o tener muchas clases que hagan poco siempre fue, es y será una decisión que el desarrollador deberá tomar en algún momento de su vida. Si bien todo depende del tipo de proyecto en mi opinión es mejor tener muchas clases que hagan poco ya que esto facilitará el mantenimiento del sistema a lo largo del tiempo. No es lo mismo cargar una clase de 3 mil líneas para usar 10 métodos que cargar 5 clases de 100 líneas cada una que contienen únicamente esos 10 métodos.
  8. Comentar el código no es traducir el código. Cuando escriban comentarios en el código eviten comentar los IFs de la siguiente manera "Pregunta si $username es 'pepito', si no es entonces guardo el nombre en $currentUsername sino ..." Estos tipos de comentarios no sirven para nada. Un buen comentario explica en lenguaje natural lo que el código hace y no lo que el código dice. Tiene que estar dirigido a otro desarrollador diferente al creador de ese código, al propio creador y a cualquier persona que deba validar el cumplimiento de los requerimientos y que no necesariamente sabe leer código fuente.

Comentarios

  1. Hola interesante tu post, pero no me queda claro.
    Por ejemplo si tengo una tabla ARTICULOS, tengo que crear un ARTICULOSDAO donde estarán todos sus métodos.
    Y ARTICULOVO que me permitirá visualizarlo. O tengo que crear varios VO's según mi necesidad?
    Gracias.

    ResponderEliminar
  2. Diana, muchas gracias por tu comentario.

    La implementación de patrones siempre es a conveniencia y puede ser modificado según ésta.

    Dicho esto, en un sentido estricto del patrón, por cada tabla tendrías una clase DAO y una clase VO. Pensalo de la siguiente manera, la clase DAO es la que te brindará la facilidad de tener un único lugar dónde agrupar las consultas a la base de datos (dicho en la forma más simple), mientras que la clase VO es la representación de un registro de la base de datos a la cual le podés agregar comportamiento.

    En principio no sería necesario crear varias clases VO. A menos que necesites una distinción por tipo de artículo, lo cual se traduce en una clase VO por cada tipo de articulo, pero nuevamente no es requerido por el patrón sino que dependerá de tu necesidad o simplemente por comodidad.

    Espero haya aclarado tu duda y sino, por favor no dudes en escribir nuevamente proporcionando más detalles del problema para poder ayudarte.

    Saludos y gracias

    ResponderEliminar
    Respuestas
    1. Podrías recomendarme alguna bibliografía del tema?
      Estoy tratando de entender este manejo, entiendo lo del DAO pero no me queda muy claro lo del VO.
      Gracias.

      Eliminar
    2. Un buen libro sobre patrones es "UML y Patrones" de Craig Larman. Quizás este link más detallado te pueda a ayudar a comprender mejor el patrón
      http://www.oracle.com/technetwork/java/dataaccessobject-138824.html

      Eliminar
  3. Tengo una consulta con respecto al uso de los Value Objects con PHP5:
    1) Existe algún formato específico o recomendación para crear el VO de una tabla?

    2) Si dices que hay un DAO y un VO por cada tabla de la base de datos mi consulta es si mi tabla tiene 6 campos el array que captura la información del select es este:

    while($fila=$recordSet->FetchRow()) {
    $array[] = new TablaVO($fila['cam1'], $fila['cam2'], $fila['cam3'], $fila['cam4'], $fila['cam5'], $fila['cam6']);
    }
    Pero qué pasa si solo quiero trabajar con dos campos? Cómo trunco a los demás campos para que no me genere el error?

    ResponderEliminar
    Respuestas
    1. Hola David,

      1) No existe un formato específico, sin embargo la recomendación básica es que la clase VO tenga al menos un atributo por cada campo. Puedes encontrarte con que un campo en la tabla es una Foreign Key de otra por lo que ahi deberás decidir si en tu clase VO lo vas a tratar como un campo simple o si lo vas a tratar como un objeto que será otro VO correspondiente a la tabla relacionada. Este último escenario es mucho más complejo y quizás la solución en estos casos sea utilizar un ORM ( Object-Relational Mapping ).

      2) Para lograr lo que dices, sólo debes pensarlo de forma más general y versátil. Imagina que algunas tablas tienen 20 campos, por lo que no es muy óptimo ni buena práctica pasar 20 argumentos a un método. Asi es que yo te recomiendo es que utilices los métodos "accessors" de la clase VO, de esa manera puedes setear los valores de los atributos que desees, esto obliga a que en tu VO definas valores por defecto en el constructor para todos los atributos.
      Otro tip que te doy es que el VO no es un array, es un objeto.

      Entonces, modificando un poco tu código te quedaría algo así:

      $listObjects = array(); // Colección de objetos VO
      while($fila=$recordSet->FetchRow()) {
      $registroVO = new TablaVO();
      $registroVO->setFieldOne( $fila['cam1'] );
      $registroVO->setFieldTwo( $fila['cam2'] );
      $registroVO->setFieldThree( $fila['cam3'] );
      // Si quieres más campos usas el setter correspondiente

      $listObjects[] = $registroVO; // Agregas el objeto a la colección
      }

      Saludos

      Eliminar

Publicar un comentario

Entradas populares de este blog

Configurar Wi-Fi en Slackware

Ahora es común tener una notebook pero no lo es tanto el tener un linux en ella y mucho menos tener un Slackware en la notebook. Sin embargo, es posible tenerlo por lo que vale la pena dedicarle unas líneas a la configuración del Wi-Fi. Primero que nada hay que saber cuál es la placa de red inalambrica que posee la notebook, es decir su marca y modelo. Si ya está Linux instalado en la notebook solo se debe ejecutar el siguiente comando para saber cuál es la marca y modelo de la placa de red inalambrica: # lspci Este comando dará como resultado algo similar a esto: 09:00.0 Ethernet controller: Marvell Technology Group Ltd. Unknown device 4354 (rev 12) 0b:00.0 Network controller: Intel Corporation PRO/Wireless 3945ABG Network Connection (rev 02) En estas líneas se puede observar que la segunda hace referencia a la placa de red inalambrica que posee la notebook, en este caso una Intel PRO/Wireless 3945ABG . Con esta información lo que se debe hacer es buscar cuál es el módulo que se deb...

¿Qué hace un Licenciado en Sistemas?

Muchas veces, conocidos que no son informáticos, me han preguntado ¿qué hace un Licenciado en Sistemas? ...... " Buena pregunta " - sonaba en mi cabeza. Yo sé lo que yo quiero hacer en mi vida profesional pero realmente ¿qué perfil tiene un Licenciado en Sistemas? Es sencillo saber qué hace un médico, un veterinario, un ingeniero civil, etc. Por ejemplo si su perro se enferma ¿a quién llaman? si quieren construir un puente ... ¿a quién llaman? si ustedes están enfermos ¿a quién llaman? Ahora, si necesitan un sistema para su empresa ¿a quien llaman? ... ¿a un Lic. en Sistemas? ¿a un Ing. en Sistemas? ¿a un programador de aplicaciones? ¿a un analista en sistemas? ¿a un consultor informático? o ¿al amigo que se la pasa en internet y que sabe "todo" sobre computadoras e hizo un montón de cursos (word, excel, windows, redes, etc.)? Esta situación se presenta a menudo; es tan confuso que muchas veces hasta los mismos profesionales informáticos o entendidos en la materia ...

Paquetes para Slackware

Para aquellos que recién comienzan a utilizar Slackware , esta grandiosa distribución GNU/Linux una de las más antiguas, estables y seguras que existen; de seguro se habrán encontrado con la tarea de instalar algún programa extra y que no es distribuido junto con Slackware. Uno de los mejores sitios para encontrar software empaquetado para esta distro es Linuxpackages.net ; en este sitio podrán realizar búsquedas en versiones específicas de Slackware inclusive en la última versión liberada, la 12.1. Los paquete que aquí se encuentran ya han sido precompilados por lo que no deberán realizar los típicos pasos "./configure","make" y "make install"; sino que solamente podrán utilizar las herramientas installpkg, pkgtool o kpackage para instalar los paquetes descargados desde este sitio. Claro que no siempre encontrarán el soft que buscan en este excelente sitio, en esos casos siempre es bueno recurrir a los sitios del autor del soft en cuestión para obtener l...