PHP, sesiones y alta concurrencia

Hoy voy a escribir brevemente sobre cierta problemática asociada al manejo de sesiones en PHP que surge cuando nuestra aplicación web debe soportar un alto nivel de concurrencia.

PHP guarda las sesiones en archivos por defecto, aunque hay otros mecanismos posibles: por ejemplo usar bases de datos como MySQL (o bien SQLite), tirar de Memcache, guardar la información cifrada dentro de cookies, etc.

Ninguno de estos sistemas alternativos está exento de problemas. Las bases de datos al uso añaden una latencia inaceptable en la mayoría de casos, SQLite permite lecturas muy rápidas, pero como contrapartida bloquea la base de datos entera cuando se escribe, Memcache puede sobrecargar nuestra memoria y tiene el riesgo potencial de pérdida de datos por la volatilidad de su sistema de almacenaje, y por último, guardar las sesiones en cookies aumenta ligeramente el riesgo de robo de datos y puede añadir un overhead importante a todas las peticiones HTTP.

¿Y qué pasa con las sesiones en ficheros? Pues también tienen su problemática. Cada petición HTTP llega acompañada de la cookie con el ID de sesión, cuando se llama al método session_start se abre un descriptor de fichero que guarda la información asociada a la sesión con el ID ya mencionado y... se bloquea el archivo para poder escribir sobre él los cambios que hagamos en las variables de sesión sin que haya conflictos entre peticiones distintas que usan la misma sesión.

El bloqueo que menciono desaparece por defecto en cuanto finaliza la ejecución del script PHP. Esto significa que si en cierto sitio web realizamos (por ejemplo) 5 llamadas AJAX de forma paralela a scripts que hacen uso de la información de sesión, el resultado que obtendremos es una serie de respuestas serializadas en el tiempo, una detrás de otra.

Una solución parcial para conseguir aumentar el nivel de paralelización de las llamadas AJAX (o cualquier otro tipo de petición HTTP) es desbloquear el archivo de sesión lo antes posible, es decir, cuando sepamos que ya no vamos a modificar las variables de sesión y solo vamos a realizar operaciones de lectura. Para ello podemos usar la función session_write_close, en algunos casos se puede conseguir una reducción impresionante de los tiempos de carga, por lo que os recomiendo tenerlo muy en cuenta.

Otro detalle importante: es desaconsejable permitir que PHP inicie la sesión de forma automática (es preferible iniciarla solo cuando sea estrictamente necesario). Si podéis, intentad que la opción session.auto_start esté a false en el fichero de configuración php.ini.

Espero que os haya servido :) .

Enviar archivos mediante HTTP desde PHP

A raíz de mi trabajo en Bananity recientemente he estado trasteando con la subida de archivos al servidor (hasta ese momento nunca había tenido que tratar con eso) y el envío de archivos entre servidores. Y habiéndome parecido interesante lo aprendido estos dos días he creído conveniente comentarlo por aquí :) .

En primer lugar comentaré la parte más típica: cómo hacer un formulario en HTML que permita archivos y como recibir archivos desde PHP.

En segundo lugar comentaré la parte que a mí me ha parecido más interesante (aunque nada complicada): enviar archivos a otros servidores desde PHP.

Enviar archivos desde un formulario HTML

Enviar un archivo a un servidor web desde un formulario HTML es muy sencillo, resumiendo mucho: se tiene que usar el método POST, usar un elemento input de tipo file, y añadir la propiedad enctype al elemento form con el valor "multipart/form-data". Ejemplo:

1
2
3
4
<form method="post" enctype="multipart/form-data" action="http://sitiodeproceso">
    <input type="file" name="filetoupload">
    <input type="submit" value="Envia!">
</form>

Recibir archivos usando PHP

Muy bien, el visitante de nuestro sitio web ya nos ha enviado su archivo... ¿como lo procesamos para guardarlo? PHP nos provee de unas cuantas variables globales bastante útiles, entre las que se encuentra $_FILES, que es la que utilizaremos. El servidor se encargará de dejar el archivo subido en un directorio temporal, y $_FILES nos dará información sobre su ruta y posibles errores en el proceso de envío.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
 
// Notemos que uso como índice el nombre que le dí al elemento input
// en el formulario HTML
$fileinfo = $_FILES['filetoupload'];
 
// Obtenemos la ruta donde está guardado temporalmente el fichero
$filepath = $fileinfo['tmp_name'];
 
// Recuperamos el nombre que tenía el fichero cuando se envió
$filename = $fileinfo['name'];
 
// Movemos el fichero a donde creamos conveniente
move_uploaded_file ($filepath, '/ruta/donde/queremos/guardarlo/'.$filename);
 
?>

En este ejemplo no he tenido en cuenta la gestión de errores ni otros valores de $_FILES['filetoupload'] interesantes como type, error o size.

Enviar archivos con PHP (1ª opción: CURL)

Ahora sí, llegamos a la parte que me parecía más interesante :) , aunque empezaré por la "vía complicada": usar CURL. Este ejemplo lo he extraído de [1].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_VERBOSE, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_USERAGENT, "AgenteQueNosVengaEnGana");
    curl_setopt($ch, CURLOPT_URL, 'http://urlhaciaotroservidor');
    curl_setopt($ch, CURLOPT_POST, true);
 
    // Igual que <input type="file" name="filetoupload">
    // Notad la @ antes del path, es clave.
    $post = array(
        "filetoupload"=>"@/path/to/myfile.jpg",
    );
 
    curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
    $response = curl_exec($ch);
?>

La verdad es que puede ser un poco engorroso subir archivos siguiendo este procedimiento, no porque sea mucho código, sino porque se tienen que memorizar (o tener a mano) que parámetros debemos establecer y con que valores.

Evidentemente siempre nos queda la opción de encapsular un código similar a éste dentro de un método, pero a mi este "problema" me servirá como excusa para introducir una solución alternativa :D .

Enviar archivos con PHP (2ª vía: Requests)

Gracias a mi compañero Carles Iborra descubrí la biblioteca libre Requests, a la que actualmente estoy dando muchísimo uso, especialmente para realizar testeo unitario, aunque también para enviar archivos usando PHP. El siguiente ejemplo lo dice todo.

1
2
3
4
5
6
7
8
9
10
11
<?php
 
include('Requests.php');
Requests::register_autoloader();
 
$post = array(
    'filetoupload' => '@/path/to/file'
);
$request = Requests::post('http://dondeenviamoselarchivo', array(), $post);
 
?>

Referencias

  1. DTBAKER: Uploading a file using curl in PHP

Mejorar eficiencia en peticiones AJAX

JSON logoLa mayoría de desarrolladores de aplicaciones web desean que éstas sean usadas por miles de personas (y si es a la vez, mejor). Cuando se llega a un determinado número de usuarios, es difícil que la aplicación aguante esa carga si no se han tomado ciertas decisiones. La más simple (y probablemente más cara) pasa por añadir más prestaciones a los servidores con los que trabaja nuestra app, pero evidentemente podemos encontrar soluciones ligeramente mejores.

Hoy en día es extraño encontrar una aplicación web que no haga uso de AJAX, unas lo hacen de manera inteligente y otras no tanto. Vamos a ver qué podemos hacer para reducir un poco(en caso de no haberlo hecho ya) la carga que deben soportar nuestros servidores.

El consejo básico es simple: es preferible que el contenido dinámico sea generado en el lado del cliente con Javascript a que el trabajo lo hagan nuestras máquinas. Es claro que no siempre se podrá hacer eso, pero por lo general sí, como ejemplo tenemos datos que se actualizan periódicamente o que se estructuran de forma tabular o en listados.

La justificación del consejo radica en 2 puntos, en primer lugar podemos reducir el tiempo de CPU en el servidor consumido por petición, en segundo lugar podemos reducir el ancho de banda usado si enviamos datos como JSON en vez de contenido HTML pregenerado. Así dicho suena creíble, pero tenemos que comprobarlo (me fijaré en el caso concreto de PHP).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
 
// Codigo que sirve petición con JSON
$link = mysql_connect('xxx', 'xxx', 'xxx');
 
mysql_select_db('xxx');
 
$dbresult = mysql_query('SELECT nick, passwd, name FROM json');
 
$outresult = array();
 
while ($outresult[] = mysql_fetch_row($dbresult));
array_pop($outresult);
 
echo json_encode($outresult);
 
mysql_close($link);
 
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
 
// Codigo que sirve la petición con HTML
 
$link = mysql_connect('xxx', 'xxx', 'xxx');
 
mysql_select_db('xxx');
 
$dbresult = mysql_query('SELECT nick, passwd, name FROM json');
 
while ($row = mysql_fetch_row($dbresult)) {
    echo '<tr><td>',$row[0],'</td><td>',$row[1],'</td><td>',$row[2],'</td></tr>';
}
 
mysql_close($link);
 
?>

Antes de hablar sobre como se procesa la petición en el lado del cliente haré algunas observaciones. Aunque en este código no está, añadí código para medir cuantos milisegundos tardaba cada script en ejecutarse. Para aumentar la fiabilidad de las pruebas conté solo el tiempo que se tarda en procesar el resultado de la consulta a la base de datos y enviarla. Así pues excluyo el acceso a la base de datos y el cierre de la conexión de las medidas efectuadas, que son las siguientes (había 200 registros en la base de datos):

  • Petición servida en JSON (10 muestras):
    • Número de bytes enviados: 18830
    • Pico de memoria consumida: 305128 bytes ~ 297.976 Kb
    • Tiempo mínimo: 0.62799 ms
    • Tiempo máximo: 1.2610 ms
    • Tiempo medio: 0.9064 ms
    • Desviación típica: 0.1436 ms
  • Petición servida en HTML (10 muestras):
    • Número de bytes enviados: 23854
    • Pico de memoria consumida: 100008 bytes ~ 97.664 Kb
    • Tiempo mínimo: 0.7529 ms
    • Tiempo máximo: 2.4579 ms
    • Tiempo medio: 1.2909 ms
    • Desviación típica: 0.4956 ms

Lo más destacable de estas medidas es el número de bytes enviados, con JSON podemos ahorrar (como mínimo) un 21.06% de ancho de banda. Digo como mínimo porque en este ejemplo el código HTML generado es más bien austero, ya que no incorpora clases CSS ni identificadores para facilitar el manejo del DOM.

El segundo dato nos muestra la cantidad máxima de memoria consumida durante la petición procesada. Como era esperable, la primera versión del código consume más memoria debido al array que va construyendo y a la cadena JSON que ensambla finalmente a partir de la lista generada. En este punto hay que tener en cuenta que, si somos listos, esto no tiene por qué suponer un gran problema ya que ese mismo array podríamos (y de hecho es recomendable) guardarlo usando (por ejemplo) Memcached de forma que ahorramos posteriores consultas a la base de datos.

Respecto a los otros datos tengo que decir que no son significativos en lo tocante al asunto JSON vs HTML, pues la comparativa no es del todo justa. Sin embargo, lo que sí podemos ver es qué supone usar repetidas veces la primitiva echo frente a un solo uso. Por un lado aumentan los tiempos de ejecución, y por otro aumenta la dispersión de las medidas (ver desviación típica), lo que puede tocar un poco las narices cuando queremos hacer cálculos de cara a escalar nuestra infraestructura.

Otra versión del código que genera HTML es la siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
 
$link = mysql_connect('xxx', 'xxx', 'xxx');
 
mysql_select_db('xxx');
 
$dbresult = mysql_query('SELECT nick, passwd, name FROM json');
 
$outresult = '';
 
while ($row = mysql_fetch_row($dbresult)) {
    $outresult .= '<tr><td>'.$row[0].'</td><td>'.$row[1].'</td><td>'.$row[2].'</td></tr>';
}
 
echo $outresult;
 
mysql_close($link);
 
?>

Los datos que arroja esta versión son los siguientes:

  • Petición servida en HTML (2ª versión, 10 muestras):
    • Número de bytes enviados: 23854
    • Pico de memoria consumida: 126432 bytes ~ 123.469 Kb
    • Tiempo mínimo: 0.3531 ms
    • Tiempo máximo: 0.8111 ms
    • Tiempo medio:  0.5435 ms
    • Desviación típica: 0.1577 ms

Podemos extraer algunas conclusiones más de esta tercera lista de cifras. En primer lugar se nos podría ocurrir adoptar el mismo enfoque para la generación de JSON, es decir, olvidarnos de construir un array y pasar a construir un string directamente (de hecho obtendríamos mejores resultados, pues la cadena JSON es más corta que la cadena HTML). Si no vamos a usar más esos datos en un lapso de tiempo breve parece lo más acertado, pero si tenemos planeado acceder a ellos otra vez puede que sí nos convenga construir la lista como en el primer ejemplo de código.

Un detalle interesante es que teniendo un array probablemente sea más rápido generar un texto JSON con la función json_encode que concatenando strings en un bucle. El problema que tenemos (en este caso) es que el driver nativo de MySQL para PHP no permite obtener una lista con todas las filas directamente, y consecuentemente nos vemos forzados a construirla nosotros (que es menos eficiente que usar un método nativo de PHP programado en C, y de ahí que el tercer ejemplo rinda mejor que el segundo).

Tengo que admitir que no conozco por qué no hay un método nativo de PHP para obtener en forma de array todos los resultados de una consulta a una base de datos MySQL, pero creo que, en caso de no haber razones de peso... puede ser una buena jugada implementar en C una extensión del lenguaje que haga precisamente eso para ahorrar un valioso tiempo de ejecución.

En el lado del cliente las diferencias no han sido estadísticamente significativas para mi ejemplo, aunque es de esperar que los tiempos de descarga sean menores con JSON, mientras que los de proceso sean menores con HTML. El tiempo total dependerá de la potencia de cómputo del cliente y del ancho de banda de la conexión, por lo que algunos usuarios saldrán beneficiados mientras que otros puede que tarden algunos milisegundos más en obtener la página web completamente renderizada.

Sin más rodeos, resumen rápido: JSON permite reducir el ancho de banda consumido en más de un 21%, hacer un solo echo reduce el tiempo de ejecución (la reducción puede sobrepasar fácilmente el 50%, en el caso del ejemplo es un 57.89%) y la dispersión estadística de esos mismos tiempos (lo que nos puede ayudar a realizar mejores cálculos para escalar la plataforma o puede servir para que los balanceadores de carga tengan datos más fiables con los que trabajar).

Como apunte final solo quiero destacar que es importante realizar experimentos y comprobar los hechos por uno mismo, pues la tecnología evoluciona constantemente y es probable que ciertas afirmaciones no se puedan sostener en el tiempo. Además me he dejado algunas técnicas en el tintero, como usar los métodos ob_start y ob_end_clean, que nos permiten aprovechar el buffer de escritura para evitar la necesidad de concatenaciones. Supongo que viendo la extensión del artículo comprenderéis que no lo haya comentado en profundidad. Espero que os haya sido útil :) .

Validación de usuarios vía email con CakePHP

Hoy vamos a ver como desarrollar de forma sencilla un sistema que nos permita validar de forma segura que un usuario de nuestra aplicación web efectivamente tiene el email que ha indicado en el formulario de registro. El mismo sistema podría servir también para evitar cambios fraudulentos en los perfiles de usuario, para recuperar cuentas cuando se olvidan claves o para solicitar la eliminación de la cuenta de usuario sin temor a gamberradas ;) .

La idea básica consiste en generar una clave aleatoria durante el proceso de registro, guardarla en la tabla de usuarios (para ello usaremos un campo específico), enviar un email al usuario con un enlace a un método de validación del controlador de usuarios (pasando como parámetros algún identificador del usuario más la clave generada anteriormente), y finalmente, comparar en el mencionado método de validación si la clave pasada a través del enlace corresponde con la clave almacenada en la base de datos. Si dichas claves son iguales entonces se cambia el estado del usuario a "activado" modificando algún campo específico de la tabla de usuarios.

Ahora, antes de pasar a la acción todavía, pensemos en como refinar ligeramente el método para que sea más útil. Si nos fijamos en el último paso, puede parecer que hace falta una columna específica que indique si el usuario ha sido validado o no, pero podremos ahorrárnoslo, y ganar mucho por el camino. Mi idea (y seguro que al de muchos otros) es usar el propio campo dedicado al código de validación. Hay varias formas de hacerlo, pero usaré como ejemplo mi propio procedimiento. Veamos el código y luego analizaremos el proceso más detenidamente.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// app_controller.php ( AppController )
	// Generador de claves que usaremos para nuestro sistema de seguridad
	protected function genPass ($len, $uppercase = true, $lowercase = true, $numbers = true, $sym1 = true) {
		$up_dict = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
		$lo_dict = 'abcdefghijklmnopqrstuvwxyz';
		$nu_dict = '0123456789';
		$s1_dict = '.-';
 
		$pass_dict = (($uppercase) ? $up_dict : '') .
			(($lowercase) ? $lo_dict : '') .
			(($numbers) ? $nu_dict : '') .
			(($sym1) ? $s1_dict : '');
 
		$dict_size = strlen ($pass_dict);
 
		$pass = '';
		for ($i=0; $i<$len; $i++) {
			$pass .= $pass_dict[rand (0, $dict_size-1)];
		}
 
		return $pass;
	}

Nota al canto: he hecho que el generador de claves trabaje solo con un conjunto de 64 tipos de caracteres, esta restricción no tiene mucho sentido, pero me parece un número bonito y lo suficientemente grande. Ahora bien, hay que destacar que ciertos caracteres pueden conllevar problemas, como por ejemplo "+", o ":".

1
2
3
4
5
6
7
8
9
// Users controller -> Register method (before calling the save method of the User model)
 
$vcode = $this->genPass (62);
$this->data['User']['validate'] = 'v:' . $vcode;
 
// Here you should put the code to save the data into the database
// and to send an email to the user, with a link like:
// http://www.yourapp.com/users/validate_email/username/validatecode
// (It's preferible that the user don't know it's user internal id)

La única modificación sustancial que deberéis hacer en vuestro método de registro es crear la clave antes de guardar los datos de usuario en la base de datos (evidentemente la tabla de usuarios deberá tener un campo "validate"), y evidentemente, no olvidar enviar un email al usuario con el enlace de validación.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Users Controller
	public function validate_email () {
		if (count ($this->params) < 2) {
			$this->redirect ('/users/dashboard'); // Or wherever you want
		}
 
		$username = Sanitize::paranoid (
			$this->params['pass'][0],
			array ('_')
		);
		$code = Sanitize::paranoid (
			$this->params['pass'][1],
			array ('-', '.')
		);
 
		$db_code = $this->User->field (
			'validate',
			array (
			    'username' => $username,
			)
		);
 
		if ($db_code == 'v:'.$code) {		
			$this->User->updateAll (
				array (
				    'validate' => '\'' . $this->genPass (64) . '\''
				),
				array (
				    'username' => $username
				)
			);
 
			$this->set ('validated', true);
		} else {
			$this->set ('validated', false);
		}
	}

Supongamos que tenemos una clave de validación de 64 caracteres, difícilmente podríamos romperla, y aunque nos quedáramos solo con 62 caracteres, seguiríamos con un nivel de seguridad parecido. Aprovechando este detalle, en vez de generar claves de 64 caracteres, las construiremos solo con 62, y añadiremos al principio de estas la cadena "v:" (2 sin contar las comillas). Cuando nuestra aplicación detecte que los primeros dos caracteres de la clave de validación almacenada en la base de datos sean "v:" entonces considerará que el usuario aun no ha sido validado. Una consecuencia de ello es que durante el proceso de validación, una buena forma de dar por validado al usuario sería generar una nueva clave (esta vez de 64 caracteres, y sin el caracter ":").

La nueva clave generada para dar por validado al usuario puede usarse, a su vez, cuando el usuario olvide su clave y la aplicación tenga que enviarle un email, para poder verificar que el método de cambio de clave es llamado desde el email del usuario afectado, y no por una persona ajena a su cuenta (De igual forma, se podría usar para verificar eliminación de cuentas, etc.). Eso sí, nunca debe olvidarse crear una nueva clave cada vez que el sistema de verificación es usado para evitar que la seguridad decaiga.

Espero que os haya resultado útil :) , saludos.

Creando un framework con PHP ( II ) : Introspección

A la hora de usar frameworks los programadores esperan cierta magia, que las cosas funcionen sin tener que preocuparse por ciertos detalles nimios, y eso en gran parte se consigue gracias a los métodos de introspección (o reflexión, aunque prefiero el primer término), que en este caso nos son brindados por PHP.

Se podría decir que en PHP hay dos caminos para usar la introspección, el uso de ciertas funciones que aparecieron a partir de la versión 4 de PHP (siguiendo el paradigma de la programación estructurada) , o bien el uso de ciertas clases (y sus respectivos objetos, obviamente) que hicieron su aparición estelar con PHP 5.

Por diversos motivos, en el framework que estoy desarrollando he escogido el segundo camino (en realidad un mix, pero predomina la segunda opción), pero aquí comentaré los dos.

Introspección al estilo de PHP 4

Los primeros métodos que deberíamos conocer son los que nos permiten saber qué clases hay declaradas, o si una determinada clase está cargada ya en el contexto en el que nos encontramos, tenemos estos métodos:

  • get_declared_classes () : retorna una lista que contiene los nombres de todas las clases declaradas.
  • class_exists ($nombre_clase): retorna verdadero o falso en función de si la clase pasada como parametro ha sido declarada o no. Como parámetro podemos pasar una cadena, o la propia clase si ya la conocemos. Ejemplo:
    1
    2
    3
    4
    
    class Prueba {
    }
    class_exists ('Prueba'); // Retorna verdadero
    class_exists (Prueba);   // También retorna verdadero

Una vez sabemos qué clases hay declaradas, también podríamos querer cómo son éstas por dentro, tenemos los siguientes métodos (creo que esta lista no es exhaustiva):

  • get_class_methods ($nombre_clase) : Nos devuelve una lista con los nombres de todos los métodos de la clase
  • get_class_vars ($nombre_clase) : Nos devuelve una lista asociativa con los nombres de las propiedades como claves, y los respectivos valores de las propiedades como elementos de la lista.
  • property_exists ($nombre_clase, $nombre_propiedad) : Nos indica si la clase tiene la propiedad que buscamos
  • get_parent_class ($nombre_clase) : Nos devuelve el nombre de la clase padre (si no hay clase padre, entonces devuelve una cadena vacía). También podemos usar objetos en este caso, no solo clases.
  • is_subclass_of ($nombre_clase, $nombre_clase_padre) : Nos indica si la clase hereda de la clase padre, también podemos usar objetos en este caso, no solo clases.

Podemos hacer prácticamente lo mismo, pero con objetos:

  • get_object_vars ($objeto) : Se parece mucho a get_class_vars , pero se diferencia en que los valores de las propiedades de los objetos pueden variar en tiempo de ejecución, y además se pueden añadir propiedades también en tiempo de ejecución.
  • method_exists ($objeto, $nombre_metodo) : Nos indica si el objeto tiene o no un método concreto

Y aquí nos quedamos, más o menos eso es todo. Antes había ciertas funciones que completaban la parrilla y eran bastante útiles, que servían, básicamente, para hacer uso de callbacks o ejecutar métodos de forma "dinámica", pero fueron marcadas como obsoletas a partir de la versión 4.1 de PHP, por lo que no las comentaré.  El hecho de que "falten" ciertas funciones nos podría obligar a agudizar el ingenio.. o a abusar de funciones como eval , pero por suerte, en PHP 5 se introdujo una nueva forma de trabajar.

Introspección al estilo PHP 5 (clase Reflection)

En PHP 5 se introdujeron diversas mejoras, entre otras el tratamiento de errores se ha orientado más hacia el manejo de excepciones con bloques try-catch , y se han creado más clases que permiten trabajar de una forma mucho más orientada al paradigma de Orientación a Objetos, aumentando en cierta medida la cohesión del código resultante.

Una de las clases que fueron introducidas en esta versión fue la clase Reflection (y algunas otras que derivan de ésta). Todas ellas, cuando se produce algún error, lo que hacen es lanzar una excepción, simplificando la gestión y evitando que tengamos que interpretar el significado de los valores de retorno. Estas son las clases de las que hablaremos (un poco por encima):

Para hacer introspección o reflexión lo que debemos es construir un objeto a partir de una de estas clases, usando como parámetro en el constructor aquello que queramos "ver por dentro". Por ejemplo:

1
$metainformacion_clase = new ReflectionClass ("nombre_de_la_clase");

a partir de aquí contamos con un montón de métodos (muchos más que los que había disponibles en PHP 4) que nos servirán para conocer todo cuanto queramos sobre la susodicha clase. Podéis ver los métodos disponibles en los enlaces que he hecho para cada una de ellas. Es interesante comentar que, algunos métodos devuelven objetos que a su vez también sirven para aplicar introspección, como getMethod ($name) o getProperty ($name) , que devuelven sendos ReflectionMethod i ReflectionProperty.

Uno de los métodos más interesantes de ReflectionMethod es el método invoke ($object, $params) , al cual le podemos pasar un objeto para el que queremos que se ejecute el método reflejado con los parámetros indicados. Esto sería "equivalente" a hacer la llamada (atentos, el ==== no es código, lo uso para indicar una cierta equivalencia):

1
$metainformacion_metodo->invoke($object, $params); ====  $object->metodo_reflejado($params);

Con ésto no es que se pueda zanjar el asunto de la introspección o reflexión, pero hay poco más interesante por decir, casi todo lo demás es simplemente conocer los nombres de las funciones y aplicarlas, así pues, deberíamos pensar en cómo toda esta parafernalia nos puede resultar útil para crear un framework con PHP. Lo veremos en el siguiente artículo, que este ya es suficientemente largo.