Paginacion con cakePHP 1.2

lunes, 19 de marzo de 2007

Aunque salio hace ya algun tiempo, a principios de febrero, acabo de empezar a probar cakePHP 1.2, la nueva versión de este framework que por fin incluye algunas de las funcionalidades mas demandas. Las novedades son:



  • Nuevos validadores

  • FormHelper mejorado y extendido

  • EmailComponent

  • SecurityComponent: Ahora con soporte para HTTP_AUTH a través de la variable $requireLogin.

  • Nuevo formato CTP que sustituye al thtml

  • Paginación

  • Extensiones Url

  • Model Behaviors

  • Datasources

  • i18N y l10N: Localizacion e Internacionalización


Podéis encontrar una definición un poco mas detallada en quarkblog


De todas estas las que mas me han interesado han sido la paginación (la echaba en falta después de trabajar con .net) y la internacionalización. Deseoso de probarlas busque documentación al respecto y no he encontrado nada, o prácticamente nada en Español y muy escasos documentos en Ingles.


Asi que aquí dejo un pequeño tutorial sobre paginación en cakePHP. Lo primero de todo es descargar esta versión desde cakeforge y configurar la base de datos. (Supongo que se sabe como instalar y configurar cakePHP, por si acaso:[1] [2] [3])


También crearemos una tabla de ejemplo e insertaremos algunos datos:


CREATE TABLE `posts` (
`id` int(5) NOT NULL auto_increment,
`title` varchar(255) collate latin1_general_ci NOT NULL default '',
`content` text collate latin1_general_ci NOT NULL,
`created` datetime default NULL,
`modified` datetime default NULL,
PRIMARY KEY (`id`)
);

INSERT INTO `posts` VALUES (1, 'Titulo del post 1 ',  'Contenido del post 1', '2007-03-18 23:07:58', '2007-03-18 23:07:58');
INSERT INTO `posts` VALUES (2, 'Titulo del post 2', 'Contenido del post 2', ' 2007-03-18 23:06:57', '2007-03-18 23:06:57');
INSERT INTO `posts` VALUES (3, 'Titulo del post 3', 'Contenido del post 3', '2007-03-18 23:08:19', '2007-03-18 23:08:19');
INSERT INTO `posts` VALUES (4, 'Titulo del post 4 ', 'Contenido del post 4', '2007-03-18 23:07:58', '2007-03-18 23:07:58');

Creamos el modelo y el controlador, usando scaffolding para no complicarnos mucho.


/app/models/post.php


            class Post  extends AppModel
{
var $name = 'Post';
}

/app/controllers/post_controller.php


            class  PostsController extends AppController
{
var $name = 'Posts';
var $scaffold;

var $helpers = array('Html','Paginator'); // estos los usaremos mas adelante.
}

Una vez hecho esto podemos observar en nuestro equipo que http://localhost/tutorial/post/ ya incluye la paginación. Lo único que haremos será reescribir el método ‘index’ con las mismas funcionalidades para aprender como realizar esta.


Añadimos al fichero ‘post_controller.php’ las siguientes lineas:


            function  index()
{
$this->set('data', $this->paginate());
}

¿Hay una manera mas sencilla de paginar los registros? $this->paginate() realiza la misma funcion que findAll() pero paginando los registros.


Y creamos la vista ‘posts/index.ctp’ que se encargara de mostrar la información.


<h2>Listado de Posts</h2>
<p><?php echo $paginator->counter(array('format' => 'Pagina %page% de %pages%, mostrando %current% registros de %count%')); ?></p>
<p><?php echo 'Ordenado por:'. $paginator-> sortKey() . ' ' . $paginator-> sortDir(); ?></p>
<table class="scaffold" cellpadding="0" cellspacing="0">
<thead>
<tr>
<th><?php echo $paginator->sort('Id','id'); ?></th>
<th><?php echo $paginator->sort('Titulo','title'); ?></th>
<th>Contenido</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<!--<?php print_r($data); ?>-->
<?php
$i = 0;
if(is_array($data)) {
foreach ($data as $row) {

$id = $row['Post']['id'];
$title = $row['Post']['title'];
$content = $row['Post']['content'];

if($i++ % 2 == 0) {
echo '<tr>';
} else {
echo '<tr class="altrow">';
}
?>
<td>
<?php echo $html->link($id, array('action' => 'view', $id));?>
</td>
<td>
<?php echo $html->link($title, array('action' => 'view', $id));?>
</td>
<td>
<?php echo $html->link($content, array('action' => 'view', $id));?>
</td>
<td>
<?php echo $html->link('Ver', array('action' => 'view', $id));?>
<?php echo $html->link('Editar', array('action' => 'edit', $id));?>
<?php echo $html->link('Borrar', array('action' => 'delete', $id),null,
sprintf("¿Estas seguro de querer borrar el post: '%s'?", $title));?>
</td>
<?php } ?>
</tr>
<?php } ?>
</tbody>
</table>
<div class="paging">
<?php echo $paginator->prev('<< Anterior', array(), null, array('class'=>'disabled'));?>
|
<?php echo $paginator->next('Siguiente >>', array(), null, array('class'=>'disabled'));?>
</div>
<div class="actions">
<ul>
<li>
<?php echo $html->link('Nuevo Post', array('action' => 'add')); ?>
</li>
</ul>
</div>

Esta vista esta basada en cake\libs\view\templates\scaffolds\index.thtml que es la que usa cakePHP por defecto si definimos la variable $scaffold.

Lo mas interesante es el uso de las siguientes funciones:




$paginator->sort(texto_a_mostrar,campo)

Crea un enlace gracias al cual podemos ordenar automaticamente los datos

$paginator->counter(array('format' => 'Pagina %page% de %pages%, mostrando %current% registros de %count%'))

Permite definir frases mostrando una serie de variables que se detellan a continuacion:


%page% => Pagina actual

%pages% => Total de paginas

current% => Registros mostrados

%count% => Total de registros

%start% => Primer registro de la pagina

%end% => Ultimo registro de la pagina

$paginator->prev('<< Anterior', array(), null, array('class'=>'disabled'))

$paginator->next('Siguiente >>', array(), null, array('class'=>'disabled'))

Devuelven un enlace a la pagina siguiente y anterior respectivamente.

$paginator->sortKey()

$paginator->sortDir()

Devuelven el campo por el que se esta ordenando y la direccion.


Como hemos incluido pocos registros, no podemos ver nuestra paginación funcionando aunque si la ordenación por columnas. Vamos a limitar el numero de registros por pagina para poder ver los resultdos paginados. Para ello incluimos la linea:

var $paginate = array('limit' =>  2, 'order' => array('Post.created' => 'desc'));


Con ella definimos el número de registros por pagina y la ordenación inicial que queremos en nuestros datos.


¡¡¡Ya podemos ver nuestra paginación funcionando!!!

cakePHP: Aplicaciones 'bipolares' usando "beforeRender()"

viernes, 2 de marzo de 2007

Hoy me siento orgulloso de mi mismo, por fin tengo algo que escribir aquí.

Llevo unos días trabajando con PHP para un proyecto de la facultad, y después de trabajar con .net para aplicaciones web volver a un lenguaje de script se me antojaba un poco difícil. Al menos con he encontrado un framework sencillo de utilizar y aunque le faltan muchas características por implementar hay que tener en cuenta que esta todavía en desarrollo. Acaban de sacar la versión 1.2alfa. Este framework es cakePHP, mas info aquí, aquí y aquí.

Buscando información para documentarme he encontrado este blog coderbattery con algunos posts interesantes. No me considero un entendido en cakePHP, mas bien un aprendiz, pero me aventuro a mejorar una de las soluciones que proponen para ‘aplicaciones web bipolares’. El problema esta muy bien explicado en post y por lo tanto os remito a el [Aplicaciones “bipolares” con CakePHP].

La solución propuesta pasa por modificar appController. Incluyendo el siguiente código que modifica el layout por defecto dependiendo del dominio desde el que estamos accediendo:


class AppController extends Controller {
/* Constructor que determina el dominio
y en base a eso usa el layout correspondiente */
function __construct() {
parent::__construct();

// Determinar layout por defecto
if(ereg('dominio1',$_SERVER['SERVER_NAME'])) {
// Estamos en el primer dominio
$this->layout = 'dominio1_layout';
} else {
// Estamos en el segundo dominio
$this->layout = 'dominio2_layout';
}
}
...
}

Pero se presenta el problema que en aquellos controladores que no utilicen el layout por defecto hemos de incluir el siguiente código:

class LoginController extends AppController {
// Constructor para evitar el layout default
function __construct() {
parent::__construct();

if(ereg('dominio1',$_SERVER['SERVER_NAME'])) {
$this->layout = 'dominio1_login';
} else {
$this->layout = 'dominio2_login';
}
}
...
}


Esto provoca la repetición de código complicando las cosas si decidimos cambiar de dominio o incluir alguno mas pues nos veremos obligados a modificar bastantes ficheros si nuestra aplicación es compleja. Buscando un poco he encontrado la función beforeRender() de la clase Controller que se ejecuta justo antes de aplicar la el layout. Por lo tanto mi propuesta es incluir el siguiente código en el AppController:


class AppController extends Controller {
/* Determina el dominio y en base a eso usa el layout correspondiente */
function beforeRender(){
// Determinar layout por defecto
if(ereg('localhost',$_SERVER['SERVER_NAME'])) {
// Estamos en el primer dominio
$this->layout = 'dominio1_'.$this->layout;
} else {
// Estamos en el segundo dominio
$this->layout = 'dominio2_'.$this->layout;
}
}
...
}


Con esto nos ahorramos tener que incluir el código en cada controlador que no utilice el layout por defecto. Por supuesto también tiene inconvenientes y es que tendremos que crear, al menos en mi caso, los layout ‘dominio1_ajax.thtml’ y ‘dominio2_ajax.thtml’ y es probable que también alguno mas.