jueves, 5 de junio de 2008

martes, 4 de marzo de 2008

Un ejemplo de modelos

En unas pocas horas creé una trivia en CodeIgniter que permite mandar preguntas y contestar las que otros usuarios envíaron.

El código es simple, pero lo interesante que quiero mostrar es el funcionamiento de los modelos. En application/libraries/MY_Model.php se extiende la definición de los modelos. Ahí tenemos los métodos "get", "fetchlist" y "count" que reciben el tipo de objeto y después un array con los filtros que se quieren aplicar.

¿Cómo se traducen los filtros?
En general, los filtros son condiciones de base de datos. Pero también se permiten "limit", "start", "orderby" y "orderbydir" que son para limitar la cantidad de registro, cambiar el inicio (offset), la columna por la que se ordena y la forma (ASC/DESC), respectivamente.
Y aún más, se pueden definir filtros propios en cada modelo. Con definir un método filter se pueden establecer condiciones propias. El método recibe tres argumentos: tipo de objeto, clave y valor. La función debe devolver TRUE en caso de que tenga un manejo propio del filtro o FALSE si no. En el ejemplo, se puede ver esto en el modelo "trivia".

¿Qué otros métodos tenemos definidos para los modelos?
Los métodos save, update, insert y delete reciben un objeto y lo guardan en la base de datos. Estos objetos deben ser objetos que extiendan a la clase Data_object, ser prefijados con "obj_" y llamarse como la tabla que le corresponde. Cada uno debe definir los atributos que se guardarán en las columnas de la base de datos.

Descargar ejemplo

martes, 26 de febrero de 2008

Cacheando datos

Si estamos buscando la forma de mejorar la performance de nuestro sitio web, el caché es la clave.

Hay muchas formas de hacerlo, lo más común es cachear páginas enteras, pero a veces esto no es posible. En estos casos, lo principal es localizar los procesos más lentos y tratar de guardar su resultado. Para ello, lo mejor es usar memcache que nos permite guardar una variable (serializada, como string) y recuperarla muy rápidamente.

Cómo solución más genérica se puede implementar una clase que reciba y guarde datos, permitiendo cambiar la forma de hacerlo (memcache, base de datos, archivos) por si es necesario migrar el código a un servidor que no cuenta con la tecnología necesaria.

domingo, 24 de febrero de 2008

Sobreviviendo sin parámetros GET

CodeIgniter no permite el uso de variables GET por defecto, para permitir las "URLs amigables". Sin embargo, todavía se pueden pasar parámetros.

Supongamos un foro, y queremos pasar el número de tema para mostrar los comentarios. En PHP clásico haríamos una URL ver_tema.php?tema_id=1234 pero en CI, en cambio, podemos hacer tema/ver/1234. Entonces el controller es tema, el método ver y un parámetro adicional es 1234.

¿Cómo recuperamos el valor de este parámetro?
Tenemos dos opciones. Podemos definirlo como parámetro del método ver o a través de la clase URI.
La primer alternativa sería algo así

function ver($id = 0)
{
// y ya está definida $id
}

La segunda opción es

function ver()
{
$id = $this->uri->segment(3);
// y ya está definida $id
}

La primera parece más linda, ¿no? Quizás a alguien le llame la atención el = 0 en el parámetro... cómo el usuario define la URL que visita puede no enviar el parámetro y esto generaría un error fatal de PHP, irrecuperable. De esta forma podemos hacer que si el id es vacío, el usuario sea redirigido a una página de error o a la lista de temas... Básicamente, fallar con gracia.

Este caso es simple. Sin embargo se complica más cuando los parámetros son múltiples. Por ejemplo, para paginar el tema que estamos mostrando, podemos agregar un cuarto parámetro, quedando

function ver($id = 0, $pagina = 1)
{

}

Pero las cosas se pueden complicar todavía más, cuando hay múltiples parámetros y no necesariamente un orden de importancia (en el caso anterior, siempre se necesita un ID, pero no siempre un número de página).
Supongamos un formulario de búsqueda avanzado, dentro del mismo foro. Además de palabras clave, podemos seleccionar un rango de fecha y un usuario en particular. Si tratamos de aplicar el mismo criterio, llegaríamos a algo tan confuso como

function buscar($palabras_clave = '', $desde_fecha = '', $hasta_fecha = '', $usuario = 0)
{
// ...
}

Lo que parece más molesto que amigable. Hay varias posibles soluciones.

Sesiones
Una posible variante es envíar el formulario a una página que lo guarde en la sesión y luego poder navegar sabiendo que buscó a través de los datos de sesión. Contra: si un usuario hace dos búsquedas no las puede "navegar" (entrar a temas y volver, o utilizar la paginación) en forma simultanea. Contra II: no se puede compartir o guardar como favoritos la página de resultados de la búsqueda.

Uri Assoc
Si bien no podemos hacer resultados_busqueda.php?palabras_clave=...&desde_fecha=...&hasta_fecha=... podemos hacer una URI asociativa y utilizar busqueda/resultados/palabras_clave/.../desde_fecha/.../hasta_fecha/... y luego aprovechar el método uri_to_assoc de la clase URI para lograr un array asociativo simple.

Hash
Por último, podemos utilizar la URI para mandar un identificador de la búsqueda. Por ejemplo busqueda/resultados/bda90d48eda6d07d961b3ec26216fe05 y guardar en nuestra base de datos que criterios significan "bda90d48eda6d07d961b3ec26216fe05".

sábado, 16 de febrero de 2008

Lo primero que hay que saber

El objeto que se instancia como controlador, tiene referencias a todas las instancias de librerías y modelos que hayan cargadas como un atributo del mismo nombre de la clase.
Así, si cargamos la librería Email, tenemos que $this->email es una instancia de la clase Email.

El tema no termina ahí. Cada modelo, también, tiene esos mismos atributos cargados, entonces el código que tenemos en el controlador y en el módelo es similar, ya que accedemos a los mismos atributos. Entonces, si hacemos cierto código en el controlador y nos parece que es muy general y lo podemos necesitar en otro, es fácil pasarlo a un modelo.

Pero... ¿Qué pasa si estamos en una librería?
A las librerías no se les cargan los atributos, pero pueden necesitar acceso a otra librería, o a un modelo. Para eso, podemos acceder a la instancia del controlador a través de la función "get_instance".
Por ejemplo, dentro de la clase Email queremos validar el mail con el método valid_email de la clase de Validación. Entonces:

function valid_email($address)
{
// (El & es importante para no crear una copia en PHP 4)
$CI =& get_instance();

// Si no está cargada la librería, la cargamos nosotros
if (!isset($CI->validation))
{
$CI->load->library('validation');
}

// Llamamos al método
return $CI->validation->valid_email($address);
}

Simple, no?

Igualmente, esto sigue...
¿Qué pasa si en la vista, queremos acceder a una librería o modelo, por ejemplo, a la de validación para recuperar los mensajes?

Las views también pueden acceder como $this->validation al mismo objeto que el controlador o los modelos.
Internamente, esto se logra asignandole estas instancias a la clase Loader, donde se cargan las vistas.

Dos largos meses

Desde mi cambio de trabajo, mis tiempos libres se acortaron y el blog cayó al olvido.
Últimamente le dedico mis ratos PHPeros a CodeIgniter, así que voy a empezar a postear al respecto.

Lo básico. CodeIgniter es un Framework que funciona en PHP 4.3.2 en adelante. Es muy liviano y muy flexible. Inspirado en Ruby on rails. Utiliza el patrón MVC. Promueve las URLs amigables. La documentación es muy buena (sólo en inglés).