Archivo por meses: julio 2009

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

Playa de los Muertos, Carboneras

El último fin de semana estuve pasando unos días en Mojacar (Almería), y una de las pocas excursiones que hicimos fue a la conocida como Playa de los Muertos.

La ruta nos lleva desde Mojácar hasta Carboneras por una sinuosa carretera de montaña donde, una vez comenzamos a bajar hacia nuestro destino, obtenemos una imponente vista del Parque Natural de Cabo de Gata-Níjar en la costa almeriense con la desgraciadamente conocida como playa del Algarrobico en primer plano y la de los Muertos al final de todo. Digo desgraciadamente porque esta playa se ha hecho famosa por las  denuncias de Greenpeace contra la construcción del hotel que se ve aproximadamente en el medio de la foto, una mole de hormigón en medio de semejante paraje natural y que parece ser ya ha sido declarado ilegal. No te dejará indiferente la aberración.

Parque Natural de Cabo de Gata-Níjar, Algarrobico

Continuando el camino y ya pasado Carboneras en dirección a Almería nos cruzamos con una fábrica de cementos y justo un poco más adelante encontraremos el aparcamiento de la playa, a ámbos lados de la carretera y con una pequeña tienda de venta de bebidas, y es que, como veremos, las bebidas frescas se harán imprescindibles.

Esto que se ve en la foto siguiente, esa esquinita de arena que se ve en medio, será nuestro objetivo.

Playa de los Muertos

Tras una bajada a pie de unos 15 minutos por un camino empedrado, a veces peligroso llegamos a lo más parecido al paraíso convertido en playa.

Playa de los Muertos

Enclavada en un paraje totalmente natural y cercada completamente entre montañas y rocas de caprichosas formas, lo más increíble es, sin duda alguna, el agua color turquesa  y completamente transparente que baña su arena. Los Muertos no es, sin embargo, un arenal como tal ya que no es de fina arena sino de pequeñas piedrecitas totalmente redondeadas, lo que la hacen muy cómoda como playa ya que no tiene las molestias de la arena pero increíblemente incómoda para el paseo, más bien imposible.

Plñaya de los Muertos

Playa de los Muertos

Playa de los Muertos

Playa de los Muertos

El nombre de la playa tiene su origen en la amarga historia de  muertos que el mar escupía en esta playa, piratas, comerciantes, marineros… todos eran arrastrados aquí por las peligrosas corrientes que existen, algo de lo que te das cuenta al bañarte en ella. Pese a la gran cantidad de niños que había en la playa, no creo que sea la más adecuada, ya no sólo por la peligrosidad de sus aguas sino por la incomodidad, tiene un enorme escalón que salvar para entrar en el agua y de golpe te cubre 3/4 de cuerpo, pero es que la salida es mucho más complicada debido a este escalón, necesitarás la ayuda de alguna de las grandes olas que se llegan a ver.

Playa de los MuertosNo os engañeis, pese a la incomodidad del acceso, la playa no está vacía, de hecho los dos aparcamientos están prácticamente llenos. En la playa no hay servicios de ningún tipo, ni duchas, ni chiruinguitos ni nada, todo lo que necesites debes llevarlo tú mismo, sobre todo bebida bien fría.

Lo peor de todo, el camino de vuelta, ahora cuesta arriba, pero creedme que vale la pena, la vista en la playa es impresionante mires a donde mires excepto a la esquina izquierda donde sobresale la fábrica.

Generando online imágenes de información de carga de Ajax

Hoy vamos con algo que personalmente me parece muy útil y que he utilizado en multitud de ocasiones.

¡Quién no ha necesitado en algún momento una imagen de esas típicas de información de actividad que aparecen en una aplicación  web cuando lanzas una acción AJAX!

5-0

La solución: ajaxload.info.

ajaxloadinfo

Desde ajaxload.info podemos generar nuestra imagen de “loading” de muchas formas diferentes personalizando los colores, directamente y online.

Hay gran cantidad de formas distintas que se pueden utilizar, desde las más típicas hasta otras mucho más originales, todas totalmente personalizables según tus necesidades.

combo

Una vez has definido cómo quieres tu imagen puedes visualzar el resultado hasta que te satisfaga completamente y descargarla. Este sería el resultado.8-1

Otra aplicación similar es Preloaders.net, aunque yo prefiero la anterior.

Webservices: Tratando con cabeceras SOAP en PHP (2)

Tras el artículo anterior dónde explicaba cómo leer las cabeceras en una respuesta SOAP, he descubierto cómo hacer funcionar el método estándar, seguro que a más de uno le viene bien saberlo.

El problema era que con __soapCall no había manera de que me funcionase la llamada, independientemente de recibir las cabeceras de la respuesta. Ahora sé porqué. El webservice al que estaba llamando está hecho en .NET y parece ser que hay que llamarlo de distinta forma que si se hace invocando al método directamente 😐 .

Si lo llamamos directamente hacemos:

$result = $client->TuMetodo($parametros);

Si lo llamamos con __soapCall haremos:

$result = $client->__soapCall("TuMetodo", array("parameters"=>$parametros), NULL, $reqheaders, $resheaders);

¿Veis la diferencia?

De la segunda forma hay que pasar los parámetros de entrada del método cómo un sólo parámetro “parameters“, es decir, el mismo array que teníamos con la primera manera pero asignándolo a “parameters“.

Eso es todo. Ahora ya funciona y podemos recoger automáticamente los headers de la respuesta SOAP.

Sigo sin saber porqué de esta manera se tiene acceso a las cabeceras y con la invocación directa (la forma recomendada) no. Por el momento voy a seguir utilizando el desarrollo que hice en el primer artículo, la invocación directa me parece más elegante y ya que había conseguido recuperar las cabeceras, ¿por qué cambiarlo ahora? 😛 .

Webservices: Tratando con cabeceras SOAP en PHP

Llevo ya un tiempo bastante liado con webservices a los que debo llamar con PHP y hoy me ha tocado lidiar con cabeceras SOAP. La verdad es que es un mundo bastante oscuro y me he encontrado con muchas trabas. Os contaré cuales y cómo las he solucionado, pero veamos primero algo de teoría.

Los servicios web se han convertido en el principal modo de intercambio de  información entre aplicaciones independientemente de plataformas, sistemas operativos y lenguajes de programación. SOAP es uno de los protocolos sobre los que se realiza el intercambio de los datos y está basado en XML, de manera que la parte cliente interroga al servidor con un código XML en el formato adecuado y recibe la respuesta en otro XML. Para entender de qué estamos hablando veamos la estructura de una petición SOAP y su respuesta.

Llamada (request):

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="com.xplota.ws">
    <SOAP-ENV:Header>
        <ns1:entity>
            <Code>1</Code>
            <Desc></Desc>
        </ns1:entity>
        <ns1:language>
            <Code>1</Code>
            <Desc></Desc>
        </ns1:language>
        <ns1:userId>
            <Code>1</Code>
            <Desc></Desc>
        </ns1:userId>
    </SOAP-ENV:Header>
    <SOAP-ENV:Body>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Respuesta (response):

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Header>
        <Status xmlns="com.xplota.ws">
            <Code>0</Code>
            <Desc>Ok</Desc>
        </Status>
    </soap:Header>
    <soap:Body>

    </soap:Body>
</soap:Envelope>

Como se puede ver en los listados anteriores, tanto el request como el response constan de dos nodos XML, header y body. El que se utiliza habitualmente es el body (que yo he dejado en blanco pues no nos interesa en este momento) y es el que contendría tanto los parámetros que se envían al webservice en el request como los que devuelve en el response.

Enviando headers soap

En el caso que nos ocupa debía enviar determinados parámetros en el header y leer de allí los potenciales códigos de error si los hubiese habido. El envío, pese a ser una estructura en vez de un parámetro simple, fue sencillo, se define una clase con los parámetros adecuados y se le envía directamente. El motor de SOAP de PHP se encarga de la traducción. Veamos un caso práctico.

//definimos la clase para las cabeceras
class wsHeader
{
    public $Code = 0;
    public $Desc = '';

    public function __construct($code, $desc){
        $this->Code=$code;
        $this->Desc=$desc;
    }
}

//instanciamos el cliente soap
$par=array();
$client = new SoapClient("http://midominio.com/ws?wsdl", $par);

//añadimos las cabeceras a las peticiones
$headers=array();
$headers[] = new SoapHeader("com.xplota.ws", 'entity', new wsHeader(1, ''));
$headers[] = new SoapHeader("com.xplota.ws", 'language', new wsHeader(1, ''));
$headers[] = new SoapHeader("com.xplota.ws", 'userId', new wsHeader(1, ''));
$client->__setSoapHeaders($headers);

//lanzamos la llamada al metodo del ws
$result = $client->TuMetodo($parametros);

Como veis es bastante sencillo de entender. Al añadir una cabecera hay que indicarle el namespace al que pertenece para que el motor SOAP sepa como tratarla, se le da un nombre y el objeto que la contiene.

Con esto hemos solucionado la parte del envío de nuestras cabeceras SOAP y tendremos un request como indicábamos en el primer XML.

Recibiendo headers SOAP

Ahora resulta que el método de nuestro webservice nos responde con otras cabeceras que debemos saber interpretar según el XML de response del segundo listado. Pues tenemos un problema y muy gordo. No hay forma de obtener estas cabeceras, el motor SOAP de PHP sólo devuelve el body, nunca los headers.

Según el manual de PHP el método __soapCall del cliente SOAP permite definir un array en el que se devolverán estas cabeceras, pero no fui capaz de hacer funcionar la invocación de un método del webservice con esta sintaxis mientras que invocándolos directamente en el cliente (cómo la documentación indica que se puede hacer) sí que me funcionaba perfectamente. Es decir, la teoría dice que con el primer método puedo recibir las cabeceras pero no me funcionó mientras que el segundo me funcionaba pero no me devuelve las cabeceras ni hay ningún método para recuperarlas.

Tras pelearme mucho con las funciones SOAP e investigar todavía más no llegué a ninguna conclusión, es como si no le hubiese pasado a nadie, no encontré absolutamente nada útil. Sólo me quedaba una solución, hacer mi propia clase SOAP a partir de la original y procesar el XML del response a mano para obtener los datos que necesitaba. Dicho y hecho. Veamos la solución.

Primero creo mi propia clase de SOAP y compruebo si voy a poder hacer lo que quiero.

class XSoapClient extends SoapClient{
    public function __construct($wsdl, $options){
        parent::__construct($wsdl, $options);
    }

    public function __doRequest($request, $location, $action, $version){
        $response=parent::__doRequest($request, $location, $action, $version);
        return $response;
    }
}
$client = new XSoapClient("http://midominio.com/ws?wsdl", $par);

Parece que voy a tener suerte, si pruebo este nuevo cliente SOAP funciona perfectamente, pero además si compruebo el contenido de $response veo que contiene íntegramente el XML de la respuesta del webservice. Cómo veis lo único que cambia al instanciarlo es que le paso el nombre de la nueva clase. Buen comienzo, si juego bien mis cartas podré sacar las cabeceras en el método __doRequest 🙂 .

Tratemos pues ese XML para obtener lo que buscamos. Gracias a las funciones DOM y XPATH de PHP será muy sencillo. Este es el resultado final de mi cliente SOAP con recuperación de cabeceras:

class XSoapClient extends SoapClient
{
    private $responseHeaders = array();

    public function __construct($wsdl, $options){
        parent::__construct($wsdl, $options);
    }

    public function __doRequest($request, $location, $action, $version){
        $response=parent::__doRequest($request, $location, $action, $version);

        $dom = new DOMDocument;
        $dom->loadXML($response, LIBXML_NOWARNING);
        $path = new DOMXPath($dom);
        $path->registerNamespace('soap', 'http://schemas.xmlsoap.org/soap/envelope/');
        $xml = $path->query('//soap:Header/*');
        $this->responseHeaders=$this->headers2array($xml);

        return $response;
    }

    public function getResponseHeaders(){
        return $this->responseHeaders;
    }

    private function headers2array($response){
        $headers=array();
        foreach ($response as $node) {
            if($node->hasChildNodes()){
                $headers[$node->nodeName]=$this->headers2array($node->childNodes);
            }else{
                $headers[$node->nodeName]=$node->nodeValue;
            }
        }
        return $headers;
    }
}
$client = new XSoapClient("http://midominio.com/ws?wsdl", $par);
$result = $client->TuMetodo($parametros);
$soapheaders=$client->getResponseHeaders();

Problema solucionado y de manera bastante elegante. Si alguien sabe cómo conseguir las cabeceras sin montar todo este lio que me lo cuente por favor.