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

7 comentarios:

  1. tengo un problema en yii como visualizar un pdf solo verlo ya lo cree solo quiero verlo

    ResponderEliminar
  2. Saludos Christian, gracias por el tutorial.
    Te comento que estoy empezando con Yii (vengo de .net c#) y necesito crear una serie de web services. Todo funciona bien si el return es un string, pero lo que realmente necesito retorna es un modelo / array, aquí Yii solo me retorna un string con la palabra Array(0), esto me tienen loco ya que debería de ser muy fácil de implementar.

    Seguí esta guía pero nada http://www.yiiframework.com/doc/guide/1.1/it/topics.webservice

    Se que el modelo funciona ya que corre bien en los views...

    Gracias por tus comentarios.

    ResponderEliminar
  3. Listo, mi problema era que estaba tratando de retornar
    $model = Modelo::model()->findByPk($id);

    en lugar de los atributos del ActiveRecord,
    return $model->attributes;

    por si alguien comete la misma novatada aquí lo dejo.
    Saludos.

    ResponderEliminar
    Respuestas
    1. Como dice Christian en el artículo, además debes devolver mediante json_encode($model->attributes) ;)

      Eliminar
  4. Christian Muchas gracias por tu aporte, he intentado implementar el ejemplo pero no entiendo bien como funciona

    Y me devuelve varios errores al intentar implementarlo esto fue lo que hice:

    Cree un controlador con el nombre ws.

    El controlador ws en actions agrege esta linea 'ws'=>array('class'=>'CWebServiceAction',),

    Configure en el config la única url que me mostraba correctamente el xml
    somospropiedad.com/ws/ws


    Aunque no logro ver el index del ws donde me muestra el nombre y los métodos y detalles

    luego cree la archivo en componets tal cual esta en el ejemplo pero me dice:

    MiCliente y sus behaviors no tienen un método o closure llamado "getClientInt".

    le cambio el esta linea

    return $this->getClientInt()->getObtenerMensajeRemoto($argX);

    por esta:

    return $this->getClienteInt()->getObtenerMensajeRemoto($argX);

    y me devuelve este otro error.

    Function ("getObtenerMensajeRemoto") is not a valid method for this service

    Les agradecería mucho si por favor alguien me puede guiar en la creación de este WS




    ResponderEliminar
  5. Buen tutorial y uno de los muy pocos existentes sobre el la creación y consumo de webservice. Gracias Christian por tu ayuda.
    En mi caso soy nuevo en lo que es php y más aún en el uso de Yii Framework, y creo que es importante hacer el siguiente aporte aunque sea mínimo, para lograr hacer funcionar correctamente este tutorial, y de paso ayudar a los que se inician en Yii como es mi caso y de muchos otros mas.

    Como menciono cosdela en el post anterior, hay que cambiar "getClientInt" por "getClienteInt"

    Y en SiteController.php cambiar lo siguiente:

    /***
    @param string $argX argumento declarado (importante)
    @return string (importante)
    @soap
    */

    por

    /**
    * @param string $argX argumento declarado (importante)
    * @return string (importante)
    * @soap
    */

    En mi caso al realizar lo anterior me funciono correctamente, ademas de tener que habilitar la extensión de openSll y soap en php.ini
    pd: en mi caso utilizo wamp server con php 5.4.3.

    Saludos.
    P.A.S.V.

    ResponderEliminar
  6. Saludos, excelente tutorial, sin embargo no puedo avanzar porque no se como hacer la configuracion de yii para los web service

    ResponderEliminar