En este primer tutorial de PHP vamos a explicar cómo crear de una forma sencilla y eficaz una capa de comunicación con tus bases de datos, pudiendo dejar así de lado pesadas librerías sobre las cuales no tienes control total y abstraer tu código independientemente del motor de base de datos.
Introducción
Hasta hace muy poco, usaba una librería de PHP llamada ADOdb Lite para abstraerme un poco de la base de datos en mis proyectos. En realidad, no era por ser vago y no querer desarrolar mi propio código, si no que la comencé a usar porque siempre uso procedimientos y funciones de MySQL en mis modelos y las funciones de la librería nativa de PHP no me permitían hacer llamadas a estos (no se ahora, antes no).
Desde hace unos pocos años la empecé a utilizar y desde entonces la carpeta adodb_lite se iba copiando de proyecto en proyecto casi de manera inconsciente. Hasta que un día me encontré con la librería Mysqli (MySQL Improved Extension) que es una librería nativa de PHP que nos permite hacer muchas más cosas, entre otras, ejecutar procedimientos de la base de datos
.
Mediante la librería nativa de PHP Mysqli podemos ejecutar procedimientos definidos previamente en nuestra base de datos.
Fue en ese momento cuando decidí borrar la librería AdoDB Lite para crear mi propio código de comunicación con la base de datos. La verdad, que en esto, se puede hacer desde la cosa más sencilla (lo que vamos a ver en este tutorial) hasta cosas realmente complejas como doctrine.
Supongo que es cuestión de gustos y del tipo proyecto…a mí personalmente me gustan las cosas sencillas y potentes, no me importa escribir un poco más de código mientras tenga control total con la aplicacion, ¿vosotros que preferís?.
Bueno pues… ¡vamos a ello!
¿Qué vamos a hacer?
Antes de nada os explicaré un poco qué características va a tener nuestra capa de comunicación:
- Abstracción del tipo de proveedor: esto es algo básico, nunca sabes cuándo puedes cambiar de tipo de servidor (base de datos). A mí ya me ha pasado en un proyecto tener que cambiar de PostgresSQL a MySQL. Por desgracia fue en mi primer proyecto y todavía no sabía estas cosas, por lo tanto, a cambiar código por todos lados.
- Instancia única: nuestro código será construído usando el patrón de diseño Singleton, lo que nos garantizará que sólo se establece una conexión con la base de datos, ¿para qué más?.
- Autocargar en array: si ejecutamos consultas de extracción de filas, nuestra capa nos devolverá dicha información cargada en un array.
- Parámetros en consultas: se gestionarán de forma fácil los parámetros que se envían en las consultas.
Paso 1: Crear la estructura de nuestro proveedor
Como ya he comentado anteriormente, nuestra capa será abstracta al tipo de proveedor de bases de datos. Para cumplir con esto, necesitamos crear una clase base que defina qué es un proveedor y qué funciones va a poder llevar a cabo, luego, cada tipo de proveedor que necesitemos, se extenderá de dicha clase. Lo vemos con este ejemplo:
abstract class DatabaseProvider{
/*Definicion de que métodos y propiedades van a tener todos los proveedores*/
}
class MySqlProvider extends DatabaseProvider
{
/*Implementación de los métodos definidos en la clase base*/
}
class SQLServerProvider extends DatabaseProvider
{
/*Implementación de los métodos definidos en la clase base*/
}
No hace falta entender mucho de POO para entederlo, ¿no?. Simplemente se define una clase abstracta para definir lo que todos sus descendientes deben de implementar, así nos aseguramos de que todos los proveedores que hagamos tengan la misma estructura.
De modo que si hacemos algo como $ob->foo(), tanto nos dá si $ob es de la clase MysqlProvider o SQLServerProvider, sabemos que dicho método tiene que existir y que en teoría debería hacer lo mismo en un motor u otro de base de datos.
Al crear nuestra propia capa de comunicación con la base de datos, no importa cuál sea el motor de base de datos: MySQL, PostgreSQL, Oracle… no tendremos que rescribir todo el código creado anteriormente, será abstracta a nuestro motor.
Pues bien, una vez entendida la teoría, vamos a definir realmente nuestra clase base para los proveedores, yo he incluído el siguiente esquema, que es el básico: ejecución de consultas y control de errores, vosotros lógicamente lo podréis ampliar cuanto queráis
.
abstract class DatabaseProvider
{
//Guarda internamente el objeto de conexión
protected $resource;
//Se conecta según los datos especificados
public abstract function connect($host, $user, $pass, $dbname);
//Obtiene el número del error
public abstract function getErrorNo();
//Obtiene el texto del error
public abstract function getError();
//Envía una consulta
public abstract function query($q);
//Convierte en array la fila actual y mueve el cursor
public abstract function fetchArray($resource);
//Comprueba si está conectado
public abstract function isConnected();
//Escapa los parámetros para prevenir inyección
public abstract function escape($var);
}
Como podéis ver, los métodos son todos abstractos para que se implementen en sus clases hijas.
Paso 2: Crear nuestro proveedor
Una vez creada la base, tenemos que crear el proveedor. Para ello tenemos que heredar de la clase anterior e implementar los métodos:
class MySqlProvider extends DatabaseProvider
{
public function connect($host, $user, $pass, $dbname){
$this->resource = new mysqli($host, $user, $pass, $dbname);
return $this->resource;
}
public function getErrorNo(){
return mysqli_errno($this->resource);
}
public function getError(){
return mysqli_error($this->resource);
}
public function query($q){
return mysqli_query($this->resource,$q);
}
public function fetchArray($result){
return mysqli_fetch_array($result);
}
public function isConnected(){
return !is_null($this->resource);
}
public function escape($var){
return mysqli_real_escape_string($this->resource,$var);
}
}
Me he enfocado en el proveedor de MySQL, que supongo que es el más usado. Como podéis ver, todas las funciones definidas en nuestra clase base, tienen su correspondiente función en la librería Mysqli, por lo que se puede decir que funciona a modo de wrapper.
Paso 3: Crear nuestra capa de comunicación
Lo hecho hasta ahora está muy bien, pero lo único que nos permite es abstraer el tipo de proveedor, el resto de puntos que he comentado al inicio todavía están si cumplir. Esto es porque aún nos falta por crear una nueva clase que se encargará de enviar las consultas y gestionar las conexiones y respuestas del servidor.
Os pongo la estructura de la clase para que le echéis un ojo:
class DatabaseLayer
{
//Almacena internamente el proveedor
private $provider;
//Usado para las callbacks, se explica luego
private $params;
//Almacena la instancia para el Singleton
private static $_con;
//Constructor privado
private function __construct($provider){}
//Funcion del Singleton que devuelve o crea la instancia
public static function getConnection($provider){}
//Funcion callback, se explica luego
private function replaceParams($coincidencias){}
//Se encarga de poner los parámetros en su sitio
private function prepare($sql, $params){}
//Envia la consulta al servidor
private function sendQuery($q, $params){}
//Ejecuta una consulta, extrayendo solo la primera columna de la primera fila
public function executeScalar($q, $params=null){}
//Ejecuta una consulta y devuelve un array con las filas
public function execute($q, $params=null){}
}
Antes de continuar, es importante ver cuál será el formato de las consultas. Para que el código permanezca limpio y poder escapar los parámetros (para prevenir inyecciones maliciosas), los parámetros serán especificados en un array, de forma que serán insertados en la consulta reemplazando al caracter ‘?’.
Veamos un ejemplo:
$bd->execute("SELECT * FROM foo WHERE id=?", array(5))
$bd->executeScalar("SELECT id FROM foo WHERE id=? AND name like '%?%'"), array(5,"ivan"));
Ahora vamos por pasos, rellenendo lós métodos con la funcionalidad requerida.
Patrón singleton
La implementación de este patrón nos asegura que solo un objeto de dicha clase es creado. Para ello tenemos que poner el constructor como privado y llamarlo desde una función estática, en este caso getConnection. En el constructor creamos una conexión a la base de datos y se comprueba si se ha establecido.
private function __construct($provider){
if(!class_exists($provider)){
throw new Exception("El proveedor especificado no ha sido implentado o añadido.");
}
$this->provider = new $provider;
$this->provider->connect("localhost","usuarioBaseDatos", "tuPassword", "tuBaseDatos");
if(!$this->provider->isConnected()){
/*Controlar error de conexion*/
}
}
public static function getConnection($provider){
if(self::$_con){
return self::$_con;
}
else{
$class = __CLASS__;
self::$_con = new $class($provider);
return self::$_con;
}
}
sendQuery
Este método se encarga de enviar las consultas y comprobar errores:
private function sendQuery($q, $params){
$query = $this->prepare($q, $params);
$result = $this->provider->query($query);
if($this->provider->getErrorNo()){
/*Controlar errores*/
}
return $result;
}
prepare
Este método privado es importante, ya que es el que se encarga de limpiar los parámetros para prevenir errores típicos e insertarlos en su sitio correspondiente.
Para reemplazar se usa una expresión regular mediante la funcion preg_replace_callback(), que nos permite ejecutar una funcion para cada coincidencia encontrada. En este caso la callback es replaceParams (la cual definiremos a continuación).
private function prepare($sql, $params){
for($i=0;$i<sizeof($params); $i++){
if(is_bool($params[$i])){
$params[$i] = $params[$i]? 1:0;
}
elseif(is_double($params[$i]))
$params[$i] = str_replace(',', '.', $params[$i]);
elseif(is_numeric($params[$i]))
$params[$i] = $this->provider->escape($params[$i]);
elseif(is_null($params[$i]))
$params[$i] = "NULL";
else
$params[$i] = "'".$this->provider->escape($params[$i])."'";
}
$this->params = $params;
$q = preg_replace_callback("/(\?)/i", array($this,"replaceParams"), $sql);
return $q;
}
replaceParams
Esta es la función llamada por preg_replace_callback() y que se encarga de devolver el parámetro especificado para cada interrogante (?) encontrado en la consulta. Para hacer esto se usa la función next() que va moviendo el cursor del array para cada coincidencia.
private function replaceParams($coincidencias){
$b=current($this->params);
next($this->params);
return $b;
}
execute y executeScalar
Estas son las funciones que vamos a usar públicamente para realizar consultas y son muy parecidas. Tan solo se diferencian en que la primera recorre el resultado cargándolo en un array, mientras que la segunda simplemente devuelve la primera columna de la primera fila:
public function executeScalar($q, $params=null){
$result = $this->sendQuery($q, $params);
if(!is_null($result)){
if(!is_object($result)){
return $result;
}
else{
$row = $this->provider->fetchArray($result);
return $row[0];
}
}
return null;
}
public function execute($q, $params=null){
$result = $this->sendQuery($q, $params);
if(is_object($result)){
$arr = array();
while($row = $this->provider->fetchArray($result)){
$arr[] = $row;
}
return $arr;
}
return null;
}
Paso 4: Probarlo
Una vez hecho todo lo anterior la forma de uso es muy sencilla:
//Como parámetro va el nombre del proveedor que queréis cargar
$db = DatabaseLayer::getConnection("MySqlProvider");
//Imprimiría la estructura del array
print_r($db->execute("SELECT id,email FROM users WHERE name like ? LIMIT 20",array("ontuts%")));
//Imprime un valor númerico
echo($db->executeScalar("SELECT count(*) FROM users WHERE active=?",array(true)));
Reflexión final
Antes de despedirme recordaros que os podéis descargar el código completo en la zona superior de la página.
Como siempre, espero que la información compartida en este tutorial os resulte útil, con el que pretendo demostraros que no hay que tener miedo a crear tus propias librerías, siempre y cuando sepáis lo que estás haciendo
. No olvidéis dejar un comentario para cualquier duda, fallo o valoración.
¡Un saludo y hasta el próximo tutorial!
Te sugerimos otras entradas relacionadas...
¿Necesitas desarrollar un proyecto web o para móviles? ¡Estamos disponibles!

Cokidoo, los creadores de Ontuts, desarrollamos proyectos tecnológicos centrados en redes sociales y aplicaciones web, aplicaciones móviles y consultoría web y bases de datos.
Somos jóvenes, inquietos, versátiles, apasionados por la innovación y enfocados en las nuevas tecnologías. Con Ontuts tratamos de compartir nuestro conocimiento adquirido en los distintos proyectos, ayudando a la comunidad y mostrando nuestra capacidad tecnológica.
Si necesitas un presupuesto sin compromiso, estamos disponibles, no dudes en contactar con nosotros.


















Comentarios en esta publicación (43 comentarios)
¿Te ha gustado esta publicación? ¡Puedes compartir tu opinión con todos nosotros! Simplemente pincha aquí mismo.