1.4.1. Capa de abstraccion del motor de la base de datos

Parece evidente que, para la inmensa mayoría de aplicaciones basadas en PHP, el uso de su librería nativa de acceso a bases de datos va a condicionar el SGBD a usar con la aplicación. En aplicaciones comerciales, o que no pueden ni desean estar cerradas a un único motor, no será imprescindible disponer de unas funciones que encapsulen la comunicación con el SGBD y que sean independientes de éste en las interfaces que ofrecen, mientras que internamente llamarán a las funciones nativas del SGBD concreto con que se esté trabajando en cada momento.

Versiones
La versión 1.6.8. era la más actualizada en el momento de elaboración de este material
(finales de 2004).

Así pues, y buscando en PEAR, encontramos el módulo ‘DB’, una capa de abstracción y encapsulamiento de la comunicación con el SGBD. Al tener que incorporar todas las funcionalidades de los motores que soporta, el resultado será siempre el mínimo conjunto de prestaciones comunes a todos los SGBD. Las prestaciones más destacadas que ofrece la versión actual 1.6.8 son las siguientes:

•    Interfaz orientada a objetos.

•    Una sintaxis común para identificar SGBD y cadenas de conexión.

•    Emulación de “sentencias preparadas” en los motores que no las soportan.

•    Códigos de errores comunes.   

•    Emulación de secuencias o autoincrementos en SGBD que no los soportan.

•    Soporte para transacciones.

•    Interfaz para obtener información del metadato (información sobre la tabla o la base de datos).

•    Compatible con PHP4 y PHP5.

•    Motores soportados: dbase, fbsql, interbase, informix, msql, mssql, mysql, mysqli, oci8, odbc, pgsql, sqlite y sybase.

1 <?php
2 // Incluimos la librería una vez instalada mediante PEAR
3 require_once `DB.php´;
4
5 // Creamos la conexión a la base de datos, en este caso PostgreSQL
6 $db =& DB::connect(`pgsql://usuario:password@servidor/basededatos´);
7
8 // Comprobamos error en la conexión
9 if (DB::isError($db)) {
10  die($db->getMessage());
11 }
12
13 // Realizamos la consulta:
14 $res =& $db->query(`SELECT * FROM clients´);
15
16 // Comprobamos que la consulta se ha realizado correctamente
17 if (DB::isError($res)) {
18 die($res->getMessage());
19 }
20
21 // Iteramos sobre los resultados
22 while ($row =& $res->fetchRow()) {
23    echo $row[0] . "\n";
24 }
25
26 // Liberamos la hoja de resultados
27 $res->free()
28
29 // Desconectamos de la base de datos
30 $db->disconnect();
31 ?>

La estructura del código y hasta la sintaxis de las sentencias es similar a los ejemplos nativos vistos anteriormente, exceptuando las partes de las sentencias que hacían referencia al motor de base de datos en particular.

A continuación, vamos a avanzar por el código ampliando la información sobre cada paso. La conexión se especifica mediante una sintaxis de tipo DSN (data source name). Los DSN admiten multitud de variantes, dependiendo del motor al que nos conectemos, pero en casi todos los casos, tienen la forma siguiente:

motorphp://usuario:contraseña@servidor/basededatos?opcion=valor

 

•  Conexión a MySQL 

mysql://usuario:password@servidor/basededatos

•  Conexión a MySQL a través de un socket UNIX:

mysql://usuario:password@unix(/camino/al/socket)/basededatos

•  Conexión a PostgreSQL

pgsql://usuario:password@servidor/basededatos

•  Conexión a PostgreSQL en un puerto específico:

pgsql://usuario:password@tcp(servidor:1234)/basededatos

 

En cualquier llamada a un método del paquete DB, éste puede devolver el objeto que le corresponde (una hoja de resultados, un objeto representando la conexión, etc.) o bien un objeto que represente el error que ha tenido la llamada. De ésta manera, para comprobar los errores que puede originar cada sentencia o intento de conexión, bastará con comprobar el tipo del objeto devuelto:

8 // Comprobamos error en la conexión
9 if (DB::isError($db)) {
10  die($db->getMessage());
11 }

La clase DB_Error ofrece varios métodos. A pesar de que el más utilizado es getMessage(), getDebugInfo() o getCode() pueden ampliar la información sobre el error.

Para realizar consultas, disponemos de dos mecanismos diferentes:

•    Enviar la consulta directamente al SGBD.

•    Indicar al gestor que prepare la ejecución de una sentencia SQL y posteriormente indicarle que la ejecute una o más veces.

En la mayoría de los casos, optaremos por el primer mecanismo y podremos proceder como sigue:

13 // Realizamos la consulta:
14 $res =& $db->query(`SELECT * FROM clients´);
Ejemplo
Pensemos en un conjunto de actualizaciones o inserciones seguidas o en un conjunto de consultas donde vamos cam- biando un intervalo de fechas o el identificador del cliente sobre el que se realizan.

En ocasiones nos podemos encontrar ejecutando la misma consulta varias veces, con cambios en los datos o en el valor de las condiciones. En estos casos, es más indicado utilizar el segundo mecanismo.

Supongamos que tenemos que insertar un conjunto de clientes en nuestra base de datos. Las sentencias que ejecutaríamos serían parecidas a las siguientes:

 

 

INSERT INTO Clientes (nombre, nif) VALUES (‘José Antonio Ramírez’,’29078922Z’);
INSERT INTO Clientes (nombre, nif) VALUES (‘Miriam Rodríguez’,’45725248T’);
...

En lugar de esto, podemos indicarle al motor de la base de datos que prepare la sentencia, del modo siguiente:

$sth = $db->prepare(`INSERT INTO Clientes (nombre,nif) VALUES (?,?)´);

Utilizaremos la variable $sth que nos ha devuelto la sentencia prepare cada vez que ejecutemos el query:

$db->execute($sth, `José Antonio Ramírez´,`29078922Z´);
$db->execute($sth, `Miriam Rodríguez´,`45725248T´);

 

También podemos pasar una array con todos los valores:

 

$datos = array(`Miriam Rodríguez´,`45725248T´);
$db->execute($sth, $datos);

Y tenemos la opción de pasar una array de dos dimensiones con todas las sentencias a ejecutar, mediante el método executeMultiple():

$todosDatos = array(array(`José Antonio Ramírez´,`29078922Z´),
              array(`Miriam Rodriguez´,`45725248T´));
$sth = $db->prepare(`INSERT INTO Clientes (nombre,nif) VALUES (?, ?)´);
$db->executeMultiple($sth, $todosDatos);

A continuación, examinaremos los métodos de iterar sobre los resultados. En el ejemplo inicial hemos utilizado la función fetchRow() de la manera siguiente:

21 // Iteramos sobre los resultados
22 while ($row =& $res->fetchRow()) {
23    echo $row[0] . "\n";
24 }

Pero también disponemos de la función fetchInto(), que recibe como parámetro el array donde queremos que se almacene el resultado:

21 // Iteramos sobre los resultados
22 while ($res->fetchRow()) {
23   echo $row[0] . "\n";
24 }

Tanto fetchRow() como fetchInto() aceptan otro parámetro para indicar el tipo de estructura de datos que va a almacenar en $row:

•    DB_FETCHMODE_ORDERED: es la opción por defecto. Almacena el resultado en una array con índice numérico.

•    DB_FETCHMODE_ASSOC: almacena el resultado en una array asociativa en el que las claves son el nombre del campo.

•    DB_FETCHMODE_OBJECT: almacena el resultado en un objeto donde dispondremos de atributos con el nombre de cada campo para obtener el valor en cada iteración.

21 // Iteramos sobre los resultados en modo asociativo
22 while ($res->fetchInto (($row,DB_FETCHMODE_ASSOC)) {
23    echo $row[`nombre´] . "\n";
24    echo $row[`nif´] . "\n";
25 }

o bien:

21 // Iteramos sobre los resultados en modo objeto
22 while ($res->fetchInto (($row,DB_FETCHMODE_OBJECT)) {
23    echo $row->nombre . "\n";
24    echo $row->nif . "\n";
25 }

La función fetchInto() acepta un tercer parámetro para indicar el número de fila que queremos obtener, en caso de que no deseemos iterar sobre la propia función:

21 // Obtenemos la tercera fila de la consulta
22 $res->fetchInto ($row,DB_FETCHMODE_ASSOC,3); {
23    echo $row->nombre . "\n";
24    echo $row->nif . &"\n";

Hay otros métodos para obtener diferentes vistas del resultado como los siguientes:

•    getAll(): obtiene una array de dos dimensiones con toda la hoja de resultados. Tendría una forma como la siguiente:

Array
(
  [0] => Array
    (
     [cf] => Juan
     [nf] => 5
     [df] => 1991-01-11 21:31:41
    )
  [1] => Array
(
  [cf] => Kyu
  [nf] => 10
  [df] => 1992-02-12 22:32:42
)
)

 

•    getRow(): devuelve sólo la primera fila de la hoja de resultados.

•    getCol(): devuelve sólo la columna indicada de la hoja de resultados.

De la misma manera que en las librerías nativas, hay métodos que proporcionan información sobre la consulta en sí:

•    numRows(): número de filas de la hoja de resultados.

•    numCols(): número de columnas de la hoja de resultados.

•    affectedRows(): número de filas de la tabla afectadas por la sentencia de actualización, inserción o borrado.