miércoles, 25 de febrero de 2009

Otra forma de acceder a la base de datos con PHP


En un de los ejemplos anteriores creamos una clase en PHP para generar documentos XML que contuvieran los resultados de las consultas a una base de datos, en esta ocasión, crearé otra clase en PHP pero que, -como dice el título de este post- se encargará de manejar el acceso a la base de datos, ¿por qué? primero que nada recordemos que en las escuela nos hablaron de la importancia de separar nuestros proyectos de programación con bases de datos en tres capas: presentación, negocio y acceso a datos, y no solo porque de esta forma se ven más elaborados sino por el hecho de que así ya partimos de un muy buen principio de diseño, que ayudará a lograr un mejor proyecto de desarrollo. Por otro lado encontramos que al trabajar con capas nos ahorramos mucho esfuerzo de teclear código, además de que se facilita el mantenimiento de nuestros programas.


El gran beneficio: La principal ventaja que encuentro al utilizar capas en nuestro desarrollo es que al producirse cambios en los requerimientos de nuestros sistemas, realizar modificaciones al código resulta mucho más sencillo. Por ejemplo, si tenemos cambiar de sistema manejador de base de datos de MySQL a MS SQL Server, dentro de nuestro código solo tendremos que realizar cambios en la capa de acceso a bases de datos, dejando prácticamente intacto el resto del código, ya que veamos el ejemplo se entenderá bien como es esto.

Puntos previos a tener en cuenta

Como consideraciones previas, tendremos en cuenta que las responsabilidades que recaerán sobre esta clase son:
  • Conectarse a la base de datos.
  • Acceder a la base de datos.
  • Ejecutar consultas SQL.
  • Insertar registros.
  • Actualizar registros.
  • Borrar registros.
  • Desconectarse de la base de datos.
Al momento de traducir estos requerimientos a la programación, irán surgiendo una serie de campos y métodos que se utilizarán para realizar las tareas necesarias.

Así, las funciones mysql (mysql_connect, mysql_select_db, mysql_query, etc.) que antes se utilizaban dentro del código, serán encapsuladas en esta serie de métodos.

Nota: Al referirme a las clases y objetos utilizo términos que aprendí durante mi incursión en el lenguaje Java, por lo que surgen algunas diferencias con los que manejan los desarrolladores más asiduos de .Net, en el que se refieren a propiedades y atributos de las clases en lugar de campos como en Java, pero ya que el modelo de programación con objetos de PHP5 se apegó más a Java creo que esta terminología queda bastante bien :D

Construyamos nuestra nueva clase para acceso a datos

Siguiendo con el trabajo, ahora les presento la clase que idee para cumplir el objetivo de manejar el acceso a una base de datos -un poco larga la verdad :P-:


<?php
//Clase Acceso a Datos
class AccesoADatos{
//Campos de la clase
private $conexion;
private $ipServidor;
private $usuario;
private $contrasenia;
private $baseDeDatos;
private $resultados;
private $mensaje;
private $registrosLeidos;
//Constructor de la clase
function __construct($ipServidor, $usuario, $contrasenia, $baseDeDatos) {
$this->ipServidor = $ipServidor;
$this->usuario = $usuario;
$this->contrasenia = $contrasenia;
$this->baseDeDatos = $baseDeDatos;
$this->conexion = false;
$this->registrosLeidos = 0;
$this->resultados = array();
$this->mensaje = "";
}
//Destructor de la clase Acceso a Datos
function __destruct() {
$this->liberaResultado();
$this->cierraConexion();
}
//Abre la conexión con el servidor de base de datos. Devuelve falso si hubo error
public function abreConexion() {
$estadoDeConexion = true;
$this->conexion = @mysql_connect($this->ipServidor,$this->usuario,$this->contrasenia);
if(mysql_select_db($this->baseDeDatos,$this->conexion)) {
mysql_query("SET NAMES 'utf8'",$this->conexion);
$estadoDeConexion = true;
}
else {
$estadoDeConexion = false;
$this->estableceError("No fue posible acceder a la base de datos, consulte con el administrador de la base de datos para solucionarlo.");
}
return $estadoDeConexion;
}
//Establece el mensaje de error e inicializa los contadores de resultados
private function estableceError($tipo) {
$this->registrosLeidos = 0;
$this->mensaje = "Error de acceso a datos: ".$tipo;
}
//Ejecuta una consulta SQL contenida en una cadena
public function consulta($consultaSQL, $idResultado = "") {
$estado = true;
if($this->conexion) {
$r = mysql_query($consultaSQL,$this->conexion);
if(mysql_errno() > 0) {
$this->estableceError("No se completó la operación solicitada ".mysql_error());
$estado = false;
}
else
$this->registrosLeidos = mysql_num_rows($r);
if($idResultado == "")
$this->resultados[0] = $r;
else
$this->resultados[$idResultado] = $r;
}
else {
$this->estableceError("Conexión con la base de datos no establecida.");
$estado = false;
}
return $estado;
}
//Borra él o los registros de una tabla que cumplan los parámetros recibidos
public function borraRegistro($tabla, $condicion = "1") {
$estado = true;
if($this->conexion) {
$comando = "DELETE FROM `$tabla` WHERE $condicion";
mysql_query($comando,$this->conexion);
if($this->numeroDeError = mysql_errno() > 0) {
$this->estableceError("No se completó la operación solicitada ".mysql_error());
$estado = false;
}
}
else {
$this->estableceError("Conexión con la base de datos no establecida.");
$estado = false;
}
return $estado;
}
//Inserta un registro en la tabla específicada utilizando como valores el arreglo de datos recibido
public function insertaRegistro($tabla, $datos) {
if($this->conexion) {
$comando = "INSERT INTO `$tabla` (";
foreach($datos as $llave=>$dato) {
$campos[]= "`$llave`";
$valores[] = $dato;
}
$comando.= implode(",",$campos).") VALUES('".implode("','",$valores)."')";
mysql_query($comando,$this->conexion);
if(mysql_errno($this->conexion) > 0)
$this->estableceError("No se completó la operación solicitada ".mysql_error());
return mysql_affected_rows($this->conexion);
}
else {
$this->estableceError("Conexión con la base de datos no establecida.");
return 0;
}
}
//Actualiza el registro de una tabla utilizando los párametors recibidos
public function actualizaRegistro($tabla, $nuevosDatos, $campoLlave) {
if($this->conexion) {
$comando = "UPDATE `$tabla` SET ";
foreach($nuevosDatos as $llave=>$dato)
$nuevosValores[] = "`$llave` = '$dato'";

foreach($campoLlave as $llave=>$dato)
$condicion[] = "`$llave` = '$dato'";
$comando.= implode(",",$nuevosValores)." WHERE ".implode(" AND ",$condicion);
mysql_query($comando,$this->conexion);
if(mysql_errno($this->conexion) > 0)
$this->estableceError("No se completó la operación solicitada ".mysql_error());
return mysql_affected_rows($this->conexion);
}
else {
$this->estableceError("Conexión con la base de datos no establecida.");
return 0;
}
}
//Regresa el contenido de un registro del resultado en un arreglo
public function devuelveArreglo($idResultado = "") {
if($idResultado=="")
$idResultado = 0;
if($this->resultados[$idResultado])
return mysql_fetch_assoc($this->resultados[$idResultado]);
else
return false;
}
//Devuelve el número de registros leidos por la última consulta
public function devuelveRegsLeidos() {
return $this->registrosLeidos;
}
//Devuelve el utimo mensaje de error generado
public function devuelveError() {
return $this->mensaje;
}
//Libera de la memoria del servidor el resultado de cualquier consulta realizada
public function liberaResultado() {
foreach($this->resultados as $llave=>$r) {
if($this->resultados[$llave])
mysql_free_result($this->resultados[$llave]);
}
$this->resultados = array();
}
//Cierra la conexión con el servidor de base de datos
public function cierraConexion() {
if($this->conexion){
mysql_close($this->conexion);
$this->conexion = false;
}
}
}
?>

La clase recibe el nombre de AccesoADatos, y dentro de esta clase aparece un primer método identificado como __construct, que es el método constructor de la clase, es llamado por PHP al momento de instanciarla. En caso de no declararlo, PHP llamaría a un constructor por defecto que poseen todas las clases, como en el caso de la clase ConsultaXML que programamos en uno de los posts anteriores.
function __construct($ipServidor, $usuario, $contrasenia, $baseDeDatos) {
$this->ipServidor = $ipServidor;
$this->usuario = $usuario;
$this->contrasenia = $contrasenia;
$this->baseDeDatos = $baseDeDatos;
$this->conexion = false;
$this->registrosLeidos = 0;
$this->resultados = array();
$this->mensaje = "";
}


Dentro del constructor se reciben los parámetros necesarios para realizar la conexión con el servidor de base de datos, y para acceder a la base de datos seleccionada. Además, se inicializan los campos que se utilizarán dentro de los métodos de la clase.

En el siguiente método encontramos el identificador __destruct:

function __destruct() {
$this->liberaResultado();
$this->cierraConexion();
}


El cual representa el destructor de la clase que es llamado automáticamente por PHP, al momento que todas las referencias a un objeto de la clase AccesoADatos sean removidas o cuando el objeto sea explícitamente destruido, o en cualquier orden en la finalización de la ejecución. Dentro de éste método se liberará la memoria de cualquier resultado de las consultas realizadas, para finalmente cerrar la conexión con el servidor de base de datos.

El método abreConexion() realiza la conexión con el servidor de base de datos MySQL, utilizando los parámetros $ipServidor, $usuario y $constrasenia, establecidos en el constructor de la clase.

mysql_connect($this->ipServidor,$this->usuario,$this->contrasenia);
if(mysql_select_db($this->baseDeDatos,$this->conexion)) {
mysql_query("SET NAMES 'utf8'",$this->conexion);
$estadoDeConexion = true;
}

Inmediatamente después accede a la base de datos especificada por el campo $baseDeDatos, y finalmente establece que el juego de caracteres que se utilizará en las consultas es UTF-8 a través de la consulta "SET NAMES utf8".

Todos los mensajes de error que se generen con las operaciones de acceso a la base de datos se establecerán con el método estableceError(), y estos mensajes podrán ser consultados desde fuera de la clase a través del método devuelveMensaje().

El método consulta(), recibe una cadena dentro del parámetro $consultaSQL que representa una consulta SQL para ejecutarla con la función mysql_query(),

$r = mysql_query($consultaSQL,$this->conexion);
if(mysql_errno() > 0) {
$this->estableceError("No se completó la operación solicitada ".mysql_error());
$estado = false;
}
else
$this->registrosLeidos = mysql_num_rows($r);
if($idResultado == "")
$this->resultados[0] = $r;
else
$this->resultados[$idResultado] = $r;


almacenando el resultado de la consulta dentro del arreglo $resultados, identificandola con el parámetro $idResultado que puede ser tanto una cadena como un valor numérico. En caso de que no se reciba un valor que identifique el resultado, este se almacena dentro del arreglo como el elemento 0 en el índice.

El método borraRegistro() eliminará uno o más registros en una tabla especificada, dependiendo de los parámetros que reciben, dentro de los que se incluye el nombre de la tabla y la condición del comando SQL.

El métodos insertaRegistro(), realiza la inserción de un registro en la tabla especificada utilizando los valores contenidos en el arreglo asociativo $datos, el cual debe poseer el formato 'llave'=>"valor", donde la llave representa el nombre del campo de la tabla y el valor es el dato que será insertado en este campo.

$comando = "INSERT INTO `$tabla` (";
foreach($datos as $llave=>$dato) {
$campos[]= "`$llave`";
$valores[] = $dato;
}
$comando = $comando.implode(",",$campos).") VALUES('".implode("','",$valores)."')";



Utilizando el ciclo foreach y la función implode para construir el comando INSERT que será ejecutado.

El método actualizaRegistro() es similar a insertaRegistro(), solo que además del nombre de la tabla y del arreglo de datos, se recibe segundo arreglo asociativo llamado $campoLlave, que se utiliza para identificar él o los registros que serán actualizados por este método.

El método devuelveArreglo(), nos permite acceder a cada uno de los renglones del resultado de una consulta identificado por $idResultado, almacenado este renglón dentro de un arreglo asociativo por medio de la función mysql_fetch_assoc.

Con este código crearemos el archivo AccesoADatos.php, que a partir de ahora utilizaremos en los siguientes ejemplos para acceder a nuestras bases de datos. Cómo una muestra del uso de esta nueva clase, refactorizaremos el código PHP del último ejemplo, es decir el archivo consultaMySQL.php, para generar un nuevo archivo PHP que posea con la siguiente contenido:


<?php
require("ConsultaXML.php"); //Referencia al archivo que contiene la clase ConsultaXML
require("AccesoADatos.php");//Referencia al archivo que contiene la clase AccesoADatos
$consultaXML = new ConsultaXML();//Creación del documento XML
//Parámetros para la conexión a la base de datos
$servidor = "localhost";
$usuario = "root";
$contrasenia = "[TuConstraseña]";
$baseDeDatos = "obras_literarias";
$acceso = new AccesoADatos($servidor,$usuario,$contrasenia,$baseDeDatos);
if($acceso->abreConexion()) { //Accedemos a la base de datos
//Comprobamos que los datos se enviaron correctamente
if(isset($_POST['txtNombre']) && ($nombre = stripslashes(trim($_POST['txtNombre'])))!="") {
$consulta = "SELECT * FROM autores WHERE Nombre LIKE '%".addslashes($nombre)."%'";
if($acceso->consulta($consulta)) {//Realizamos la consulta y comprobamos el resultado
if($acceso->devuelveRegsLeidos() > 0){//Comprobamos si se encontraron registros
//Creamos el documento XML con el resultado de la consulta
$consultaXML->iniciaDocumento();
$consultaXML->startElement("Tabla");
$consultaXML->writeAttribute("nombre","autores");
while($renglon = $acceso->devuelveArreglo()) {//Extrae un renglon del resultado como un arreglo asosciativo
$consultaXML->creaRegistroDesdeArreglo($renglon, "Id");
}
$consultaXML->endElement();
$consultaXML->salida();
}
else{
$mensajeDeError = "Autor no encontrado";
}
}
else {
$mensajeDeError = $acceso->devuelveError();
}
}
else {
$mensajeDeError = "Datos imcompletos";
}
}
else {
$mensajeDeError = $acceso->devuelveError();
}
if($mensajeDeError != "") {//En caso de que exista algún mensaje de error lo devolvemos como respuesta
$consultaXML->iniciaDocumento("Error");
$consultaXML->text($mensajeDeError);
$consultaXML->salida();
}?>

El funcionamiento seguirá siendo exactamente el mismo, pero como vemos la cantidad de líneas de código y de variables se reduce, gracias al encapsulamiento de la clase. Y la parte más importa es, que nos abstraemos de la mayoría de los detalles en cuanto al acceso a la base de datos, por lo que en caso de cambiar de sistema manejador de base de datos, o agregar nuevas funcionalidades al acceso a datos, solo deberemos modificar esta clase.

En los siguientes ejemplos veremos como utilizar el resto de los métodos la clase AccesoADatos, programando una interface en Ajax y Javascript mucho más completa.

Hasta la próxima.

4 comentarios:

KhRoNo_AnGeL dijo...

que onda beto... veo que utilizas un a clase para el accesos a los datos para refactorizar, como tu lo mencionas... pero últimamente eh aprendido algo... "una clase para le manejo de los datos, no es la capa de acceso a datos"... para tener una capa de acceso a datos bien definida, tienes que desligar a tu arquitectura de negocios totalmente de la responsabilidad de acceder a los dotas, de conocer la estructura de estos, su origen, su formato, y por tanto del conocer algún lenguaje de consultas (sql, oracle, xml, texto).... ¿por que es esto? por que así es mas natural alcanzar "Los beneficios"... Para un comportamiento mas natural .. tienes que ver also datos como objetos sin un comportamiento ni estado determinado... haciendo una analogia... imagina que tu beto eres la capa de negocios de un sistema... beto necesita saber el numero de teléfono de steve jobs... ¿Que haces?.. hablas al 040 y le dices a la operadora (capa de acceso a datos) DameElTelefonoDe("Steve Jobs") .... a ti no te importa si la operadora tiene que buscar ne la base de datos local, nacional, internacional, o un pais determinado... no eso no importa la responsabilidad de ella es darte el numero... ahora.. tampoco importa si la empleada tien que desencriptar la info... o si esta en una tabla que se llama "Directorio_Internacional", no.... beto lo unico que quiero es el telefono de Steve Jobs y no le importa como le haga la operadora.... asi es como debe de idealizarce una capa de acceos a datos... mas natural... XD...

KhRoNo_AnGeL dijo...

ahhhs se me olvidaba..... algo que es parte de mi concepción de una arquitectura multicapa....No hay que confundir... refactorizacion de código con la arquitectura en capas... son cosas muy diferentes... estan en contextos diferentes y etapas del ciclo de viso muy opuestas.. jejej

KhRoNo_AnGeL dijo...

PD.. we.. no te molestes si critique tu articulo, pero ambos sabemos que la mejor forma de mejor es retroalimentandos.... así como yo aprendo muchas cosas de ti... puede ser al revez también ¿o no?

Anónimo dijo...

Buenos comentarios, es ciento que la capa de acceso a datos es algo más grande aún, que le permita a las aplicaciones de las capas superiores abstraerse de los detalles relacionados con la base de datos. De hecho a la clase programada, que es una versión simplificada de la que utilizo en la práctica le vendría mejor tener otro nombre, y tendría que complementarse con otra serie de clases que funcionen como intermediarios entre la capa de negocio y la de acceso a datos.

Publicar un comentario