sábado, 12 de enero de 2008

Zend Framework: Paginado (parte I)

Como sabran PHP esta evolucionando, gracias al aporte de todas las personas que comparten algo en común una pasion.

Desde que Ruby on Rails salio, PHP ha tenido que adaptarse al cambio, utilizar el patron modelo vista controlador, a partir de ese momento salieron a la luz muchos frameworks como prado, cakePHP entre otros, pero tambien se creo uno llamado zend framework que es el que utilizaremos para este ejemplo, que consiste en mostrar los resultados en un datagrid paginado, utilizando POO con PHP.

Software utilizado:



Comenzamos ... :$

Clases de utilidad
en primer lugar tengo que mostrar dos clases de persistencia que utilizo:

la clase DBManager.class.php
<?
/********************************************/
/* Clase DBManager */
/*Clase que administra las conexiones con */
/*la base de datos. */
/*Autor: Hugo Flores Joseph 2006 */
/*Copyright (c) */
/********************************************/
require_once('Recordset.class.php');

class DBManager{

private $host='localhost';
private $db ='agenda';
private $user='';
private $pass='';
private $connectionID;// Retorna un enlace identificador de la conexion hecha con la base de datos
private $queryID = -1;// Esta variable guarda elúltimo resultado creado por link identifier de una consultra (almacena un entero<true=1,false=0>)
private $tempResultObj = '';// Almacena el resultado del Objeto Recientemente creado via el método execute()
private $error="";


//----------------Métodos Públicos--------------------//

public function DBManager($user='',$pass='')
{
if(!empty($user))$this->user=$user;
if(!empty($pass))$this->pass=$pass;

try{
$this->connectionID=@mysqli_connect($this->host,$this->user,$this->pass,$this->db);
if(!$this->connectionID) throw new Exception(mysqli_connect_error());
}
catch(Exception $e)
{
echo "A ocurrido un error: ".$e->getMessage();
exit();//termina la ejecucion del script
}
}

//funcion ADHOC utilizada con SELECT
public function executeQuery($sql = "")
{
$this->queryID = mysqli_query($this->connectionID,$sql);
$this->tempResultObj = new Recordset($this->queryID);//Inicializa un objeto sin considerar si la consulta retorna algun resultado o no.
return $this->tempResultObj;
}
//funcion ADHOC utilizada con INSERT, UPDATE, DELETE
public function executeUpdate($sql = "")//devuelve falso si la consulta tuvo exito
{
$resp=false;
$this->queryID = @mysqli_query($this->connectionID,$sql); //Inicializa un objeto sin considerar si la consulta retorna algun resultado o no.
if($this->queryID) $resp=true;
return $resp;
}
public function getConnection() { return $this->connectionID;}
public function closeConnection(){ mysqli_close($this->connectionID);}
}
?>


La segunda clase que utilizaremos es Recordset.class.php, esta clase tiene un comportamiento similar a java.sql.ResultSet de Java:
<?
/********************************************/
/* Clase Recordset */
/* clase que me permite manejar un conjunto */
/* de registros de una base de datos */
/* MySQL. */
/* Autor: Hugo Flores Joseph 2006 */
/* Copyright (c) */
/********************************************/

class Recordset{
//------Variables Públicas---------//
public $fields;
public $BOF = null;// indica que la posición actual del registro esta antes del primer registrp en un Objeto Recordset.
public $EOF = null;// indica que la posición actual del registro esta después del último registro en un Objeto Recordset.

//------Variables Privadas---------//
private $_numOfRows = -1; // No Cambie este valor! SOLO LECTURA!
private $_numOfFields = -1; // No Cambie este valor! SOLO LECTURA!
private $_tempResult = '';// Almacena un valor que fue retornado desde una función específica de la Base de Datos
private $_queryID = -1;// Esta variable guarda el resultado de un link identifier
private $_currentRow = -1;// Esta variable guarda la actual fila en un Recordset.

//------Métodos--------------------//
// Devuelve: query id exitoso o falso si
// la función Constructor ha fallado
public function Recordset($queryID)
{
$this->_queryID = $queryID;
if ($queryID) {
$this->_numOfRows = @mysqli_num_rows($this->_queryID);
$this->_numOfFields = @mysqli_num_fields($this->_queryID);//devuelve el número de campos de la consulta o result set
}
else {
$this->_numOfRows = 0;
$this->_numOfFields = 0;
}
// Si el resultado contiene filas
if ($this->_numOfRows > 0 && $this->_currentRow == -1) {
$this->_currentRow = 0;
$this->fields = mysqli_fetch_array($this->_queryID);//captura la fila en un array mssql_fetch_aaray(Devuelve: Un array que corresponde a la fila capturada, o FALSE si no hay más filas)
$this->EOF = false;
$this->BOF = false;
}
return $this->_queryID;
}//fin de método Recordset

// Devuelve: True si hay todavía filas disponibles, o False
// si no hay mas filas. Mueve a la proxima fila en un
// Objeto Recordset Especifico y hace que el registro de la fila actual
// y la correspondiente informaciónde la fila se recupere
// en la colección de los campos. Note: Al contrario del método moveRow(),
// cuando _currentRow esta getNumOfRows() - 1, EOF podria inmediatamente ser
// True. Si el número de fila no es proporcionado, la funcion podria posicionarse
//automáticamente en la primera fila.
public function nextRow()
{
if ($this->getNumOfRows() > 0)
{
$this->fields = array();
$this->_currentRow++;
$this->fields = @mysqli_fetch_array($this->_queryID);
// Esto esta no trabajando. True todo el tiempo
if ($this->fields)
{
$this->_checkAndChangeEOF($this->_currentRow - 1);
return true;
}
}
$this->EOF = true;
return false;
}//fin de método nextRow()

// Devuelve: true si es exitoso, false si ha fallado moveRow()
// moueve el puntero interno de una fila de el Objeto Recordset
// hacia un puntero de una fila especifico y la correspondiente
// información de la fila que podria recuperarse de la colección de
// campos. Si el número de fila no es proporcionado, la funcion podria posicionarse
// automáticamente en la primera fila.
public function moveRow($rowNumber = 0)
{
if ($rowNumber == 0) {
return $this->firstRow();
}
else if ($rowNumber == ($this->getNumOfRows() - 1)) {
return $this->lastRow();
}
if ($this->getNumOfRows() > 0 && $rowNumber < $this->getNumOfRows()) {
$this->fields = null;
$this->_currentRow = $rowNumber;
if(@mysqli_data_seek($this->_queryID, $this->_currentRow)) {//mueve el puntero interno de las filas Devuelve: TRUE si se ejecuta con éxito, FALSE si falla
$this->fields = @mysqli_fetch_array($this->_queryID);
/* This is not working. True all the time */
if ($this->fields) {
// No necesita llamar a _checkAndChangeEOF() por que
// la posibilidad de mover hacia la última fila ha
// sido manejada por el código de arriba
$this->EOF = false;
return true;
}
}
}
$this->EOF = true;
return false;
}//fin de moveRow()

// Devuelve: true en caso de exito, false en caso de fallo de lastRow() moueve
// el puntero interno de la fila de un Objeto Recordset hacia la última fila
// y recupera la correspondiente información de la fila
// de la collecion de campos.
public function lastRow()
{
if ($this->getNumOfRows() > 0) {
$this->fields = array();
$num_of_rows = $this->getNumOfRows();
$this->_tempResult = @mysqli_data_seek($this->_queryID, --$num_of_rows);//Devuelve: TRUE si se ejecuta con éxito, FALSE si falla y es --$num_of_rows por que en sql
if ($this->_tempResult) { //existe un ultimo registro en blanco entonces tengo que posicionarme en el
/* $num_of_rows decrementado anterioemente */ //penultimo registro.
$this->_currentRow = $num_of_rows;
$this->fields = @mysqli_fetch_array($this->_queryID);
/* Esto no esta trabajando. Verdadero todo el tiempo */
if ($this->fields) {
/* Caso Especial para hacer EOF=fallse. */
$this->EOF = false;
return true;
}
}
}
$this->EOF = true;
return false;
}//fin de método lastRow()

// Devuelve: true en caso de éxito, false en caso de fallo, firstRow() mueve
// el puntero interno de la fila de un Objeto Recordset hacia la primera fila
// y recupera la correspondiente información de la fila
// de la collecion de campos.

public function firstRow()
{
if ($this->getNumOfRows() > 0) {
$this->fields = array();
$this->_currentRow = 0;
if (@mysqli_data_seek($this->_queryID, $this->_currentRow)) {
$this->fields = @mysqli_fetch_array($this->_queryID);
$this->EOF = false;
/* Esto no esta trabajando. Verdadero todo el tiempo */
if ($this->fields) {
return true;
}
}
}
$this->EOF = true;
return false;
}


// Retorna: El número de filas de un resultado.
public function getNumOfRows(){return $this->_numOfRows;}

/* Chequea y Cambia el Estado de EOF. */
public function _checkAndChangeEOF($currentRow)
{
if ($currentRow >= ($this->_numOfRows - 1))
{
$this->EOF = true;
}else{
$this->EOF = false;
}
}

// close() solo necesita ser llamado si esta usando mucha memoria
// al correr su script. libera la memoria.

public function close()
{
$this->_tempResult = @mysqli_free_result($this->_queryID);
return $this->_tempResult;
}
}//fin de la clase
?>
Configurando nuestro proyecto
Una vez que tenemos estas dos clases procederemos a organizar nuestro proyecto siguiendo la estructura de directorio recomendada por el framework dentro del directorio htdocs de Apache, creamos una carpeta llamada zfpaging y los subdirectorios lo siguientes:


application/
controllers/
IndexController.php
models/
views/
scripts/
index/
index.phtml
helpers/
filters/
html/
.htaccess
index.php


La estructura que se muestra es la configuracion por defecto de un proyecto con zend framework, pero lo vamos a adaptar a lo que nostros utilizaremos:

application/
controllers/
IndexController.php
models/
views/
scripts/
index/
index.phtml
helpers/
filters/
.htaccess
library/
paging/
sql/
zend/
.htaccess
public/
images/
scripts/
styles/
.htaccess
.htaccess
index.php

No hay comentarios: