Clase para interacción con bases de datos
Esto no es ADOdb (nadie se confunda), ni lo pretende.
La intención era crear una pequeña clase que simplificase la vida a la hora de acceder a bases de datos. Una capa de abstracción para no tener que recordar como se conecta o deja de conectar a tal o cual motor de base de datos, etc.
Por ahora está en desarrollo (posiblemente lo estará siempre), pero es completamente funcional que no libre de errores.
Esta implementado MYSQL y parcialmente POSTGRESQL; es lo que he necesitado hasta la fecha.
# Copyright (C) 2008 jmcb <jmanuel@2mdc.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# http://www.gnu.org/copyleft/gpl.html
//clase para conexion y querys a bases de datos
//de momento solo MYSQL (#TODO# otros motores de DDBB)
//Se marcan con #required# las funciones o pasos necesarios para el uso basico de la clase
/*
* motores soportados ("$this->sMotor"):
* mysql -> MySQL 4, 5
*/
class ddbb{
//variables privadas
var $dConexion; //identificador de enlace
var $bSeleccion; //estado de seleccion de base de datos (true, false)
var $rResultados; //identificador de la consulta
//variables publicas
var $sMotor; //motor de base de datos (mysql, postgresql)
var $sDDBBServidor; //servidor de base de datos; nombre cualificado o ip
var $sDDBBUsuario; //usuario de la base de datos
var $sDDBBPassword; //clave del usuario
var $sDDBB; //base de datos a usar (schema)
var $aFila; //una fila de resultados
var $aFilaTodos; //array con todas las filas de resultados
var $sQuery; //SQL query completa, pasar entre comillas dobles ("); se envia a las funciones como cadena, cada "%s" implica una sustitucion con un valor del array pasado (todos sus elementos deben ser cadenas de caracteres, ver "fConsulta" y "fQuery")
var $sQueryTipoArray; //define que tipo de array devolvera una query: dos (MYSQL_BOTH) -> ambos tipos de array, aso (MYSQL_ASSOC) -> asociativo, num (MYSQL_NUM) -> numerico; por defecto (si no se define explicitamente valor): ambos; es valido asignandole las constantes entre parentesis (naturales de PHP)
var $sTipoQuery; //tipo de query que se esta realizando: select, insert, update, delete, ...
var $aTablas; //array con las tablas implicadas en la consulta
var $iTotalRow; //total de registros encontrados
var $sUltimaId; //ultima id insertada en campos "autoincrement"; false mientras no haya inserciones; devuelve valor incorrecto si el campo id de la tabla es "BIGINT"
//control de errores
var $sError;
/**
* Constructor
* Los parametros por defecto conectan a un servidor de MYSQL local,
* con el usuario administrador (sin clave) y a la base de datos "test"
* (que existe por defecto)
*
*/
function ddbb(){
$this->sMotor = 'mysql';
$this->sDDBBServidor = 'localhost';
$this->sDDBBUsuario = 'root';
$this->sDDBBPassword = '';
$this->sDDBB = 'test';
$this->aResultados = array();
$this->aFila = array();
$this->aFilaTodos = array();
$this->sQueryTipoArray = 'dos';
$this->sTipoQuery = '';
$this->aTablas = array();
$this->iTotalRow = 0;
$this->sUltimaId = false;
$this->sError = false;
}
//contador de instancias de la clase; se usa al cerrar la conexion a la base de datos, para no dejar instancias sin acceso
function fInstCount($inc = 1){
if(!isset($iInstCount)) static $iInstCount = 0;
if($inc == 1) $iInstCount++;
if($inc == -1) $iInstCount--;
//echo('$iInstCount --> '.$iInstCount);
return($iInstCount);
}
//#required#
//conexion al servidor
//
/**
* Conexión al servidor.
* Selecciona automaticamente la base de datos,
* llamar a "fSeleccionarDDBB()" cambiando la propiedad "$this->sDDBB"
* para usar otra base de datos con esta misma conexion.
*
* @param Servidor $servidor Usuario $usuario Clave $clave Schema $db
* @return boleano
* @access public
*/
function fConectar($servidor = false, $usuario = false, $clave = false, $db = false){
ddbb::fInstCount();
if($servidor) $this->sDDBBServidor = $servidor;
if($usuario) $this->sDDBBUsuario = $usuario;
if($clave) $this->sDDBBPassword = $clave;
$this->dConexion = mysql_connect($servidor, $usuario, $clave);
if($this->dConexion === false){
$this->sError .= 'No se puede conectar a la base de datos '.$this->sDDBB.', error:'.mysql_error($this->dConexion).")\n";
return(false);
}
return($this->fSeleccionarDDBB($db));
}
//#required#
//conexion al servidor
function fDesconectar(){
//$bool = mysql_free_result($this->rResultados); //fuerza liberacion de memoria, solo sirve para "SELECT" (devuelve booleano)
if(ddbb::fInstCount(-1) == 0){
$sReturn = mysql_close($this->dConexion);
if(!$this->bSeleccion) $this->sError .= 'No se puede cerrar la conexion a la base de datos '.$this->sDDBB.', error:'.mysql_error($this->dConexion).")\n";
}
else $sReturn = true;
return($sReturn);
}
//eleccion de base de datos
function fSeleccionarDDBB($db = false){
if($db !== false) $this->sDDBB = $db;
$this->bSeleccion = mysql_select_db($this->sDDBB, $this->dConexion);
if(!$this->bSeleccion) $this->sError .= 'No se puede seleccionar la base de datos '.$this->sDDBB.', error:'.mysql_error($this->dConexion).")\n";
return($this->bSeleccion);
}
/*
* tipo de query (primera palabra de la sentencia SQL)
*
* TODO detectar (guardando en array) todas las consultas que se hacen (por ejemplo en UNION) y las tablas utilizadas, categorizando segun sean JOIN ...
*/
function fTipoQuery($query = false){
if($query === false) $query = $this->sQuery;
$aQuery = explode(' ', trim(str_replace(array('(', ')'), '', $query))); //evita que en UNION o similar (empiezan con parentesis) no encuentre el primer SELECT
$this->sTipoQuery = strtolower($aQuery[0]);
//que tablas estan implicadas en la consulta
$this->aTablas = array();
$aPatron = array();
switch($this->sTipoQuery){
case 'describe':
case 'explain':
if(strpos(strtolower($this->sQuery), 'select') === false){
$aPatron[] = '/'.$this->sTipoQuery.'\W+(\w*)/i';
break;
}
case 'select':
$aPatron[] = '/FROM\W+(\w*)/i';
$aPatron[] = '/JOIN\W+(\w*)/i';
break;
case 'delete':
$aPatron[] = '/FROM\W+(\w*)/i';
break;
case 'insert':
if(strpos(strtolower($this->sQuery), 'into') !== false){
$aPatron[] = '/INTO\W+(\w*)/i';
break;
}
case 'update':
$aPatron[] = '/'.$this->sTipoQuery.'\W+(\w*)/i';
break;
default:
//$this->sError .= 'Tipo de consulta no soportada: '.$this->sTipoQuery.' (error-> '.mysql_error($this->dConexion).")\n";
}
foreach($aPatron as $sPat){
if(preg_match_all($sPat, $this->sQuery, $regs, PREG_PATTERN_ORDER)){
foreach($regs[1] as $reg){
$this->aTablas[] = $reg;
}
}
}
}
//construir query
//si ya se ha asignado una query mediante la propiedad "sQuery" y no se desea cambiar NO llamar a esta funcion y llamar al metodo "fConsulta" sin parametros
//"$cantidad" e "$inicial" devuelven un rango de resultados ("LIMIT" en MYSQL, "TOP" en MSSQL, ...)
//ej: $obj->fQuery("SELECT * FROM tabla WHERE campo=%s AND b=%s", array('1', '2'))
//cuando se requiera el comodin SQL "%" escribirlo doble "%%"; ej: $obj->fQuery("SELECT * FROM tabla WHERE campo='%%%s%%'", array('hola')) ->producira la salida: "SELECT * FROM tabla WHERE campo='%hola%'"
function fQuery($query, $pars = array(), $cantidad = 0, $inicial = 0){
//preparar las cadenas para que no den problemas SQL
if(!get_magic_quotes_gpc()){
$pars = array_map('addslashes', $pars);
}
$this->sQuery = vsprintf($query, $pars);
if($cantidad){
switch($this->sMotor){
case 'mysql':
//si "$inicial=0" devuelve "$cantidad" de registros desde el primero
//si se quieren devolver todos los que haya desde un "$inicial=x", dar como "$cantidad" un valor mayor al total de registros que pueda devolver la consulta (un numero al azar suficientemente grande, por ejemplo 123456789123456789123456789)
$this->sQuery .= ' LIMIT '.$inicial.','.$cantidad;
break;
case 'postgresql':
$this->sQuery .= ' LIMIT '.$inicial.' OFFSET '.$cantidad;
break;
}
}
}
//#required#
//consulta a la base de datos, (si "SELECT", necesita de "fLeeFila()" para empezar a devolver resultados)
function fConsulta($query = false, $pars = array(), $cantidad = 0, $inicial = 0){
if($query) $this->fQuery($query, $pars);
$this->fTipoQuery($query); //comprueba el tipo de query
switch($this->sTipoQuery){
case 'select':
case 'describe':
case 'explain':
//#TODO# ver si se puede calcular el total de resultados (con consultas "LIMIT") sin lanzar dos veces la consulta
$this->rResultados = mysql_query($this->sQuery); //array con los resultados sin "LIMIT"
$this->fNumResultados(); //calculo del total de resultados (evitando la posibilidad de "LIMIT")
if($cantidad){
if(is_resource($this->rResultados)) mysql_free_result($this->rResultados); //libera la memoria de los resultados previos, ya que solo fueron necesarios para el recuento total
if($query) $this->fQuery($query, $pars, $cantidad, $inicial);
$this->rResultados = mysql_query($this->sQuery); //array con los resultados
}
break;
case 'delete':
case 'insert':
case 'update':
$this->rResultados = mysql_query($this->sQuery); //array con los resultados
$this->sUltimaId = mysql_insert_id(); //-> devuelve la ultima id insertada
$this->fNumResultados(); //calculo del total de resultados
break;
default:
$this->sError .= 'Tipo de consulta no soportada: -'.$this->sTipoQuery.'- (error-> '.mysql_error($this->dConexion).")\n";
}
if($this->rResultados === false){
$this->sError .= 'La consulta : '.$this->sTipoQuery.' ha generado el error-> '.mysql_error($this->dConexion).")\n";
return(false);
}
else return(true);
}
//calcular el numero de filas devueltas o afectadas por la query
//#TODO# ver informacion de las dos funciones, pueden dar resultados erroneos
function fNumResultados(){
switch($this->sTipoQuery){
case 'select':
case 'describe':
case 'explain':
$this->iTotalRow = mysql_num_rows($this->rResultados);
//var_dump($this->rResultados);
return(true);
break;
case 'delete':
case 'insert':
case 'update':
$this->iTotalRow = mysql_affected_rows($this->dConexion);
if($this->iTotalRow < 1) $this->iTotalRow = false;
return(true);
/*#TODO# If the last query was a DELETE query with no WHERE clause, all of the records will have been deleted from the table but this function will return zero with MySQL versions prior to 4.1.2.*/
break;
default:
$this->sError .= 'No es posible contar el numero de registros, no se reconoce el tipo de query: '.$this->sTipoQuery."\n";
return(false);
}
}
//recolocar puntero en el array de resultados (solo para SELECT)
function fRecPuntero($pos = 0){
if($this->iTotalRow) $bReturn = mysql_data_seek($this->rResultados, $pos); //falla con E_WARNING y devuelve FALSE si el result set esta vacio
else $bReturn = false;
if(!$bReturn) $this->sError .= 'No se ha podido recolocar el puntero debido al error: '.mysql_error($this->dConexion)."\n";
return($bReturn);
}
//#required#
//lee fila de resultados
function fLeeFila(){
//este metodo solo tiene sentido con consultas tipo "SELECT"
if($this->sTipoQuery == 'select' || $this->sTipoQuery == 'describe' || $this->sTipoQuery == 'explain'){
//tipo de array que devolvera la query
switch($this->sQueryTipoArray){
case 'dos':
$this->sQueryTipoArray = MYSQL_BOTH;
case MYSQL_BOTH:
break;
case 'aso':
$this->sQueryTipoArray = MYSQL_ASSOC;
case MYSQL_ASSOC:
break;
case 'num':
$this->sQueryTipoArray = MYSQL_NUM;
case MYSQL_NUM:
break;
default:
$this->sError .= 'El tipo de array que debe devolverse es desconocido'."\n";
}
if($this->aFila = mysql_fetch_array($this->rResultados, $this->sQueryTipoArray)) return(true);
else return(false);
}
else{
$this->sError .= 'Intenta obtener un array de resultados de una consulta no "SELECT"'."\n";
return(false);
}
}
//crea un array con todas las filas de resultados
function fLeeFilasTodos(){
//recoloca el puntero en el inicio para obtener todos los resultados
if($this->iTotalRow > 0) $this->fRecPuntero();
//este metodo solo tiene sentido con consultas tipo "SELECT"
if($this->sTipoQuery == 'select' || $this->sTipoQuery == 'describe' || $this->sTipoQuery == 'explain'){
//tipo de array que devolvera la query
switch($this->sQueryTipoArray){
case 'dos':
$this->sQueryTipoArray = MYSQL_BOTH;
case MYSQL_BOTH:
break;
case 'aso':
$this->sQueryTipoArray = MYSQL_ASSOC;
case MYSQL_ASSOC:
break;
case 'num':
$this->sQueryTipoArray = MYSQL_NUM;
case MYSQL_NUM:
break;
default:
$this->sError .= 'El tipo de array (-'.$this->sQueryTipoArray.'-) que debe devolverse es desconocido'."\n";
}
while($this->aFilaTodos[] = mysql_fetch_array($this->rResultados, $this->sQueryTipoArray)) ;
if($this->aFilaTodos[count($this->aFilaTodos)-1] === false) array_pop($this->aFilaTodos); //el anterior bucle (si todo es correcto) colocara un false en el ultimo elemento, sobra
return(true);
}
else{
$this->sError .= 'Intenta obtener un array de resultados de una consulta no "SELECT"'."\n";
return(false);
}
}
}
/*
ejemplo de uso:
include_once("../inc/class.ddbb.inc");
$oPar_pais = new ddbb();
$oPar_pais->fConectar(DB_SERVER, DB_USER, DB_PASSWORD, DB_DATABASE);
$oPar_pais->fConsulta("SELECT id, fdNombre FROM tbpaises ORDER BY fdNombre");
//este bucle solo para consultas SELECT
while($oPar_pais->fLeeFila()){
echo($oPar_pais->aFila['id']);
}
$oPar_pais->fDesconectar();
*/
Añadir Comentario June 4th, 2008






