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);
}
}

jueves, 4 de octubre de 2007

Parametros flexibles, retornos duros

Al programar una función, o un método de una clase, siempre es lo mejor poder recibir cualquier tipo de parámetro. Ver si es más cómodo mezclar dos parámetros en uno, usando un array, o si un dato puede ser de varios tipos. Todas estas cosas hay que tenerlas en cuenta para que luego sea menos difícil trabajar con lo que ya está hecho. Otra cosa que debemos intentar es siempre devolver el mismo tipo de dato, incluso si no se pudo realizar lo que se esperaba, entonces si se debe devolver un array, pero hubo un error podemos devolver un array vacío en vez de false o null. Un ejemplo simple de esto



function select($nombre, $valores = array(), $seleccionado = '', $atributos = array())
{
if (!is_array($valores) || count($valores) == 0)
{
return '';
}

if (is_string($nombre))
{
if (is_array($atributos))
{
$atributos['name'] = $nombre;
} else
{
$atributos .= ' name="' . htmlentities($nombre) . '"';
}
}
$out = '<select';
if (is_array($atributos))
{
foreach ($atributos as $atributo_nombre => $atributo_valor)
{
$out .= ' ' . $atributo_nombre . '="' . htmlentities($atributo_valor) . '"';
}
} else
{
$out .= ' ' . $atributos;
}
$out .= '>';
foreach ((array)$valores as $valor_clave => $valor_etiqueta)
{
$out .= '<option value="' . htmlentities($valor_clave) . '"' . ($valor_clave == $seleccionado || (is_array($seleccionado) && in_array($valor_clave,$seleccionado)) ? 'selected="selected"' : '') . '>' . htmlentities($valor_etiqueta) . '</option>';
}
return $out . '</select>';
}

echo select('select',array('a' => 'A', 'b' => 'B','c' => 'C','d' => 'D'),array('a'),array('id' => 'select', 'multiple' => 'multiple'));
echo PHP_EOL;
echo select('select',array('a' => 'A', 'b' => 'B','c' => 'C','d' => 'D'),'a','id="select"');
echo PHP_EOL;
echo select('select');

domingo, 30 de septiembre de 2007

Procesamiento de formularios

Siempre, el mayor problema, es procesar datos que se envían. Vamos a hacer un seguimiento de que cosas hay que revisar para cada formulario. Como noción previa, tenemos que tener en cuenta que todos los datos que envía el client (POST, GET, COOKIE) poco confiable.
  1. Revisar el permiso del usuario
    No todos los usuarios tienen permisos para todos los formularios, eso es claro. Hay que establecer si el usuario puede enviar ese formulario o no, o si sólo lo puede hacer en parte.

  2. Validar todos los datos
    Significa que por cada formulario que se le pide, hay que establecer condiciones. En general, los campos de texto tienen que tener un largo máximo (lo más normal es establecerlo de acuerdo a la base de datos) o fijo (por ejemplo, los códigos de tarjetas de crédito). También hay que revisar el formato (usualmente para las direcciones de e-mail). Hay datos que sólo pueden ser numéricos, o fechas que pueden no ser válidas.
    Los checkbox, radio buttons y dropdowns tienen un rango de opciones prefijados y no se pueden salir de ellos.
    Todas estas cosas hay que tenerlas en cuenta, porque un dato erróneo puede hacer que se pierda información, pero se puede evitar si se le fuerza al usuario a corregir ese error.

  3. Escapar datos
    Dependiendo el destino de los datos ingresados, hay que escapar los datos de la forma que corresponda. En general, vamos a guardarlos en bases de datos, por lo que las funciones *_escape_string nos proveen una forma fácil de escapar los datos de acuerdo al tipo de base de datos que usemos.
    En este punto, hay que tener cuidado con las "comillas mágicas" (magic_quotes_gpc), que si están habilitadas pueden hacer que los datos sean doblemente escapados. Lo mejor es quitar las comillas, si esta directiva está en funcionamiento.
    ¿Por qué no nos olvidamos de escapar datos si tenemos magic_quotes_gpc?
    La idea de magic_quotes era no tener que escapar datos, y facilitarle la tarea al programador. El problema de esta función, es que escapa datos en forma arbitraría y sin tener en cuenta en donde se utilizan los mismos.
    Otro posible error es querer guardar datos que no envió el usuario, pero por tener magic_quotes_gpc On no escapamos, incorrectamente.

  4. Guardar los resultados
    Los datos son válidos, están preparados... Lo único que queda es guardarlo, y hacer cualquier otro proceso que sea necesario.

  5. Mostrar los resultados
    Todavía no terminó nuestro trabajo. La información que guardamos, probablemente la queramos mostrar a alguien. En este punto es muy importante escapar los datos nuevamente. Anteriormente los guardamos tal cual lo mandó el usuario, pero a la hora de mostrarlo en una página, hay que evitar que se haya podido ingresar información malisiosa. En general, escapar las entidades HTML es suficiente para esto, pero sería mejor escapar todas las entidades, incluso las más "raras" para presentar correctamente la información.
Estos 5 puntos son necesarios para la mayoría de los procesamientos de datos. Parece engorroso y complicado, pero no lo es realmente.

martes, 25 de septiembre de 2007

Conexión por IP

Recientemente descubrí que es más rápido conectarse a un servidor utilizando su IP que su nombre. Por ejemplo, en vez de mysql_connect('localhost'); uno puede usar mysql_connect('127.0.0.1'); y termina mejorando la velocidad de conexión. Una pequeña optimización, pero frecuente.

domingo, 23 de septiembre de 2007

Guardar elementos de un array en una variable

El otro día, me llamó la atención leer que es más lento acceder a un elemento de un array que a una variable. Decidí probar cuanta es la diferencia, y si vale la pena. Mi conclusión es que la diferencia existe y si se llama más de 10 veces al mismo indice, puede valer la pena crear una variable para eso, pero igualmente sólo es recomendable hacerlo dentro de una función, en un "alcance" cerrado para que no quede en la memoria todo el tiempo.

Ver ejemplo

jueves, 20 de septiembre de 2007

Leer el manual

Tengo que leer el manual
Tengo que leer el manual
Tengo que leer el manual
Tengo que leer el manual


Sí, es aburrido, pero hay que tener en cuenta que PHP cuenta con muchísimas funciones que quizás uno ignora y podemos evitarnos redescubrir la rueda.
También hay que leer los comentarios de los usuarios, muchos agujeros se tapan allí.

La última función que descubrí: dl

domingo, 16 de septiembre de 2007

Recorrer un array

Quizás suene reiterativo al post for - while, pero ahora en vez de ejecutar un código N veces, quiero recorrer todas las posiciones de un array. ¿Cuál es la forma más conveniente?
En primer lugar, tenemos el for optimizado.
Por otra parte, podemos utilizar el foreach, que justamente recorre el array de datos.
Una última alternativa es ir recorriendo el array utilizando su puntero interno.

En este caso, la mejor alternativa es el foreach, especialmente dedicado para esto.

Ver ejemplo

jueves, 13 de septiembre de 2007

Post o Get

¿Cuál es el criterio para elegir si un formulario debe ir por Post o por Get?
La w3c nos da una lista para decidir. Vamos a utilizar esa lista como base, y tratar de ampliarla un poco

GET
  • W3C: La interacción es más como una pregunta (?)
  • Es un dato que sirve como guía para la presentación
  • Se quiere poder ofrecer la posibilidad de copiar y pegar para guardarla, ingresarla en una página, pasarsela a otra persona

POST
  • W3C: La interacción es más bien una orden
  • W3C: La interacción cambia el estado del recurso en camino tal como lo percibió el usuario
  • W3C: El usuario espera resultados de la interacción
  • Resalto la segunda de la W3C como muy importante: tratar de mantener los procesamientos de formularios como Post
  • Maneja datos "sensibles" como contraseñas
  • Es mucha la información (la cantidad de caracteres de la URI puede ser limitado pro el servidor)

AMBOS
  • Cuando se procesan datos, es buena costumbre realizar Post/Redirect/Get

martes, 11 de septiembre de 2007

Reporte de errores

Uno no programa en forma correcta, si existen errores. Y si estos errores se escapan de nuestra vista, será más difícil detectarlos y corregirlos. Un error que no provoca consecuencias negativas, sigue siendo un error. Lo más común, en este sentido, es comprobar el valor de variables que no están inicializadas, o no sabemos si lo están... Si probamos la diferencia entre

1) if ($variable)
2) if (isset($variable))
3) if (!empty($variable))

Lo primero que me llama la atención es cuanto más lento es la primera opción, si la variable no está definida, pero si está variable está definida es más rápida que empty y que isset, con lo que si se está seguro que la variable existe, esta es la mejor solución, pero ante la duda es preferible utilizar empty/isset.

Volviendo al tema de los errores, siempre que se codifica es bueno ver los errores, por lo tanto siempre es bueno ejecutar

error_reporting(E_ALL);

mientras se está desarrollando, y reducir el nivel a E_NONE cuando se está en un sitio live, o eliminar el error_display

Ver ejemplo

domingo, 9 de septiembre de 2007

Listar directorio

Es común la necesidad de buscar todos los archivos en una carpeta. Lo más común siempre es usar opendir, pero la funcion glob nos acerca al lado oscuro de la fuerza, ofreciendonos una alternativa más simple... Sin embargo, esta solución es más lenta.
Otra alternativa es la funcion dir, que nos devuelve un objeto directorio, una solución más "elegante" si se quiere, pero no es más que un wrapper de lo mismo, por lo que no es más rápido.

Ver ejemplo

viernes, 7 de septiembre de 2007

Evitar dobles llamados a funciones

Se me ocurría que podía agarrar cualquier función y optimizarla mediante almacenar resultados ya calculados en una variable temporal.
Por lo tanto, agarré una fórmula matemática común, la "cuadrática" que se utiliza para obtener las raíces de un polinomio de grado dos, y empecé a probar formas de mejorarla.

Varios fueron los intentos, y la conclusión a la que llegué es que si bien es mejor almacenar resultados de llamados de funciones, no es lo mejor guardarlo de operatorias más simples como la multiplicacion.

Ver ejemplo

miércoles, 5 de septiembre de 2007

Par o impar

¿Cómo sabemos si un número entero es par o impar?
Por supuesto que no es muy complicado.
Cuando recién comenzaba, recuerdo haber buscado una forma de hacerlo, no muy práctica

floor($a / 2) == $a / 2;

El resultado es correcto, pero esto se podría facilitar aún más conociendo el operador %

$a % 2 == 0;


Sin embargo, se puede hacer todavía más rápido, aunque no necesariamente fácil, verificando si el binario contiene al 1 o no.

$a & 1 == 0

Ver ejemplo

sábado, 1 de septiembre de 2007

str_replace - strtr

Estas dos funciones son muy parecidas. En general, str_replace es más famosa, pero ¿cuál conviene? Vamos a probar...

str_replace('e','a','hello world');
strtr('hello world','e','a');

Vamos a comparar un reemplazo simple. En esta comparación, str_replace se lleva un punto.

¿Qué pasa si se quiere reemplazar un texto que no se encuentra?
Ambas funciones funcionan más rápido, pero str_replace sigue siendo más óptima. En esta instancia, ya parecería mejor esta opción, pero ambas funciones nos permiten reemplazar varios textos a la vez. Probemos.

str_replace(array('o','e'),'a','hello world');
strtr('hello world',array('o' => 'a','e' => 'a'));

En este caso reemplazamos "o" y "e" por "a", y str_replace sigue siendo mejor... Vamos a darle una última oportunidad a strtr

str_replace(array('o','e'),array('i','o'),'hello world');
strtr('hello world',array('o' => 'i','e' => 'o'));

En este caso, se reemplaza 'o' por 'i' y 'e' por 'a', y sorpresivamente strtr se lleva la victoria.

Conclusión: en general, es preferible utilizar str_replace, pero cuando se quiere reemplazar un conjunto de caracteres por otro conjunto (y no un único valor) es mejor strtr.

Ver ejemplo

miércoles, 29 de agosto de 2007

Nombres de variables

Es importante que nuestro código después pueda ser leído, ya sea por uno mismo o por otros, por lo tanto es muy recomendable que los nombres con los que designemos a las variables sea explicativo de que es lo que contiene.
Personalmente, no estoy de acuerdo con incluir el tipo de dato en el nombre de la variable, sino lo que representa.
Cómo excepción común a esta regla están las variables que no tienen un sentido real, sino que son útiles, cómo por ejemplo

for ($i = 0; $i < N; $i++) {
for ($j = 0; $j < M; $j++) {

}
}

domingo, 26 de agosto de 2007

for - while

Usualmente, para iterar un código N veces se hace

for ($a = 0; $a < N; $a++) {

pero pensandolo un poco, este código se puede optimizar, ya que la segunda y tercer sentencia se pueden juntar, con lo que llegamos a lo siguiente

for ($a = -1; ++$a < N;) {

También se podría encarar de esta forma

$a = 0;
while (++$a < N) {

pero la anterior parece (apenas) mejor, en la práctica. Si bien esta optimización de código mejor muy poco la velocidad, hay que tener en cuenta que es una de las iteraciones más comunes y se puede repetir mucho a lo largo de nuestro trabajo.

Ver ejemplo