Paradigma Reflection en PHP

Literalmente, el paradigma Reflection es un proceso mediante el cual el software puede observarse a sí mismo, aprender sobre cómo está desarrollado y modificarse automáticamente. ¿Ha quedado claro?. Ya 😛 .

Supongamos que tienes una clase con unos atributos y unos métodos. Supongamos ahora que desde otra clase necesitas averiguar por cualquier razón qué atributos y métodos tiene esa clase primera. Más aún, supongamos que necesitas saber los parámetros de llamada de cada método. ¡No se puede!. Pues te equivocas 😛 , sí que se puede, el paradigma Reflection es el que nos da la solución y viene implementado de serie en PHP con un conjunto de clases que lo dan todo hecho. Una especie de ingeniería inversa.

Estaréis pensando ¿qué utilidad tiene eso? ¿por qué he de necesitarlo?. Bueno, depende de lo que quieras hacer 😛 . Probablemente si sabes que existe y que se puede hacer, algún día recurras a ello. En mi caso necesitaba crear un sistema dinámico para listar un número indeterminado de clases, sus métodos y poder ejecutarlos con todos sus parámetros, es decir, listar todos los parámetros de un método en un formulario, que el usuario pueda rellenarlos y ejecutar ese método de la clase para obtener el resultado. Se podría hacer cargando en una base de datos todas las clases, sus métodos y los atributos de estos, pero habría que hacer un mantenimiento horrible y tenerlo en cuenta, además, cada vez que se modifique algo ya que el sistema está en desarrollo. Reflection nos aporta una solución más elegante.

Nuestro sistema consta de cuatro pasos:

  • Listar las clases disponibles
  • Una vez se selecciona una clase, listar sus métodos públicos.
  • Al escoger un método, listar sus parámetros en un formulario.
  • Ejecutar el método y devolver el resultado al usuario.

1) Lista de clases

Este paso es el sencillo y obvio, en mi caso simplemente listo los archivos que hay en la carpeta donde guardo todas las clases, no hay más truco. Sólo quedaría montar un combo en un formulario para que el usuario escoja la clase a probar.

$clases=array();
$d=dir("./clases");
while (false !== ($entry = $d->read())) {
 //los archivos de clases son del tipo class.nombreClase.php
 $temp=explode(".", $entry);
 $ext=$temp[count($temp)-1];
 if($ext=='php'){
   $clases[]=$temp[count($temp)-2];
 }
}
natcasesort($clases);
$clases=array_merge(array("Escoge Clase"), $clases);

2) Obtención de métodos de una clase

Hacemos ahora uso de la clase ReflectionClass para obtener los métodos públicos de una clase. Hay muchos más métodos dentro de esta clase para consultar muchísimas cosas.

$methods=array();
if(isset($_GET['clase']) && file_exists("class.".$_GET['clase'].".php")){
	require_once("clases/class.".$_GET['clase'].".php");

	$class = new ReflectionClass($_GET['clase']);
	$metodos=$class->getMethods();

	foreach($metodos as $m)
		$methods[]=$m->name;
	$methods=array_merge(array("Escoge Método"), $methods);
}

Como véis, obtenemos otro array con los métodos públicos de la clase seleccionada, listo para montar otro combo con los métodos disponibles.

3) Obtención de los parámetros de un método

Sabiendo ahora el método y la clase que queremos ejecutar sólo tenemos que averiguar sus parámetros de invocación. Parece imposible, pero de nuevo el API Reflection viene a nuestra ayuda.

$parametros=array();
if(isset($_GET['method'])){
 $class = new ReflectionClass($_GET['clase']);
 try{
   $asmth=$class->getMethod($method);
   foreach ($asmth->getParameters() as $i => $param)
     $parametros[]=$param->getName();
 }catch(ReflectionException $e){}
}

Tenemos de nuevo otro array con los parámetros del método. Sólo debemos montar un formulario con tantos campos de texto como parámetros del método para que el usuario pueda probarlo.

4) Ejecutar el método

Voy a complicarlo más. Todas mis clases tienen implementado un singleton, con lo que no se pueden instanciar tal cual 😛 .Podríamos haber hecho algo del tipo:

$class=$_GET['clase']::getInstancia();

Pero PHP no permite utilizar una variable en el nombre de clase al llamar a un método estático, devolvería un error:

Parse error: syntax error, unexpected T_PAAMAYIM_NEKUDOTAYIM

Lo que traducido del hebrero 😐 significaría “Unexpected double-colon“, es decir, en los dos puntos.

Si no tuviésemos el singleton y no fuese necesario hacer la llamada estática que devuelva la instancia, podríamos hacer algo del tipo:

$class=new $_GET['clase'];
$res=$class->metodo($parametros);

Pero seguiríamos teniendo un problema con los parámetros ya que no sabemos cuántos hay que pasar, no podemos montar algo dinámico en una llamada directa a un método.

Lo primero que debemos hacer es llamar al método getInstancia de mi clase para que nos devuelva una instancia a la misma. Para eso recurrimos a la función call_user_func indicándole la clase y el método a ejecutar.

$class=call_user_func(array($_GET['clase'], 'getInstancia'));

Ahora ejecutamos el método correspondiente sobre la clase ya instanciada pasándole los parámetros necesarios en un array, con lo que no importa la cantidad de ellos que haya, irán todos en una lista. Para ello usamos esta vez call_user_func_array.

$params=array();
foreach($_GET as $name=>$param){
 if($name!="clase" && $name!="method" && $name!="submit")
   $params[]=$param;
}
$res=call_user_func_array(array($class, $method), $params);

Eso es todo. Hemos conseguido averiguar los métodos de una clase, sus parámetros y ejecutarlos salvando todos los problemas que nos hemos ido encontrando.

Espero que os sirva de ayuda 🙂 .

17 comentarios en “Paradigma Reflection en PHP

  1. Pingback: Bitacoras.com
  2. La gente de PHP tardo bastante en implementarlo y es de gran utilidad, en Java se usa con bastante frecuencia. Es otra forma, además de mediante el uso de interfaces, de poder implementar plugins en nuestras aplicaciones.

    OOP powa.

  3. Me fue de gran utilidad. Quiero saber si utilizando reflection puedo lograr conocer si una clase incluye una interfaz, algo como esto:
    include(‘interfaz.php’);

  4. disculpa no cogio el codigo, es algo cmo esto:
    /*quiero comprobar que esta clase incluya a otra llamada interfaz.php*/
    include(‘interfaz.php’);

    /*Esto de abajo ya lo logre con esa clase que dices “getInterfaces()” */
    class Ejemplo implements buscador

    /*la clase interfaz.php lo que tiene es: */
    interface buscador

  5. Muy buen aporte, llevo muchos años trrabajando con lenguajes de escritorio, el ultimo y en el que mas experiencia tengo es .net, y llevo algun tiempo trabajando con php y me ha sorprendido gratamente que exista tal potencia para este lenguaje…

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *