Archivo de la categoría: Programación

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

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.

Patrón singleton con herencia en PHP

Pensaba que sería más sencillo. Me he tirado varios días hasta conseguir que todo funcione correctamente.

La idea es implementar el siguiente diagrama diseñado con ArgoUML:

diagramadeclase11

Es decir, una clase base de la que heredan otras.

El escenario consiste en distintos webservices a los que tengo que llamar dentro de la aplicación. Lo que he hecho ha sido mapear cada webservice con una clase que contenga los mismos métodos que el webservice, de manera que lo que haya que hacer contra los webservice se haga contra clases del mismo nombre y con los mismos métodos devolviendo directamente la información devuelta.

Desde el principio planteé que las clases de los webservices extendiesen de una clase base que tuviese la funcionalidad común de todos, es decir, las llamadas a los webservices y el control de errores de los mismos, así evitaría duplicar esta funcionalidad en cada clase.

Posteriormente decidí que cada clase debía implementar el patrón singleton para asegurar que sólo hubiese una instancia de la misma clase en cada momento y evitar así que el código se viciase instanciando una nueva cada vez que se necesita.

La solución global era, por tanto, aplicar el singleton a la clase padre y que todas las que extendiesen de ésa tuviesen automáticamente el singleton y no tener que implementarlo en cada una de ellas. Idealmente es bonito, la práctica fue bastante peor.

La primera prueba fue declarar el método getInstancia encargado de asegurar el singleton de este modo:

private static $instancia = null;

public static function getInstancia($asurl)
{
    $returnValue = null;
    if (!isset(self::$instancia)) {
            $class = _CLASS_;
            self::$instancia = new $class($asurl);
    }
    $returnValue=self::$instancia;
    return $returnValue;
}

Se suponía que haciéndolo así se crearía, en caso de no existir, una nueva instancia del objeto que fue llamado, en nuestro caso, por ejemplo, Clase1. Sin embargo al comenzar las pruebas nos dimos cuenta que _CLASS_ no devuelve el nombre de la clase a la que se llama sino el de la clase sobre la que se ejecuta, en este caso Base, con lo cual no nos servía para nuestro propósito.

A continuación encontramos una función de PHP que nos devuelve lo que nosotros necesitamos, es decir, el nombre de la clase llamada: get_called_class. Lástima que esté disponible a partir de PHP 5.3 (en beta). Aún así encontramos una implementación alternativa de esta función válida para versiones anteriores de PHP.

if (!function_exists('get_called_class')){
    function get_called_class(){
        $bt = debug_backtrace();
        $lines = file($bt[1]['file']);
        preg_match('/([a-zA-Z0-9_]+)::'.$bt[1]['function'].'/', $lines[$bt[1]['line']-1], $matches);
        return $matches[1];
    }
}

Con esto ya podíamos crear la instancia de la clase que necesitábamos desde el singleton, pero nos aguardaba aún una sorpresa. No podíamos instanciar más de una clase (distinta) de las que heredan de un mismo padre, a pesar de ser distintos objetos asume que al heredar del mismo tiene que utilizar la misma instancia del singleton y no te deja, ya que  instancia (la variable del código anterior) ya está generada y es de otro tipo.

Pongamos un ejemplo para verlo mejor. Creamos una instancia de Clase1, el singleton nos devolverá una nueva instancia ya que no existe. Ahora creamos otra de Clase2. El objeto devuelto es de tipo Clase1. Aunque nosotros queremos crear objetos distintos (Clase1 y Clase2), la instancia de Base es compartida por ambos. Como ya he instanciado Clase1, instancia es de tipo Clase1, con lo que al instanciar Clase2 te devuelve la instancia ya creada, Clase1. Podríamos comprobar que instancia (la variable) esté creada y sea del mismo tipo que la nueva que se quiere instanciar, pero al crear la nueva (Clase2) destruiríamos la antigua (Clase1) con lo que perderíamos este objeto, y eso no es lo que se busca.

¿La solución? Parecerá algo cutre, pero es esta:

Base

class osusnet_com_Base
{
    private $asurl = '';
    private static $instancia = array();
    private $iVersion = null;
    private $sAsConsumer = '';
    private $debug = 0;
    protected function __construct($asurl)
    {
        if($asurl==""){
            return false;
        }else{
            $this->asurl=$asurl;
            $this->iVersion=1;
            $this->sAsConsumer="kk";
            $this->debug=0;
        }
    }

    public static function getInstancia($asurl)
    {
        $returnValue = null;
        $class = get_called_class();
        if (!isset(self::$instancia[$class])) {
            self::$instancia[$class] = new $class($asurl);
            $returnValue=self::$instancia[$class];
        }
        return $returnValue;
    }
}

Clase1

class osusnet_com_Clase1 extends osusnet_com_Base
{
    public function GetUserContext($cookie)
    {
    }
    public function CheckStateUser()
    {
    }
    public function LogIn()
    {
    }
}

Es decir, instancia (la variable) pasa a ser un array donde guardaremos un elemento para cada instancia de cada una de las clases que lo heredan. Si buscamos un tipo ya creado nos devolverá esa instancia, en caso contrario la crea y la deja disponible para posteriores instancias. La base es única pero se crea un objeto de cada clase disponible.

Como cualquier patrón singleton, para usarlo debemos hacer:

$instancia = osusnet_com_Clase1::getInstancia($url);

Recuerda que el constructor está protegido con lo cual cualquier intento de querer instanciar una clase de estas directamente dará error, prueba a hacer:

$instancia=new osusnet_com_Clase1();

No se cual será el comportamiento en otros lenguajes, mis años de Java quedan ya algo atrás, ni siquiera se si la teoría de POO es el comportamiento aquí descrito o es una mala implementación de PHP, no soy un experto en la teoria de la programación orientada a objetos. A mi la lógica me dice que si instancias una clase de un tipo, te debería instanciar todo,  sin compartir la clase base, aunque igual también tiene sentido que si todas extienden de Base y ésta tiene el singleton donde instancia y getInstancia son estáticos, sea común a todas. Cuando más lo pienso más me lío.

Al menos encontré una solución.

Adobe AIR VIII – Información encriptada y persistente

Ya hemos visto como trabajar con bases de datos con AIR, sin embargo he deubierto un nuevo método para conservar datos que puede resultar especialmente útil para mantener información sensible, como claves de acceso a servicios, ya que se guarda encriptada. Hablamos de la clase EncryptedLocalStore.

Una de las peculiaridades de esta clase es que es persistente mientras la aplicación no borre sus contenidos, ya que aunque se desinstale los datos siguen ahí, lo que resulta especialmente útil si tus aplicaciones son de pago (guardar licencias) o tienen periodos de prueba (así no se podrá utilizar de nuevo al desinstalar y volver a instalarla).

Cada aplicación dispone de su propia EncryptedLocalStore con lo que los datos que se guarden en una no interferirán con los de otra.

EncryptedLocalStore tiene sólamente tres métodos estáticos (no es necesario instanciar la clase) que permiten guardar, leer y eliminar datos.

  • setItem(“nombre”, valor):  guarda un dato.
  • getItem(“nombre”):  lee el dato.
  • removeItem(“nombre”):  elimina los datos.

Para dar mayor versatilidad al sistema, los datos a guardar deben ser del tipo ByteArray, así que se podrá guardar cualquier dato que se pueda convertir a este tipo y no sólo cadenas de texto.

Veamos un ejemplo:

//guardar datos
var clave:String = "password";
var bytes:ByteArray = new ByteArray();
bytes.writeUTFBytes(str);
EncryptedLocalStore.setItem("clave", bytes);

//leer datos
var datos:ByteArray = EncryptedLocalStore.getItem("clave");
var miclave:String=datos.readUTFBytes(datos.length));

//eliminar datos
EncryptedLocalStore.removeItem("clave");

Extremadamente sencillo y útil, no se como no lo había visto antes, creo que pasa bastante desapercibida esta manera de guardar datos en la documentación.

De esta manera podemos guardar datos sin necesidad de bases de datos. Eso sí, al parecer no deben exceder los 10mb o comenzaremos a tener problemas de rendimiento. Recuerda lo que decía al principio, aunque el usuario desinstale la aplicación los datos de EncryptedLocalStore siguen en su sistema operativo.

Adobe AIR VII – Detectar el modo de inicio de la aplicación

Bueno, pues antes de lo previsto tengo que hacer el primer añadido a la serie de Adobe Air que creía ya terminada 😛 .

La primera curiosidad de hoy se refiere a conseguir que nuestra aplicación se inicie automáticamente al identificarte en el sistema operativo. Muy sencillo:

try{
    NativeApplication.nativeApplication.startAtLogin=true;
}catch(e:Error){
    //el sistema operativo no soporta la opcion
}

Con esto conseguimos que se inicie sóla, pruébalo 😉 . Deberías dejar una opción en algún lado de tu aplicación para que el usuario desactive esta posibilidad, no a todo el mundo le gusta.

La nueva versión 1.5.1 de AIR trajo consigo una interesante funcionalidad para saber si nuestra aplicación ha sido iniciada a mano por el usuario o se ha iniciado automáticamente con el sistema operativo, pero para conseguir que funcione debemos seguir algunos pasos.

Primero debemos actualizar el sdk de AIR a la versión mas reciente desde aquí. Si el sdk de Flex es anterior al 3.3 deberías actualizarlo también desde aquí. El de Flex se descomprime en la carpeta “sdks” de tu instalacion de Flex Builder, el de AIR se descomprime dentro del sdk de Flex que has instalado antes o en el que ya tienes.

Ahora arrancamos Flex Builder y lo primero que haremos será añadir el nuevo SDK de Flex (si lo has instalado) a la lista de sdk’s disponibles. Desde tu proyecto, boton derecho, “Properties” y aquí vas a “Flex Compiler” y a “Configure Flex SDKs…“. Añades un nuevo sdk seleccionando la carpeta donde está instalado, aceptas y en la ventana anterior indicas que utilice este nuevo.

A continuación configuraremos la aplicación AIR donde queremos usar las nuevas funciones, para ello editamos el descriptor de la aplicación, el fichero xml donde se configuran las opciones, y arriba de todo dejaremos la segunda línea así:

<application xmlns="http://ns.adobe.com/air/application/1.5.1">

Es decir, que utiliza el namespace de la versión 1.5.1. Sólo eso.

Ya tenemos todo preparado. La nueva característica consiste en un método del evento InvokeEvent, reason, que nos indica precisamente eso, cómo fue iniciada la aplicación.

Simplemente debemos añadir a nuestra aplicación el detector adecuado y hacer lo que consideres oportuno dependiendo del tipo de iniciación.

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" invoke="detecta(event)">
    <mx:Script>
        <![CDATA[
            private function detecta(a:InvokeEvent):void{
                if(a.reason=="LOGIN"){
                    //la aplicacion se lanza automaticamente al hacer login
                }else{
                    //STANDARD
                    //La aplicacion se ejecuta manualmente
                }
            }
        ]]>
    </mx:Script>
</mx:WindowedApplication>

¿Qué utilidad puede tener? Pues además de las que se te ocurran a ti mismo, puede servir para mostrar o no la ventana de la aplicación si ésta se ha lanzado a mano o “a máquina”.  Por ejemplo, si nuestra aplicación, como vimos en el primer capítulo de esta serie, es de ésas que se quedan residentes en la barra de tareas, puedes iniciarla de distinto modo si la lanza el usuario a mano o si se inicia con el sistema operativo. Normalmente cuando lanzamos nosotros una aplicación esperamos ver la ventana de ésta para utilizarla, mientras que si se lanza sóla preferimos no ver nada, cuando se necesite ya sabemos que está en la barra de tareas y que con un click se abrirá.

Eso es todo. Interesante ¿no? 🙂 .

Adobe AIR y VI – Notificaciones instantáneas ó toast style windows

Llegamos, por fin, al último capítulo de esta serie sobre Adobe Air.

En el proyecto en el que estoy trabajando necesito que se lancen notificaciones ante determinados eventos en el servidor. Podría hacerlo abriendo ventanas normales, claro, pero son avisos que no requieren acción del usuario, sólo son mensajes, así que me gustaría que fuesen no intrusivos, que no molesten al usuario con lo que esté haciendo. Son los típicos mensajes que lanzan algunos programas de esos que se quedan como un icono en la barra de tareas (Messenger, McAfee…).

Tras mucho buscar llegué a la primera conclusión. Este tipo de ventanas las llaman “toast style windows” 😐 . El segundo paso era cómo hacerlas. Seguí buscando y encontré varias clases que implementaban lo que yo quería, si alguien lo ha hecho ya… ¿por qué reinventar la rueda?. Probé varios sistemas y el que más me convenció fue éste, directamente desde Adobe. Sencillamente lo controla todo, desde lanzar varias ventanas y que se apilen una sobre otra a lo alto del escritorio hasta controlar si el usuario está utilizando el ordenador o no para no eliminarlas sin que las vea. Sencillamente perfecto. Con algo de estilos pueden quedar muy bien.

Toast windows con AirEl ejemplo del que os hablo venía para hacer con Adobe Flash, no con Flex, pero se adapta sólo.

Lo primero que necesitaremos son las clases que lanzan las ventanas. Os las dejo aquí.

Y ahora las utilizamos en la aplicación.

import com.xplota.display.DisplayManager;
import com.xplota.display.MessageWindow;

private var displayManager:DisplayManager;

function nuevoMensaje(texto:String):void(){
    displayManager = new DisplayManager();
    displayManager.displayMessage(texto);
}

nuevoMensaje("prueba de nueva ventana");

Ya está! Eso es todo. Sencillo ¿eh? 😉 . Prueba a lanzar varias ventanas y verás como se apilan automáticamente unas sobre otras. Si haces click sobre una de ellas verás que desaparece, da igual en cual, deja ese hueco. Si esperas 10 segundos (configurables), verás que la ventana desaparece sóla, pero si no utilizas ni el teclado ni el ratón, el sistema supone que no estás viendo lo que ocurre y mantiene ahí los mensajes para que no los pierdas. No olvides diseñarlas un poco más chulas que las que trae por defecto 😛 .

Hasta aquí hemos llegado con este minicurso de AIR. Creo que he cubierto la mayoría de cosas que se necesitan para comenzar una aplicación decente en AIR, aún así es posible que añada alguno más, quién sabe…

Espero que os hayan sido útiles.

Adobe AIR V – Trabajando con bases de datos locales sqlite

Llegamos al penúltimo capítulo de esta serie acerca de Adobe AIR. Hoy veremos como trabajar en local con datos persistentes de manera que podamos recuperarlos en otras sesiones. Al decir en local me refiero a que no se necesita conexión a Internet para salvaguardarlos, no se utilizará una base de datos remota sino el propio ordenador del cliente.

A diferencia de las aplicaciones Flash embebidas en un navegador, AIR permite el acceso completo al sistema de archivos del sistema operativo, no tiene las limitaciones de la máquina virtual Flash ni las políticas de seguridad, así que podemos crear, abrir, modificar y guardar cualquier archivo.

Si unimos todo esto con sqlite obtenemos lo que buscamos ya que AIR trae soporte nativo para sqlite con lo que guardar y consultar datos con sentencias sql no será un problema, más sencillo imposible.

AIR permite dos modos de trabajo con sqlite, síncrono y asíncrono. La diferencia es obvia, en el primer tipo se espera a que la ejecución de las instrucciones sql termine para continuar la ejecución de la aplicación, mientras que en el modo asíncrono se utilizan eventos que se disparan cuando se da por finalizada la consulta. En modo sincrono debemos ejecutar todo enjaulado en estructuras try/catch para prevenir errores y excepciones.

Aunque puedes crear la base de datos directamente desde la propia aplicación con sentencias CREATE TABLE, yo prefiero distribuir la aplicación con la base de datos ya creada, es mucho más cómodo. Para crearlas suelo usar dos utilidades:

También puedes hacer tu propia aplicación AIR para gestionar bases de datos sqlite, no conozco ninguna pero sería muy útil. ¿Quién se atreve? 🙂

Con cualquiera de ellas podremos crear la estructura de nuestra base de datos que copiaremos en el directorio del código fuente, para que al compilarla y empaquetarla se distribuya con ella.

Como no todos los sistemas operativos permiten a cualquier usuario escribir en la ruta de instalación de las aplicaciones, lo mejor y más recomendable es copiar la base de datos “base” en la carpeta de usuario del cliente que está ejecutándola ya que ahí siempre tiene permisos. Para hacerlo AIR nos provee de todos los mecanismos necesarios.

Con los métodos userDirectory y applicationDirectory de la clase File accedemos a las rutas de usuario y de la aplicación respectivamente independientemente del sistema operativo sobre el que está corriendo la aplicación, con lo cual hacemos el proceso completamente transparente.

var a:File=File.userDirectory.resolvePath("basedatos.db");
if(!a.exists){
    var b:File=File.applicationStorageDirectory.resolvePath("basededatos.db");
    b.copyTo(a, true);
}

Sencillo y evidente, sobran más explicaciones. Sabiendo esto es ya muy fácil conectar a la base de datos. Utilicemos el método síncrono antes comentado.

private var dbFile:File;
private var conn:SQLConnection;
private var q:SQLStatement;  

private function iniciaDB():void {
    var dbFile:File=File.userDirectory.resolvePath("basededatos.db");
    if(!dbFile.exists){
        var b:File=File.applicationStorageDirectory.resolvePath("basededatos.db");
        b.copyTo(dbFile, true);
    }
    conn = new SQLConnection();
    try{
        conn.open(dbFile);
    }catch (error:SQLError){
        Alert.show("ERROR ABRIENDO BBDD "+error.message+" "+error.details);
    }
}

¡Ya estamos dentro de la base de datos! Sólo nos queda ejecutar consultas y recuperar los resultados. No tiene mucha ciencia. Veamos un ejemplo para que queda bien claro.

var q:SQLStatement;
var result:SQLResult;

q.text = "SELECT campo1, campo2 FROM tabla";

try{
   q.execute();
   result=q.getResult();
   if(result.data){
     var numResults:int = result.data.length;
     for (var i:int = 0; i < numResults; i++){
        var r:Object = result.data[i];
        //accedemos a los campos desde el objeto, r.campo1, r.campo2
     }
   }
}catch (error:SQLError){
  //la consulta nos ha devuelto un error
}

Eso es todo como iniciación sqlite desde AIR. Del mismo modo se pueden ejecutar instrucciones INSERT, DELETE

A partir de aquí puede seguir cualquiera 😛 .

En el próximo y último capítulo veremos como hacer ventanas de notificación instantáneas y no intrusivas, del estilo de los avisos de conexión del Messenger 😉 .

Adobe AIR IV – Detectando el estado de la conexión

Continuamos con la serie de artíulos sobre Adobe Air. Hoy trataremos un tema curioso como es detectar si el cliente dispone de conexión a Internet. ¿Qué utilidad puede tener saberlo? Sencillo, permitir que la aplicación trabaje en modos offline y online.

Esta parte viene bastante bien explicada en el manual. Existe un evento “NETWORK_CHANGE” que no sirve para nuestro propósito 😐 .  ¿Para qué sirve entonces? Para detectar cambios en las interfaces de red en general. Este evento se dispara cada vez que se producen modificaciones en alguna de las interfaces de red de la máquina que ejecuta la aplicación, lo que no quiere decir que cambie el estado de la conexión a Internet ya que este evento no sabe si hay o no salida a Internet ni a través de cual de las interfaces se sale. ¿Qué utilidad puede tener entonces? No lo sé 😛 , pero ahí está el evento por si lo quieres utilizar para algo…

NativeApplication.nativeApplication.addEventListener(Event.NETWORK_CHANGE, onNetworkChange);
function onNetworkChange(event:Event)
{
    //Check resource availability
}

Para nuestro propósito debemos utilizar la clase URLMonitor que, como su propio nombre indica, sirve para monitorizar una URL. En efecto, así es como AIR detecta el estado de la conexión, conectando periódicamente a la URL que le indiquemos. Si no puede alcanzarla asume que te has quedado sin salida a Internet. Vamos, que si estábais esperando algo superavanzado y chulo, podéis seguir esperando, es el método más tradicional que existe: leches, no puedo conectar con Google, habré perdido la wifi. Es lo mismo 😛 .

import air.net.URLMonitor;
import flash.net.URLRequest;
import flash.events.StatusEvent;

var monitor:URLMonitor;
monitor = new URLMonitor(new URLRequest('http://www.adobe.com'));
monitor.addEventListener(StatusEvent.STATUS, announceStatus);
monitor.start();

function announceStatus(e:StatusEvent):void {
    trace("Status change. Current status: " + monitor.available);
}

Dentro de la función, lo que nos interesa es el valor de monitor.available, true ó false, lo que nos indicará si podemos o no conectar con la URL. Éste es, en realidad, el concepto de URLMonitor, determinar si una URL es alcanzable o no. Que una URL no sea alcanzable no quiere decir que no tengas acceso a Internet, sólo que no tienes con aquella URL. La comprobación, por tanto, debería hacerse con la URL del backend de nuestra aplicación y no con cualquier URL en general ya que de quien depende nuestra aplicación es de nuestro backend, no de Google 😛 .

El mismo sistema se puede utilizar para comprobar el estado de cualquier aplicación que escuche en un puerto distinto al 80. La explicación sería exactamente la misma y lo haríamos de este modo:

import air.net.ServiceMonitor;
import flash.events.StatusEvent;

socketMonitor = new SocketMonitor('www.adobe.com',6667);
socketMonitor.addEventListener(StatusEvent.STATUS, socketStatusChange);
socketMonitor.start();

function announceStatus(e:StatusEvent):void {
    trace("Status change. Current status: " + socketMonitor.available);
}

En el próximo capítulo veremos cómo añadir potencia a nuestras aplicaciones utilizando sqllite para guardar datos offline. Si combinamos la posibilidad de guardar datos en la aplicación con la de comprobar el estado de red contra el backend podremos hacer que nuestra aplicación funcione en modo offline guardando lo que necesitase enviar y procesándolo cuando tenga conexión. Creo que no se ha entendido bien, pongamos un ejemplo práctico.

Tengo una aplicación que permite enviar SMS a móviles a través de una pasarela HTTP que será quien haga los envíos. Mi aplicación pide el número de destino y el texto a enviar, conecta con la pasarela y le pasa el mensaje que quiero enviar. Como es lógico, si en el momento de enviar no tengo conexión con la pasarela, no podré enviar el SMS. Tengamos ahora presente lo que acabamos de ver en este artículo… ¡puedo saber en cada momento si la pasarela HTTP es alcanzable!  Si cuando voy a hacer el envío no tengo conexión, los guardo en mi base de datos sqllite y cuando cambie el estado de la conexión (URLMonitor me avisará) lanzo todos los envíos pendientes.

Seguro que si lo piensas un poco tú también le encuentras alguna utilidad 🙂 . Lo dicho, en el próximo capítulo… sqllite.

Adobe AIR III – Actualizando automáticamente las aplicaciones

He aquí otra de esas cosas superchulas que vemos en aplicaciones de otros. Ejecutas una aplicación y automáticamente te dice que hay una nueva versión y si quieres actualizarla. Si aceptas, se actualiza ella sola.

Esto en AIR es extremadamente sencillo, sólo necesitaremos unas pocas líneas de código 🙂 .

La primera parte consiste en invocar la comprobación desde tu aplicación, en el evento creationComplete.

private function actualiza():void{
appUpdater.updateURL = "http://tuservidor.com/update.xml";
appUpdater.isCheckForUpdateVisible = false;
appUpdater.addEventListener(UpdateEvent.INITIALIZED, onUpdate);
appUpdater.addEventListener(ErrorEvent.ERROR, onError);
appUpdater.initialize();
}

private function onUpdate(event:UpdateEvent):void {
	appUpdater.checkNow(); // Go check for an update now
}
private function onError(event:ErrorEvent):void {
	Alert.show(event.toString());
}

La segunda es crear un xml en la ruta indicada en “updateURL” con la información de la última versión disponible y la url a la misma. Muy sencillo.

<?xml version="1.0" encoding="utf-8"?>
<update xmlns="http://ns.adobe.com/air/framework/update/description/1.0">
<version>v0.2</version>
<url>http://tuservidor.com/tuaplicacion.air</url>
<description><![CDATA[
v0,2
Aquí puedes incluir información sobre la nueva versión...
Nuevas funciones, opciones...
]]></description>
</update>

Sube el XML al servidor junto a la nueva versión de la aplicación. Recuerda que al compilarla deberás decirle que la versión ha cambiado ya que este es el parámetro que se utiliza para la comprobación, el número de versión.

Eso es todo, tu aplicación se actualizará automáticamente si hay nuevas versiones.

En el próximo capítulo veremos cómo detectar el estado de la conexión a Internet y su utilidad.