Location: PHPKode > scripts > ddbb > class.ddbb_mysql.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.
 *
 * 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();
*/
?>
Return current item: ddbb