miércoles, 10 de julio de 2013

Webservices con Yii Framework



Si, Yii tiene webservices, basados en la técnica SOAP de PHP.

Cuando y Porqué se require un Webservice.

Requieres un webservice para hacer llamadas a métodos que están en un servidor distinto a aquel donde tienes la aplicación, regularmente este es el principal uso aunque pudiese darse el caso de que el servidor sea el mismo pero con el requerido método alojado en una aplicación distinta.  También, un Webservice se usa para aislar funcionalidad, para "componentizar" (asi se le conoce hoy, aunque no me guste el término).

Quien aloja el webservice.


Lo aloja una instancia de CController en algún servidor hecho con YII Framework, el método a ser compartido (el método remoto) se aloja como una función común y corriente dentro de ese Controller (no como un action). El Controller declarará un action estático llamado: "ws".

La URL del Webservice en Yii


Una aplicacion que consume el webservice hecho en YII usará como URL la dirección del "action" "ws" alojado en algún controlador, por ejemplo:
http://coquito.local/prueba/index.php?r=/site/ws

Una aplicación consumidora del webservice va a conectarse a una URL que le dara acceso al mecanismo de webservices de tu aplicación, esta URL que tu provees no es sino la dirección de un "action" especial alojado en alguna controladora.  Este action especial, al ser consultado devolverá un esquema XML, el cual pudieses usar tú como desarrollador para saber qué se esta exportando en ese webservice. Además y por supuesto ese esquema XML es el que será utilizado por el cliente PHP SOAP para saber cómo hablar con tu webservice.

Por esa razón es que hay que declarar un "action estático" llamado "ws" (se hace creando una entrada en el array actions() de la clase CController), si compruebas el resultado de ese action poniendo la URL del webservice directo en un browser verás que se recibe un texto XML con información del web service.

  1. <?php
  2. class SiteController extends CController
  3. {
  4.     public function actions()
  5.     {
  6.         return array(
  7.             'ws'=>array('class'=>'CWebServiceAction',),
  8.         );
  9.     }

No es obligatorio que el action se llame "ws".  Si le cambias el nombre, habrá que cambiarlo en la declaración del action en la controladora y en la URL entregada a los clientes.

Cuando el action "ws" es procesado por tu controladora, tal procesamiento lo hace una clase del paquete "services" de Yii Framework: CWebServiceAction, la cual entregará como respuesta al action a un esquema XML.

La declaración correcta del método a ser exportado en un Webservice.

Cada método remoto alojado en esa clase basada en la clase CController de Yii deberá tener una "anotación" llamada @soap para que el componente de YII encargado en proveer el webservice sepa cuales serán los metodos a exportar (los que tengan la anotación @soap) y además sabrá cuales serán sus argumentos y tipo de retorno usando las anotaciones @param y @return.

Ejemplo de un método SOAP disponible en un Controlador de YII:


  1. /**
  2.      @param string $empleado              <----- defines el tipo de argumento
  3.      @return string                       <----- defines tipo de retorno
  4.      @soap                                <----- declara este metodo como "remoto"
  5. */
  6. public function calcularNomina($empleado){
  7.   return 123;
  8. }


**Notarás que si no pones las anotaciones @ entonces el método o sus argumentos o tipo de retorno no serán exportados para ser consumidos como un webservice.**

Tipos de dato soportados

Como te he mencionado, los tipos de dato que el webservice va a usar *no son* los tipos implicitos de tus variables, sino en cambio son aquel tipo que tu declares en la anotación.  @param string cedula, será considerado como un argumento string para el webservice.

Lo mismo sucederá con el tipo de retorno de la función a exportar.  @return string una nota..

No he profundizado en este punto, cuando lo haga lo documentaré aquí.

En mi experiencia, y para no indagar tanto en los tipos de dato a pasarle al web service, lo que siempre hago es usar tipo "string", y, si es un array lo que debo pasar, o una instancia de una clase, hago lo siguiente:

Casi siempre uso JSON:
$dato = json_encode(array(1=>"uno", 2=>"dos")) 

A veces uso la serialización, aunque trato de no usarla mucho debido a compatibilidad del metodo de de-serialización en otros entornos:
$dato = serialize($instancia_de_un_empleado);

Implementación:

PASO1- Creando al componente "MiCliente.php" en la aplicación que consume el webservice

Instala este archivo en tu carpeta protected/components/ de tu aplicacion web que pretende consumir al webservice remoto.


  1. class MiCliente extends CApplicationComponent{
  2.         private $client = null;
  3.         public $ws_url;
  4.         private function getClienteInt() {
  5.                 if($this->client == null)
  6.                 {
  7.                         // para que reconozca nuevas funciones del WS 
  8.                         ini_set (  'soap.wsdl_cache_enable'  ,  0  );
  9.                         ini_set (  'soap.wsdl_cache_ttl'  ,  0  );
  10.                         $this->client = new SoapClient($this->ws_url);
  11.                 }
  12.                 return $this->client;
  13.         }
  14.         public function obtenerMensajeRemoto($argX) {
  15.                 return $this->getClientInt()->getObtenerMensajeRemoto($argX);
  16.         }
  17. }


PASO2- Instalar el Componente Cliente en la configuración de YII.

  1. // archivo: /protected/config/main.php:
  2.     'components' => array(
  3.        ...bla...
  4.        'cliente'=>array(
  5.              'class'=>'application.components.MiCliente',
  6.              'ws_url'=>'http://elservidor.com/aplicacion/index.php?r=site/ws',
  7.        ),
  8.        ...bla...
  9.     ),


PASO3- Usando el componente "cliente" que consume el webservice.

echo "el mensaje remoto es: ".Yii::app()->cliente->obtenerMensajeRemoto("hola");

PASO4- Creando a el Webservice en el servidor remoto.

requisitos:

1. debe ser una clase basada en Controller.
2. debes crear un action estatico llamado: "ws"
3. el metodo a exportar debe tener la etiqueta @soap
4. los argumentos del metodo a exportar deben declarar sus argumentos usando: "@param string"
4. si el metodo va a devolver valores, hay que declarar el tipo de retorno: "@return string"

en protected/controllers/SiteController.php


  1. <?php
  2. class SiteController extends CController
  3. {
  4.     public function actions()
  5.     {
  6.         return array(
  7.             'ws'=>array('class'=>'CWebServiceAction',),
  8.         );
  9.     }
  10.      /***
  11.           @param string $argX  argumento declarado (importante)
  12.           @return string  (importante)
  13.           @soap
  14.      */
  15.     public function getObtenerMensajeRemoto($argX)
  16.     {
  17.         return "HOLA REMOTO, TU MENSAJE ES: ".$argX;
  18.     }
  19. }


Plano UML de la arquitectura interna la implementación de Yii-Webservices.

La linea azul desde "start" es la que indica el ciclo de vida de un request hasta: "finish". Las lineas rojas con punta blanca son "extends" (herencia), las otras lineas rojas son "se relaciona con, hace algo en..".

http://www.yiiframeworkenespanol.org/doc/webservices-con-yii-plano-UML-christiansalazar.gif

Referencias:

http://www.php.net/manual/en/book.soap.php
http://www.php.net/manual/en/refs.webservice.php
http://www.yiiframework.com/doc/guide/1.1/en/topics.webservice