Location: PHPKode > scripts > ddbb > class.ddbb.inc
<?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.
 * 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
 * postgresql -> PostgreSQL
 *
 * PHP 4
 */

if(!defined('CLASSDDBB_DIR')) define('CLASSDDBB_DIR',dirname(__FILE__));

/**
 * Selector de tipo de base de datos con la que conectar.
 *
 * @param $motor Motor de base de datos
 * @return object
 * @access public
 */
function &ddbb_sel($motor = 'mysql', $servidor = 'localhost', $usuario = 'root', $password = '', $ddbb = 'test'){
	if($motor == 'mysql' && class_exists('mysqli')) $motor = 'mysqli';

	$class = 'ddbb_'.$motor;
	include_once(CLASSDDBB_DIR.'/class.'.$class.'.inc'); //si no existe el fichero a incluir no se devuelve error, se controla a continuacion

	if(!class_exists($class)){
		die('No se puede usar el tipo de base de datos -> '.$class."\n");
		return(false);
	}

	$obj = new $class($servidor, $usuario, $password, $ddbb);
	return($obj);
}

class ddbb{
	//variables privadas
	var $bConectado; //indica si se ha conseguido conectar o no a la base de datos

	//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 $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 $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 $sSubTipoQuery; //subtipo de query que se esta realizando, para consultas alter, create o drop: table, database
	var $sPrefijo; //prefijo para las tablas
	var $sPrefijoId; //identificador de prefijo para las tablas que sera sustituido con $this->sPrefijo, por defecto '_p_'; no usar ni % ni $
	var $aTablas; //array con las tablas implicadas en la consulta
	var $aCampos; //array con las campos de la consulta, en la forma: ('nombre' => nombre, 'tipo' => tipo, 'long' => longitud, 'flags' => flags) (para cada campo de los resultados)
	var $iTotalFilas; //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"
	var $aTiempos; //array de tiempos empleado en consultas; asociativo:
		/*
		Conectar -> tiempo en conectar a servidor,
		SeleccionarDDBB -> tiempo en seleccionar base de datos,
		$this->sTipoQuery -> tiempo en realizar consulta,
		[fila][] -> tiempo para el recuento de resultados, 1 por fila de resultados, todos con respecto a un mismo tiempo inicial
		totalLimit -> tiempo para el recuento de resultados en consultas tipo SELECT limitadas
		filasTodos -> tiempo para rellenado del array "$aFilaTodos"
		*/
	var $sTagsPermitidosIns; //cadena de texto con tags html permitidos (en consultas insert/update, en la forma '<p><strong>[...]'; 'todo' -> permite todos los tags (comportamiento por defecto), '' -> (o sin valor) elimina todos los tags
	var $sTagsPermitidosSel; //cadena de texto con tags html permitidos (en consultas que devuelvan datos, como select), en la forma '<p><strong>[...]'; 'todo' -> permite todos los tags, '' -> (o sin valor) elimina todos los tags (comportamiento por defecto)

	//control de errores
	var $sError;
	var $aErrorMensajes; //array con los mensajes de error la clase, a fin de que puedan ser traducidos/modificados; las secuencias %s deben dejarse ya que corresponden a variables que seran luego sustituidas, si es necesario poner un % se pondra como %%

	/**
	 * Constructor
	 * Los parametros por defecto conectan a un servidor local,
	 * con el usuario administrador (sin clave) y a la base de datos "test"
	 *
	 */
	function ddbb($servidor = 'localhost', $usuario = 'root', $password = '', $ddbb = 'test'){
		$this->bConectado = false;
		//$this->sMotor = 'mysql';
		$this->sDDBBServidor = $servidor;
		$this->sDDBBUsuario = $usuario;
		$this->sDDBBPassword = $password;
		$this->sDDBB = $ddbb;
		$this->aResultados = array();
		$this->aFila = array();
		$this->aFilaTodos = array();
		$this->sTipoQuery = '';
		$this->sSubTipoQuery = '';
		$this->sPrefijo = '';
		$this->sPrefijoId = '_p_';
		$this->aTablas = array();
		$this->iTotalFilas = 0;
		$this->sUltimaId = false;
		$this->aTiempos = array();
		$this->sTagsPermitidosIns = 'todos';
		$this->sTagsPermitidosSel = '';

		$this->sError = false;
		$this->aErrorMensajes = array(
			'tipoQuery1' => 'Tipo de consulta no soportada: %s'
		);
	}

	/**
	 * Contador de instancias de la clase;
	 * se usa al cerrar la conexion a la base de datos,
	 * para no dejar instancias sin acceso.
	 *
	 * @param $inc Incremento
	 * @return integer
	 * @access private
	 */
	function instCount($inc = 1){
		if(!isset($iInstCount)){
			static $iInstCount = 0;
		}
		if($inc == 1) $iInstCount++;
		if($inc == -1) $iInstCount--;
		//echo('$iInstCount --> '.$iInstCount);
		return($iInstCount);
	}

	/**
	 * Extrae la codificacion de la pagina en la que se esta ejecutando el objeto
	 * y la convierte a un formato reconocido por la base de datos.
	 *
	 * @param $tipo Tipo de dato devuelto
	 * @return string
	 * @access protected
	 */
	function codificacion($tipo = 'charset'){
		//extraer codificacion de la pagina actual de las cabeceras enviadas al navegador
		if(!isset($this->sCharset)) $this->sCharset = '';
		if(!isset($this->sCollation)) $this->sCollation = '';
		$aCodificacion = array();

		//necesita php>=4.3.0
		if(function_exists('headers_list')) $aCodificacion = headers_list();
		elseif(function_exists('apache_response_headers')) $aCodificacion = apache_response_headers();

		//lista de charset: SHOW CHARACTER SET (SQL en MYSQL)
		$codificacion = '';
		foreach($aCodificacion as $codificacion){
			//if(strpos($codificacion, 'charset=') !== false) $this->sCharset = str_replace('"', '', substr($codificacion, strpos($codificacion, 'charset=')+8));
			if(strpos($codificacion, 'charset=') !== false) break;
		}

		if(strpos(strtolower($codificacion), 'utf') !== false){
			if($tipo == 'charset') return('utf8');
			elseif($tipo = 'collation') return('utf8_general_ci');
		}
		elseif(strpos(strtolower($codificacion), 'iso-8859-1') !== false || strpos(strtolower($codificacion), 'iso-8859-15') !== false){
			if($tipo == 'charset') return('latin1');
			elseif($tipo = 'collation') return('latin1_spanish_ci');
		}

		return(false);
	}

	/**
	 * Tipo de query (primera palabra de la sentencia SQL).
	 * Sin incluir parentesis y demas (caso de UNION)
	 * TODO detectar (guardando en array) todas las consultas que se hacen (por ejemplo en UNION) y las tablas utilizadas, categorizando segun sean JOIN ...; devuelve nombres imcompletos de tablas cuando llevan espacios
	 *
	 * @param $query Query SQL
	 * @return boolean
	 * @access protected
	 */
	function tipoQuery($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
		//TODO ver "mysql_field_table"
		$this->aTablas = array();
		$aPatron = array();
		switch($this->sTipoQuery){
			case 'alter':
			case 'create': //TODO revisar coincidencias y como se guarda el tipo de create y la tabla o base de datos que se crea
			case 'drop':
				$this->sSubTipoQuery = strtolower($aQuery[1]);
				$aPatron[] = '/'.$this->sTipoQuery.'\W+\w*\W+(?:IF.+EXISTS)*\W*([\w\.]*)/i';
				$aPatron[] = '/REFERENCES\W+([\w\.]*)/i';
				break;
			case 'describe':
			case 'explain':
			case 'show':
				if(strpos(strtolower($query), '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 'lock':
				$aPatron[] = '/TABLES\W+([\w\.]*)/i'; //TODO puede cerrar mas de una tabla en la misma instruccion
				break;
			case 'insert':
				if(strpos(strtolower($query), 'into') !== false){
					$aPatron[] = '/INTO\W+([\w\.]*)/i';
					break;
				}
			case 'update':
				$aPatron[] = '/'.$this->sTipoQuery.'\W+([\w\.]*)/i';
				break;
			case 'set':
			case 'unlock':
			case 'use':
				break;
			default:
				$this->sError .= sprintf($this->aErrorMensajes['tipoQuery1'], $this->sTipoQuery)."\n";
				return(false);
		}

		$this->sQuery = $query;

		foreach($aPatron as $sPat){
			if(preg_match_all($sPat, $query, $regs, PREG_PATTERN_ORDER)){
				for($cont=0;$cont<count($regs[0]);$cont++){
					$sTemp = '';
					if(strpos($regs[1][$cont], '.') === false) $sTemp = str_replace($this->sPrefijoId, $this->sPrefijo, $regs[1][$cont]);
					else $sTemp = str_replace($this->sPrefijoId, $this->sPrefijo, substr($regs[1][$cont], strpos($regs[1][$cont], '.')+1));
					$this->aTablas[$sTemp] = $sTemp;

					$this->sQuery = str_replace($regs[0][$cont], str_replace($regs[1][$cont], str_replace($this->sPrefijoId, $this->sPrefijo, $regs[1][$cont]), $regs[0][$cont]), $this->sQuery); //prefijado de tablas en la consulta
				}

				$this->aTablas = array_keys($this->aTablas); //elimina entradas duplicadas
			}
		}
		return(true);
	}

	/**
	 * Devuelve microtime en segundos
	 *
	 * @return float
	 * @access private
	 */
	function microtimeSeg(){
	    list($mseg, $seg) = explode(" ", microtime());
	    return((float)$mseg + (float)$seg);
	}
}

/*
ejemplo de uso:

include_once("./inc/class.ddbb.inc");

$oTest = ddbb_sel('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();
*/
?>
Return current item: ddbb