lunes, 22 de febrero de 2010

Charlas PHP

El Grupo PHP, comunidad de usuarios PHP de Argentina, se complace en ofrecer las primeras charlas organizadas en el marco de su ciclo de conferencias. En este evento, se ofrecerán una diversidad de tópicos con el objetivo de compartir conocimiento, y afianzar los lazos de la comunidad. La agenda de tópicos incluye charlas introductorias, intermedias, y avanzadas.

La reunión se realizará el Sábado 6 de Marzo, de las 9:30 a las 15:00, en el Hotel Las Naciones, Corrientes 818, piso 2. La pre-inscripción al evento es obligatoria.

Para ver la agenda detallada e inscribirse acceder a:

http://grupophp1.eventbrite.com

Más información sobre el Grupo PHP en:

http://grupophp.com.ar

¡Los esperamos!

miércoles, 9 de diciembre de 2009

Locale en Ubuntu

Usar setlocale no es fácil. Hay que saber el nombre exacto del paquete de idioma que queremos usar.

En ubuntu, para ver la lista de opciones disponibles podemos hacer "locale -a". Para instalar un lenguaje nuevo, "apt-get install language-pack-**-base" y luego reiniciar el apache.

jueves, 5 de junio de 2008

Buen sitio: http://www.phpbench.com/ (en inglés)

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).

domingo, 16 de diciembre de 2007

Reflection Class

Desde PHP 5 se cuenta con un conjunto de clases e interfaces que nos permiten obtener información sobre las clases, funciones, métodos, atributos y objetos. Este conjunto nos permite, por ejemplo, saber cuantos parámetros recibe una función, y cuantos son requeridos... Un ejemplo de cómo usar esto.

/**
* Esta funcion repite
*
* @param string $texto
* @param integer $veces
*/
function repeat($texto, $veces = 2) {
return str_repeat($texto, $veces);
}
$reflection = new ReflectionFunction('repeat');
echo 'Definida por el usuario: ' , $reflection->isUserDefined() , "\r\n";
echo 'Documentacion: ' , $reflection->getDocComment() , "\r\n";
echo 'Cantidad de Parametros: ' , $reflection->getNumberOfParameters() , "\r\n";
foreach ($reflection->getParameters() as $parametro)
{
echo 'Parametro "' , $parametro->getName() , '" ' , ($parametro->isOptional() ? '' : 'no ') , 'es opcional' , "\n";
echo 'Por default: ' , ($parametro->isDefaultValueAvailable() ? $parametro->getDefaultValue() : 'no tiene') , "\n";
}
?>

domingo, 9 de diciembre de 2007

JSON

JSON es un formato de transmisión de datos muy útil para pasar de un código a otro.

El ejemplo más común es pasar un objeto o un array de PHP a JavaScript y viceversa. Para esto PHP 5 ofrece dos funciones que hacen el transpaso: json_encode y json_decode. Existen clases que ofrecen un funcionamiento similar para PHP 4.

La ventaja de esta notacion es la transparencia y facilidad con la que se transmiten los datos. XML es una alternativa que también presenta sus ventajas. Es preferible para una serie de datos, si se quieren hacer consultas con XQuery, pero para la transmisión de un dato en particular la sintaxis de JavaScript se presenta como más simple.

martes, 27 de noviembre de 2007

Lista de permitidos vs Lista de prohibidos

Al momento de pensar en seguridad, validación y filtrado de datos, tenemos dos opciones: o decidimos que permitimos o que prohibimos.

Por un lado, se puede usar un filtro para evitar ataques XSS que se base en buscar una expresion regular buscando código javascript (ya sea etiquetas script o atributos onload, onclick, onmouseover...). Esto sería usar "blacklist" es decir, poner código que no se puede usar.

Por otro lado, se puede permitir etiquetas HTML del tipo b, i, u... y todas las otras etiquetas simplemente eliminarlas. Esto sería "whitelist", o sea, limitar al usuario en lo que sí puede hacer.

¿Cuál es preferible?

Al utlizar una "blacklist" uno tiene que pensar en todos los posibles "ataques" que pueda recibir. Esto es un riesgo, ya que si a nuestro atacante se le ocurre un método que a nosotros no se nos ocurrió, estaríamos en un grave problema. Pero, si en cambio, utilizamos un sistema de "whitelist", el usuario sólo podría acceder si cumple con nuestro patrón permitido, cuestión que sería mucho menos probable que pueda concretar un ataque.

La desventaja, por otro lado, de la whitelist, es que podemos filtrar o no permitir información que es, realmente, válida.

Entonces, con una blacklist tenemos mayor posibilidad de ser víctimas de ataques por parte de usuarios malintencionados, mientras que utilizando una whitelist podemos dejar a usuarios correctos sin la posibilidad de ingresar datos.

Personalmente, la segunda opción me parece la mejor, ya que sus consecuencias son menos severas.

jueves, 22 de noviembre de 2007

Mezclar array con objetos

La extensión SPL nos permite tener objetos que actuan como array. Una forma de hacerlo es usar la clase predefinida ArrayObject. Otra forma es implementar una interfase ArrayAccess para poder acceder a nuestros datos como si fueran un array. Un ejemplo de esto

class MyArray implements ArrayAccess {
private $data;

public function __construct($array = array())
{
$this->data = $array;
}

public function offsetGet($key)
{
return $this->data[$key];
}

public function offsetSet($key, $value)
{
return $this->data[$key] = $value;
}
public function offsetExists($key)
{
return isset($this->data[$key]);
}
public function offsetUnset($key)
{
unset($this->data[$key]);
}

public function avg()
{
if (count($this->data) > 0)
{
return array_sum($this->data) / count($this->data);
}
}
}

$array = new MyArray(array(1, 2, 3, 4));
echo (int)isset($array[0]);
echo $array[0];

unset($array[1]);
echo (int)isset($array[1]); //throw an ugly notice
echo $array[1];

$array[1] = 4;
echo (int)isset($array[1]);
echo $array[1];

echo $array->avg();

martes, 13 de noviembre de 2007

Saber si se envió un parámetro con valor por defecto

Una función puede tener argumentos opcionales. Ellos se determinan dándole un valor por defecto al definirla. Por ejemplo

function prueba($clave, $valor = null)

pero cómo podemos diferenciar si se llama a esa función con un sólo parámetro, o si se usan dos y el segundo es igual al valor predeterminado?
La solución es rebuscada, pero no difícil... Simplemente tenemos que buscar cuántos argumentos se pasaron a la función al llamarla


function prueba($clave, $valor = null)
{
if (func_num_args() > 1) {
echo '$valor se envió';
}
}
prueba('1', null);
prueba('1');

jueves, 8 de noviembre de 2007

Nombrar fechas (no en inglés)

Es muy común el problema de querer poner "8 de noviembre de 2007" y tener "8 de november de 2007"... En general se utiliza un switch para traducir el nombre del mes, pero hay una forma mucho más simple de hacerlo. PHP trae la posibilidad de configurar la localización, y de allí elegir configuración "regional" como idioma, moneda, números.

Para ello, primero debemos fijar el ámbito con setlocale y después podemos utilizar el formato de fecha con strftime

Ejemplo

lunes, 5 de noviembre de 2007

Juego de Estados

Juego:
Tomar el nombre de dos estados de Estados Unidos, mezclarlos y luego reordenar las letras para formar el nombre de otros dos estados de Estados Unidos.

Mi solución

¿Alguna sugerencia?

martes, 30 de octubre de 2007

Mejorar paginación

Recientemente descubrí un método para poder mejorar el rendimiento de una paginación en MySQL. Generalmente se necesitan dos consultas a la base de datos, una para traer los datos y otra para saber que cantidad de datos hay en total. Lo nuevo, si bien no altera la cantidad de consultas, es que podemos hacer que el servidor MySQL sólo tenga que procesar una vez.

¿Cómo funciona esto?
En el primer pedido, además de pedirle los datos, le decimos al servidor que calcule la cantidad de filas totales de la consulta, y después hacemos una consulta para conseguir ese dato.
mysql> SELECT SQL_CALC_FOUND_ROWS * FROM tbl_name
-> WHERE id > 100 LIMIT 10;
mysql> SELECT FOUND_ROWS();

Esto es especialmente útil si utilizamos una cláusula ORDER BY, o una consulta compleja, con subqueries, múltiples JOIN o muchos cálculos.

Consultar el manual de MySQL

jueves, 25 de octubre de 2007

Autocargar clases

Desde PHP 5, no es necesario cargar todas las clases al principio de cada script, sino que si existe una función __autoload es llamada cuando se quieren utilizar una clase que no existe. Esta función recibe el nombre de la la clase que se quiere cargar y tiene que encargarse de hacer que exista esa clase para evitar que se arroje un error porque no exista.

Este es un buen avance de PHP 5, que desde la versión 5.1.2 también nos brinda la posibilidad de registrar múltiples funciones para hacer esto, en vez de limitarse sólo a una como era anteriormente, a través de spl_autoload_register (la documentación sólo está disponible en inglés).

jueves, 18 de octubre de 2007

Switch con expresiones

Los switch generalmente son muy útiles, pero al mismo tiempo muy limitados. Buscando la forma, podemos extender su uso, para poder evaluar expresiones, y llamar a funciones. Lo que tenemos que hacer es poner las expresiones en los case para que sea el argumento a comparar.

Por ejemplo, podemos hacer lo siguiente



switch (true) {
case ($variable >= 0 && $variable < 3):
echo '$variable está entre 0 y 3';
break;

case ($variable >= 3 && $variable < 7):
echo '$variable está entre 3 y 7';
break;

case ($variable >= 7):
echo '$variable es mayor a 7';
break;
}

De esta forma, en vez de hacer tres IFs encadenados podemos recurrir a esta opción, para que nuestro código sea más legible.

martes, 16 de octubre de 2007

Comillas simples y dobles

Para la mayoría, las comillas simples y dobles, en PHP, son iguales. Sin embargo presentan diferencias fundamentales. Usando comillas dobles existen más caracteres especiales: \n \r \t \\ \$ \" \[0-7]{1,3} \x[0-9A-Fa-f]{1,2} mientras que con comillas simples sólo tenemos \\ y \'.
Sabiendo ya esto, podemos suponer que las comillas simples son más rápidas. Por otro lado, debemos agregar que dentro las comillas dobles se pueden utilizar variables sin concatenar, sino escribiéndolas directamente en su contenido.
Más información pueden encontrar en el manual de PHP, en el apartado de Cadenas.

Por último, vamos a comparar los tiempos de cada uno, en varias circunstancias.
Ver ejemplo

jueves, 11 de octubre de 2007

Manejar fechas

Es normal tener que modificar el formato de una fecha para adaptarlo a un tipo de base de datos, o para presentarlo a un usuario. En general, strtotime nos provee una buena herramienta para hacerlo, ya que soporta el formato tradicional de MySQL y muchos otros, pero el problema suele estar cuando queremos recibir una fecha en formato dd/mm/yy, ya que por defecto esta función toma el formato estadounidense mm/dd/yy y puede confundirse 01/07 con el 7 de enero, en vez del 1 de julio que esperábamos. La solución que ofrece PHP es con la función strptime, que nos permite indicar el formato de la fecha. Sin embargo, esta función es muy nueva (desde PHP 5.1.0) y no está implementada en Windows, con lo que es bastante limitada. Por lo tanto, podemos crear una pequeña función, con este propósito.

function strtotime2($date)
{
if (preg_match('#^((0?[1-9])|([1-2]?[0-9])|(3[0-1]))/(0?[1-9]|(1[0-2]))/((19|20)?([0-9]{2}))$#', $date))
{
list($day, $month, $year) = explode('/', $date);
return mktime(0, 0, 0, $month, $day, $year);
} else
{
return strtotime($date);
}
}