<?php
# Copyright (C) 2009 José Manuel Carnero <hide@address.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.
*
* Para mysql -> MySQL 4, 5
*
* PHP 4
*/
class ddbb_mysql extends 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 $sCharset; //codificacion en que se lanzaran las querys; a vacio usara la que tenga la base de datos; valores: utf-8,
var $sCollation; //collation en que se lanzaran las querys; vacio usara la que tenga la base de datos; util para evitar problemas en las comparaciones de cadenas de distintas tablas
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)
/**
* 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_mysql($servidor = 'localhost', $usuario = 'root', $password = '', $ddbb = 'test'){
parent::ddbb($servidor, $usuario, $password, $ddbb);
$this->sMotor = 'mysql';
$this->sQueryTipoArray = 'dos';
$this->sCharset = $this->codificacion('charset');
$this->sCollation = $this->codificacion('collation');
$this->aErrorMensajes = array_merge($this->aErrorMensajes, array('conectar1' => 'No se puede conectar a base de datos. Extension MYSQL para PHP no instalada.',
'conectar2' => 'No se puede conectar a la base de datos <em>%s</em>, error: [%s] %s',
'desconectar1' => 'No se puede cerrar la conexion a la base de datos <em>%s</em>, error: [%s] %s',
'seleccionarDDBB1' => 'No se puede seleccionar la base de datos <em>%s</em>, error: [%s] %s',
'consulta1' => 'La consulta <em>%s</em> ha generado el error: [%s] %s',
'consulta2' => 'Tipo de consulta no soportada: %s',
'numResultados1' => 'No es posible contar el numero de registros, no se reconoce el tipo de query: %s',
'recPuntero1' => 'No se ha podido recolocar el puntero debido al error: [%s] %s',
'leeFila' => 'Intenta obtener un array de resultados de una consulta no "SELECT"',
'leeFilasTodos' => 'Intenta obtener un array de resultados de una consulta no "SELECT"',
'tipoQuery' => 'El tipo de array (<em>%s</em>) que debe devolverse es desconocido'
));
}
/**
* Conexion 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.
* #required#
*
* @param $servidor Servidor
* @param $usuario Usuario
* @param $clave Clave
* @param $db Schema (base de datos)
*
* @return boolean
* @access public
*/
function conectar($servidor = false, $usuario = false, $clave = false, $db = false){
if(!function_exists('mysql_connect')){
$this->sError .= sprintf($this->aErrorMensajes['conectar1'])."\n";
$this->bConectado = false;
return(false);
}
ddbb::instCount();
if($servidor) $this->sDDBBServidor = $servidor;
if($usuario) $this->sDDBBUsuario = $usuario;
if($clave) $this->sDDBBPassword = $clave;
//medicion de tiempo
$iTiempoIni = $this->microtimeSeg();
$this->dConexion = mysql_connect($this->sDDBBServidor, $this->sDDBBUsuario, $this->sDDBBPassword);
$iTiempoFin = $this->microtimeSeg();
$this->aTiempos['Conectar'] = $iTiempoFin - $iTiempoIni;
if($this->dConexion === false){
$this->sError .= sprintf($this->aErrorMensajes['conectar2'], $this->sDDBB, mysql_errno($this->dConexion), mysql_error($this->dConexion))."\n";
$this->bConectado = false;
return(false);
}
$this->bConectado = true;
return($this->seleccionarDDBB($db));
}
/**
* Desconexion del servidor.
*
* @return boolean
* @access public
*/
function desconectar(){
//$bool = mysql_free_result($this->rResultados); //fuerza liberacion de memoria, solo sirve para "SELECT" (devuelve booleano)
if(ddbb::instCount(-1) == 0){
$sReturn = mysql_close($this->dConexion);
if(!$this->bSeleccion) $this->sError .= sprintf($this->aErrorMensajes['desconectar1'], $this->sDDBB, mysql_errno($this->dConexion), mysql_error($this->dConexion))."\n";
$this->bConectado = true;
}
else $sReturn = true;
$this->bConectado = false;
return($sReturn);
}
/**
* Seleccion de base de datos.
*
* @param $db Base de datos
* @return boolean
* @access public
*/
function seleccionarDDBB($db = false){
if($db !== false){
//if($this->sDDBB == $db) return(true); //sale con cierto si ya esta seleccionada la base de datos
$this->sDDBB = $db;
}
else return(true); //devuelve cierto si no se requiere seleccionar una base de datos concreta, como cuando se desee crear una
//medicion de tiempo
$iTiempoIni = $this->microtimeSeg();
$this->bSeleccion = mysql_select_db($this->sDDBB, $this->dConexion);
$iTiempoFin = $this->microtimeSeg();
$this->aTiempos['SeleccionarDDBB'] = $iTiempoFin - $iTiempoIni;
if(!$this->bSeleccion) $this->sError .= sprintf($this->aErrorMensajes['seleccionarDDBB1'], $this->sDDBB, mysql_errno($this->dConexion), mysql_error($this->dConexion))."\n";
//codificacion en que se lanzaran las consultas a la base de datos
//es equivalente a las tres sentencias:
/*SET character_set_client = x;
SET character_set_results = x;
SET character_set_connection = x;*/
/*estas muestran los valores de las anteriores
SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';
*/
//if(ddbb::instCount(0) == 1 && $this->sCharset != '') mysql_query("SET CHARACTER SET '".$this->sCharset."'");
if(ddbb::instCount(0) == 1 && $this->sCharset != '') mysql_query("SET NAMES '$this->sCharset' COLLATE '$this->sCollation'");
return($this->bSeleccion);
}
/**
* Lista de los campos de la consulta
* Devuelve tanto por posicion en el recordset de resultados como por nombre de campo.
*
* @return boolean
* @access private
*/
function listaCampos(){
$this->aCampos = array(); //se vacia, por si se vuelve a llamar la funcion
if(is_resource($this->rResultados)){
for($i=0;$i<mysql_num_fields($this->rResultados);$i++){
//$this->aCampos[] = array('nombre' => mysql_field_name($this->rResultados, $i), 'tipo' => mysql_field_type($this->rResultados, $i), 'long' => mysql_field_len($this->rResultados, $i), 'flags' => mysql_field_flags($this->rResultados, $i));
$this->aCampos[$i]['nombre'] = mysql_field_name($this->rResultados, $i);
$this->aCampos[$this->aCampos[$i]['nombre']]['pos'] = $i;
$this->aCampos[$this->aCampos[$i]['nombre']]['tipo'] = $this->aCampos[$i]['tipo'] = mysql_field_type($this->rResultados, $i);
$this->aCampos[$this->aCampos[$i]['nombre']]['long'] = $this->aCampos[$i]['long'] = mysql_field_len($this->rResultados, $i);
$this->aCampos[$this->aCampos[$i]['nombre']]['flags'] = $this->aCampos[$i]['flags'] = mysql_field_flags($this->rResultados, $i);
}
return(true);
}
return(false);
}
/**
* 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 "consulta" sin parametros
* 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%'"
*
* @param $query Query SQL
* @param $pars Array de parametros de la query
* @param $cantidad Numero de registros a devolver
* @param $inicial Registro inicial a devolver
*
* @return void
* @access public
*/
function query($query, $pars = array(), $cantidad = 0, $inicial = 0){
//elimina tags html y php de los parametros de la consulta
//si los parametros no van en el array "pars" no tiene efecto
//TODO ver mysqli::real_escape_string
if($this->sTagsPermitidosIns != 'todos'){
for($i=0;$i<count($pars);$i++){
$pars[$i] = strip_tags($pars[$i], $this->sTagsPermitidosIns);
}
}
//preparar las cadenas para que no den problemas SQL
if(!get_magic_quotes_gpc()){
$pars = array_map('addslashes', $pars);
}
$this->tipoQuery($query); //comprueba el tipo de query
$this->sQuery = vsprintf($this->sQuery, $pars);
if($cantidad){
//añade a la consulta "SQL_CALC_FOUND_ROWS", permite tener el total de filas en consultas limitadas con "SELECT FOUND_ROWS()", sin volver a lanzar la consulta
$iSelectPos = strpos(strtoupper($this->sQuery), 'SELECT ');
if($iSelectPos !== false && strpos($this->sQuery, 'SQL_CALC_FOUND_ROWS') === false) $this->sQuery = substr($this->sQuery, 0, $iSelectPos + 7).'SQL_CALC_FOUND_ROWS '.substr($this->sQuery, $iSelectPos + 7);
//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;
}
}
/**
* Consulta a la base de datos, (si "SELECT", necesita de "leeFila()" para empezar a devolver resultados).
* #required#
*
* @param $query Query SQL
* @param $pars Array de parametros de la query
* @param $cantidad Numero de registros a devolver
* @param $inicial Registro inicial a devolver
*
* @return boolean
* @access public
*/
function consulta($query = false, $pars = array(), $cantidad = 0, $inicial = 0){
if(!$this->bConectado){
if(!$this->conectar($this->sDDBBServidor, $this->sDDBBUsuario, $this->sDDBBPassword, $this->sDDBB)) return(false);
}
if($query) $this->query($query, $pars, $cantidad, $inicial);
//medicion de tiempo
$iTiempoIni = $this->microtimeSeg();
/*nota: mysql_unbuffered_query Envia una consulta SQL a MySQL, sin recuperar ni colocar en bufer las filas de resultado*/
$this->rResultados = mysql_query($this->sQuery); //recurso de resultados
//seleccionar una base de datos despues de crearla
//TODO, revisar como se selecciona una base de datos despues de su creacion
if($this->sTipoQuery == 'create' && $this->sSubTipoQuery == 'database') $this->seleccionarDDBB($this->aTablas[0]); //despues de crear una base de datos la selecciona para las siguientes sentencias
if($this->sTipoQuery == 'use') $this->seleccionarDDBB($this->aTablas[0]); //use `database`
$iTiempoFin = $this->microtimeSeg();
$this->aTiempos[$this->sTipoQuery] = $iTiempoFin - $iTiempoIni;
if($this->rResultados === false){
$this->sError .= sprintf($this->aErrorMensajes['consulta1'], $this->sTipoQuery, mysql_errno($this->dConexion), mysql_error($this->dConexion))."\n";
if($cantidad){
$this->sQuery = str_replace('SQL_CALC_FOUND_ROWS ', '', $this->sQuery); //no es necesario en caso de error y no es algo añadido por el usuario
}
return(false);
}
switch($this->sTipoQuery){
case 'describe':
case 'explain':
case 'select':
case 'show':
if($cantidad){
//medicion de tiempo
$iTiempoIni = $this->microtimeSeg();
//calculo de resultados para consultas con LIMIT
$rResultados = mysql_query('SELECT FOUND_ROWS() AS totalRows', $this->dConexion);
$aResultados = mysql_fetch_array($rResultados);
$iTiempoFin = $this->microtimeSeg();
$this->aTiempos['totalLimit'] = $iTiempoFin - $iTiempoIni;
$this->iTotalFilas = $aResultados['totalRows'];
$this->sQuery = str_replace('SQL_CALC_FOUND_ROWS ', '', $this->sQuery); //no es necesario despues de calcular el total de filas y no es algo añadido por el usuario
}
else $this->numResultados(); //calculo del total de resultados
$this->listaCampos(); //lista de campos de la consulta
break;
case 'insert':
$this->sUltimaId = mysql_insert_id(); //-> devuelve la ultima id insertada
case 'alter':
case 'create':
case 'drop':
case 'use':
case 'update':
case 'delete':
$this->numResultados(); //calculo del total de resultados
break;
case 'lock':
case 'set':
case 'unlock':
break;
default:
$this->sError .= sprintf($this->aErrorMensajes['consulta2'], $this->sTipoQuery)."\n";
return(false);
}
return(true);
}
/**
* Calcular el numero de filas devueltas o afectadas por la query.
* #TODO# ver informacion de las dos funciones, pueden dar resultados erroneos
*
* @return boolean
* @access public
*/
function numResultados(){
switch($this->sTipoQuery){
case 'describe':
case 'explain':
case 'select':
case 'show':
$this->iTotalFilas = mysql_num_rows($this->rResultados);
//var_dump($this->rResultados);
return(true);
break;
case 'alter':
case 'create':
case 'drop':
case 'use':
case 'delete':
case 'insert':
case 'update':
$this->iTotalFilas = mysql_affected_rows($this->dConexion);
if($this->iTotalFilas < 1) $this->iTotalFilas = 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;
case 'lock':
case 'set':
case 'unlock':
break;
default:
$this->sError .= sprintf($this->aErrorMensajes['numResultados1'], $this->sTipoQuery)."\n";
return(false);
}
}
/**
* Recolocar puntero en el array de resultados (solo para SELECT).
*
* @param PosicionPuntero $pos
* @return boolealn
* @access public
*/
function recPuntero($pos = 0){
if($this->iTotalFilas) $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 .= sprintf($this->aErrorMensajes['recPuntero1'], mysql_errno($this->dConexion), mysql_error($this->dConexion))."\n";
return($bReturn);
}
/**
* Lee una fila de resultados.
*
* @return boolean
* @access public
*/
function leeFila(){
//este metodo solo tiene sentido con consultas tipo "SELECT"
if($this->sTipoQuery == 'select' || $this->sTipoQuery == 'describe' || $this->sTipoQuery == 'explain' || $this->sTipoQuery == 'show'){
if(!$this->tipoArrayQuery($this->sQueryTipoArray)) return(false); //tipo de array que devolvera la query
//medicion de tiempo
$iTiempoIni = $this->microtimeSeg();
$aTemp = $this->aFila; //conserva el ultimo array de resultados (la ultima fila), que sera pisada en el siguiente if si llega al final del recordset
if($this->aFila = mysql_fetch_array($this->rResultados, $this->sQueryTipoArray)){
$iTiempoFin = $this->microtimeSeg();
$this->aTiempos['fila'][] = $iTiempoFin - $iTiempoIni;
//elimina tags html y php de los resultados
if($this->sTagsPermitidosSel != 'todos'){
foreach($this->aFila as $key => $valor){
$this->aFila[$key] = strip_tags($valor, $this->sTagsPermitidosSel);
}
}
return(true);
}
else{
$this->aFila = $aTemp;
return(false);
}
}
else{
$this->sError .= sprintf($this->aErrorMensajes['leeFila'])."\n";
return(false);
}
}
/**
* Crea un array con todas las filas de resultados.
*
* @return boolean
* @access public
*/
function leeFilasTodos(){
//recoloca el puntero en el inicio para obtener todos los resultados
if($this->iTotalFilas > 0) $this->recPuntero();
//este metodo solo tiene sentido con consultas tipo "SELECT"
if($this->sTipoQuery == 'describe' || $this->sTipoQuery == 'explain' || $this->sTipoQuery == 'select' || $this->sTipoQuery == 'show'){
if(!$this->tipoArrayQuery($this->sQueryTipoArray)) return(false); //tipo de array que devolvera la query
//medicion de tiempo
$iTiempoIni = $this->microtimeSeg();
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
//TODO
//elimina tags html y php de los resultados
/*if($this->sTagsPermitidosSel != 'todos'){
foreach($this->aFila as $key => $valor){
($this->aFila[$key] = strip_tags($valor, $this->sTagsPermitidosSel);
}
}*/
$iTiempoFin = $this->microtimeSeg();
$this->aTiempos['filasTodos'] = $iTiempoFin - $iTiempoIni;
return(true);
}
else{
$this->sError .= sprintf($this->aErrorMensajes['leeFilasTodos'])."\n";
return(false);
}
}
/**
* Tipo de array que devolvera la query.
*
* @param $tipo Tipo de array que devolvera la consulta
* @return boolean
* @access private
*/
private function tipoArrayQuery($tipo = ''){
if(!empty($tipo)) $this->sQueryTipoArray = $tipo;
//tambien admite las constantes de mysql (no i)
switch($this->sQueryTipoArray){
case 'dos': //devuelve array asociativo y numerico
case MYSQL_BOTH:
$this->sQueryTipoArray = MYSQLI_BOTH;
case MYSQLI_BOTH:
break;
case 'aso': //devuelve array asociativo
case MYSQL_ASSOC:
$this->sQueryTipoArray = MYSQLI_ASSOC;
case MYSQLI_ASSOC:
break;
case 'num': //devuelve array numerico
case MYSQL_NUM:
$this->sQueryTipoArray = MYSQLI_NUM;
case MYSQLI_NUM:
break;
default:
$this->sError .= sprintf($this->aErrorMensajes['tipoQuery'], $this->sQueryTipoArray)."\n";
return(false);
}
return(true);
}
}
/*
ejemplo de uso:
es preferible usar el metodo descrito en "class.ddbb.inc"
include_once("./inc/class.ddbb.inc");
include_once("./inc/class.ddbb_mysql.inc");
$oTest = new ddbb_mysql(DB_SERVER, DB_USER, DB_PASSWORD, DB_DATABASE);
//$oTest->conectar(DB_SERVER, DB_USER, DB_PASSWORD, DB_DATABASE); //se puede omitir si se han pasado los parametros de conexion al constructor
$oTest->consulta("SELECT * FROM usuarios");
while($oTest->leeFila()){
echo($oTest->aFila['login']."\n");
}
//var_dump($oTest->aTiempos);
$oTest->desconectar();
*/
?>