/ desarrollo

WebServices con NuSOAP en PHP - Ejemplo 2

En este segundo ejemplo de WebServices con PHP utilizando NuSOAP como biblioteca, voy a mostrar un ejemplo un poco más complejo que el del post anterior (WebServices con NuSOAP en PHP - Ejemplo 1). En este nuevo ejemplo voy a añadir un objeto "Cliente" en el servidor, el cual va a ser devuelto al consumidor/cliente del webservice (previa conversión del objeto a un vector). Para que quede más en claro, comenzare por los ejemplos del servidor y el cliente.

El servidor

Al ser un ejemplo un poco más complejo decidí agregar algunos comentarios más para que el código sea un poco más legible. Igualmente luego del código comentaré algunos fragmentos de código más en detalle.
 

<?php
// Importar la biblioteca NuSOAP
require_once('nusoap/lib/nusoap.php');
$miURL = 'http://pruebas.orlandobrea.com.ar/nusoap';
$server = new soap_server();
$server->configureWSDL('ws_orlando', $miURL);
$server->wsdl->schemaTargetNamespace=$miURL;

/*

  • Ejemplo 2: getCliente es una funcion mas compleja que recibe un id y retorna el cliente
  • que le corresponde (solo retorna 2 clientes). Tener en cuenta que las respuestas del
  • WebService son un Array (vector) y no un objeto propiamente dicho. Si deseamos recuperar
  • el objeto del lado del cliente, lo debemos convertir de vector a objeto. En el ejemplo 3
  • se ampliara sobre este tipo de operaciones.
    */

// El objeto que voy a enviar al cliente como respuesta
class Cliente {
var $id;
var $nombre;
var $apellido;
var $cuit;
}
// Un objeto de acceso a datos (DAO - Data Access Object) que es el encargado de
// administrar los objetos almacenados (en este caso todos los valores estan
// prefijados, pero en un ejemplo real se podrian obtener de una base de datos)
class ClienteDAO {
/**

  • getCliente($id) : Cliente
  • Esta funcion deberia implementarse con una conexion a una base de datos
  • y obtener la informacion de la base directamente
    */
    function getCliente($id) {
    $obj = new Cliente();
    if ($id == 1) {
    $obj->id = 1;
    $obj->nombre = 'Blas';
    $obj->apellido = 'Pascal';
    $obj->cuit = '11-11111111-1';
    }
    if ($id==2) {
    $obj->id = 1;
    $obj->nombre = 'Isaac';
    $obj->apellido = 'Newton';
    $obj->cuit = '22-22222222-2';
    }
    return $obj;
    }
    }
    // Creo la estructura en la cual voya convertir al objeto Cliente, para enviarlo al
    // consumidor del WebService (En este caso contiene un vector con id, nombre, apellido
    // y cuit)
    $server->wsdl->addComplexType('Cliente',
    'complexType',
    'struct',
    'all',
    '',
    array(
    'id' => array('name' => 'id', 'type' => 'xsd:int'),
    'nombre' => array('name' => 'nombre', 'type' => 'xsd:string'),
    'apellido' => array('name' => 'apellido', 'type' => 'xsd:string'),
    'cuit' => array('name' => 'CUIT', 'type' => 'xsd:string')
    )
    );
    // Registro la funcion que se expone en el webservice, la que puede consumir el cliente,
    // e indico a que funcion PHP se debe llamar cuando el cliente invoque el webservice
    // (getCliente)
    $server->register('getCliente', // Nombre de la funcion
    array('id' => 'xsd:int'), // Parametros de entrada
    array('return' => 'tns:Cliente'), // Parametros de salida
    $miURL
    );
    // Funcion que es llamada por el WebService de NuSOAP cuando el cliente intenta consumir
    // el servicio web expuesto
    function getCliente($id) {
    $dao = new ClienteDAO();
    $cliente = $dao->getCliente($id);
    // Convierto el objeto en un vector, con una key por cada propiedad del objeto
    $respuesta = array('id'=> $cliente-id,
    'nombre' => $cliente->nombre,
    'apellido' => $cliente->apellido,
    'cuit' => $cliente->cuit
    );
    // Indico que el parametro de respuesta es del tipo tns:Cliente, y cual es el vector
    // que contiene dicha estructura
    return new soapval('return', 'tns:Cliente', $respuesta);
    }

$server->service($HTTP_RAW_POST_DATA);
?>

En el códido de este nuevo ejemplo, existen algunas diferencias con respecto al del ejemplo1, por eso voy a intentar detallar cada una de ellas con un poco de detalle, para poder explicar de que se trata cada una de las nuevas incorporaciones.

Creamos un objeto Cliente

En este nuevo módulo servidor utilizamos una clase llamada "Cliente" que es la que luego de ser convertida, será devuelta al cliente/consumidor del WebService. Decidí crear una clase, para hacer el ejemplo un poco más acorde a lo que nos encontraremos en la vida cotidiana, es decir, hoy en día estamos muy acostumbrados a trabajar con objetos, y que toda la información que tengamos disponibles en nuestros sistemas se encuentren representadas por objetos. Es por eso, que comence  incorporar los objetos al ejemplo. La definición de la clase es la siguiente:

class Cliente {
var $id;
var $nombre;
var $apellido;
var $cuit;
}

Es un objeto muy simple, el cual no tiene asociaciones con otros objetos, simplemente tiene 4 atributos:

  • id
  • nombre
  • apellido
  • cuit

Si bien el objeto es muy simple, nos servirá para poder comenzar a ver cuales son las implicancias del mismo y como debemos convertirlo para luego retornarlo el cliente del webservice.

Creamos un objeto ClienteDAO

Siguiendo con la esta nueva meta de acercar el ejemplo a la vida real, incorporé un objeto DAO (Data Access Object). Para aquellos que no conocen o no están acostumbrados a usar un DAO, voy a intentar definirlo con unas pocas palabras. Cuando creamos aplicaciones multicapa, algo muy común hoy en día, es normal que una de las capas se llame DAO, que podríamos traducir como Capa de Acceso a Datos. Esta capa de acceso a datos, es la encargada de manejar los datos de la aplicación, por ejemplo la que se encarga de leer, guardar y borrar datos de un origen de datos, como podría ser una base de datos, un archivo XML, u otro webservice. Esta capa nos permite abstraer toda la lógica de acceso a datos, para que, si el día de mañana decidimos cambiar de motor de base de datos, o de origen de datos, no tengamos que modificar todas las demás partes del código, solamente cambiamos las clases de esta capa. Generalmente se utiliza un objeto DAO por cada objeto del dominio que queremos guardar/modificar/borrar (existen formas de agrupar en un único objeto las operaciones sobre varios objetos del dominio, pero es dependiente del lenguaje, y prefiero no ahondar en ello para no complicar el ejemplo).

Ya explicado, muy brevemente, que es un DAO vamos a la definición de la clase ClienteDAO:

class ClienteDAO {
 /**
  * getCliente($id) : Cliente
  * Esta funcion deberia implementarse con una conexion a una base de datos
  * y obtener la informacion de la base directamente
  */
      public function getCliente($id) {
         $obj = new Cliente();
            if ($id==1) {
                 $obj->id = 1;
                 $obj->nombre = 'Blas';
                 $obj->apellido = 'Pascal';
                 $obj->cuit = '11-11111111-1';
            }
            if ($id==2) {
                $obj->id = 1;
                $obj->nombre = 'Isaac';
                $obj->apellido = 'Newton';
                $obj->cuit = '22-22222222-2';
            } 
            return $obj;
       }
}

Simplemente definimos un método, getCliente, para el objeto que crearemos. Para mantener el ejemplo sencillo, la información retornada por el mismo esta pre-fijada, es decir, no es tomada desde una base de datos, u otra fuente, como si sería en una aplicación del mundo real. Si este método lo implementaramos en una aplicación real, ejecutariamos una sentencia SQL similar a "select * from Cliente where id=".$id y luego convertiriamos la respuesta en un objeto Cliente que es el que retornamos.

Dado que es un ejemplo sin conexión con un origen de datos, esta simple función puede retornar solo 2 objetos clientes, uno para cuando se le pasa como parámetro 1, y otro para cuando se pasa como parámetro 2. En caso de pasar cualquier otro valor como parámetro la función retornará un objeto Cliente sin inicializar sus atributos.

Definimos el tipo de datos devuelto por el WebService

A diferencia del ejemplo 1, en el cual nuestro WebService solo retornaba un String (xsd:string), en esta oportunidad nuestro WebService, retornará un objeto (representado en un vector). Pero como estamos retornando un valor complejo, es decir, no es un string, o int, u otro tipo que ya este predefinido, debemos definirlo nosotros, para ello usamos el siguiente ceodigo:


En el código, estamos informandole a nuestro objeto $server, que utilizaremos un tipo de datos propio, o tipo complejo (este tipo de datos complejo se reflejará en nuestro WSDL del WebService). Los parámetros que más nos interesan para la función "addComplexType" son el primero, y el último. 

El primer parámetro define cual es el nombre que le asignamos a este tipo de datos complejo, en este caso "Cliente".

El último parámetro define como esta compuesto este tipo de datos complejo. Como podemos ver en el ejemplo, el tipo de datos se define como un array (vector) el cual esta compuesto por varios elementos (id, nombre, apellido, cuit), y cada uno de dichos elementos debe tener un vector que contenga "name" y "type" es decir la definición para cada tipo de datos que compone este tipo complejo. Lo que hacemos, no es más que definir un tipo de datos complejo, compuesto por diferentes tipos de datos simples (con simple, me refiero a aquellos que son los estandares) y/o otros tipos de datos complejos que ya hayan sido definidos. Es similar a la definición de una estructura de datos, u objeto. Debemos tener en cuenta que los tipos de datos simples tienen el prefijo xsd (recordarlo para incorporarlo a cada tipo que estamos definiendo).

Registración de la función a exponer

Al igual que en el ejemplo 1, debemos exponer cuales funciones deseamos publicar en nuestro WebService, para ello llamamos al método "register" de nuestro objeto $server, como podemos ver en el siguiente código:

$server->register('getCliente', // Nombre de la funcion
array('id' => 'xsd:int'), // Parametros de entrada
array('return' => 'tns:Cliente'), // Parametros de salida
$miURL);

La registración de la función expuesta, es muy similar al ejemplo 1, la única diferencia es que en vez de retornar un tipo de datos xsd:string, retornamos un tipo de datos complejo tns:Cliente. Este tipo de datos, es el que definimos en el punto anterior. Con los últimos 2 puntos le estamos indicando a NuSOAP que vamos a retornar un tipo de datos complejo, el cual tiene la estructura definida en tns:Cliente (estructura definida en el punto anterior). 

Función que retorna el cliente

Al igual que en el ejemplo anterior tenemos una función que es la encargada de procesar el pedido y retornar la respuesta. En este caso dicha función se llama getCliente y es la siguiente:

function getCliente($id) {
$dao = new ClienteDAO();
$cliente = $dao->getCliente($id);
// Convierto el objeto en un vector, con una key por cada propiedad del objeto
$respuesta = array(
'id'=> $cliente->id,
'nombre' => $cliente->nombre,
'apellido' => $cliente->apellido,
'cuit' => $cliente->cuit
);
// Indico que el parametro de respuesta es del tipo tns:Cliente, y cual es el vector
// que contiene dicha estructura
return new soapval('return', 'tns:Cliente', $respuesta);
}

Al estar utilizando objetos para obtener los datos que deseamos retornar, tenemos algunos pasos más en al función que en el ejemplo anterior. 

  • Definimos en objeto $dato que es del tipo ClienteDAO (la capa de acceso a datos).
  • Obtenemos el cliente con el $id pasado como parámetro, a través del objeto DAO que creamos en el primer paso.
  • Convertimos el objeto cliente en un vector para luego poder retornarlo (usamos como key, el nombre de cada uno de los atributos del objeto).
  • Retornamos el vector, informando que el mismo es del tipo tns:Cliente (es decir, le pedimos a NuSOAP que nos cree un tns:Cliente a partir del vector pasado como parámetro.

¿Como creo el mapeo entre el objeto y el vector?

Para alcanzar este objetivo, cada uno puede tener sus preferencias, yo utilizó una correspondencia 1 a 1, entre el nombre del atributo y la key del vector. Ejemplo:

class Cliente { $vector = array(
var $id; --------> 'id' => '',
var $nombre; --------> 'nombre' => '',
var $cuit; --------> 'cuit' => ''
} );

Como vemos en el ejemplo, la propiedad id del objeto Cliente, se corresponde con la key 'id' del vector $vector. Cuando el objeto es copiado al vector, al acceder a $cliente->id; PHP no da el mismo valor que $vector['id'];

Cliente

El cliente no tiene mayores modificaciones que el del ejemplo 1, simplemente modifica el nombre del script del servidor, y la función a la cual se llama en el mismo.

<?php
require_once('nusoap/lib/nusoap.php');
// Crear un cliente apuntando al script del servidor (Creado con WSDL)
$serverURL = 'http://pruebas.orlandobrea.com.ar';
$serverScript = 'nusoap_server_ej2.php';
$metodoALlamar = 'getCliente';
$cliente = new nusoap_client("$serverURL/$serverScript?wsdl", 'wsdl');
// Se pudo conectar?
$error = $cliente->getError();
if ($error) {
  echo '<pre style="color: red">' . $error . '</pre>';
  echo '<p style="color:red;'>htmlspecialchars($cliente->getDebug(), ENT_QUOTES).'</p>';
  die();
}
// 2. Llamar a la funcion getCliente del servidor
$result = $cliente->call(
"$metodoALlamar", // Funcion a llamar
 array('id' => '1'), // Parametros pasados a la funcion
  "uri:$serverURL/$serverScript", // namespace
   "uri:$serverURL/$serverScript/$metodoALlamar" // SOAPAction
 );
// Verificacion que los parametros estan ok, y si lo estan. mostrar rta.
if ($cliente->fault) {
    echo '<b>Error: ';
    print_r($result);
    echo '</b>';
} else {
    $error = $cliente->getError();
    if ($error) {
        echo '<b style="color: red">Error: ' . $error . '</b>';
    } else {
        echo 'Respuesta: ';
        print_r($result);
    }
}
?>

El cliente es muy sencillo, ya que simplemente muestra el contenido de la respuesta del servidor. En un ejemplo del mundo real, el vector que nos devuelve el servidor, lo deberíamos convertir a un objeto cliente (haciendo el mapeo inverso. Antes pasamos del objeto a un vector, y ahora deberíamos pasar del vector a un objeto). En el ejemplo 4, voy a hacer el cliente un poco más complejo, convirtiendo del vector a un objeto, y luego operando con el objeto.

Conclusión

En este ejemplo el servidor fué el que recibió modificaciones, para poder comenzar a operar con objetos, pero el cliente lo mantuve igual de simple que en el ejemplo anterior. También podés descargar el archivo comprimido con el ejemplo. Como vemos a medida que vamos aproximando nuestro ejemplo a la vida real, el mismo se hace un poco más complejo y debemos utilizar diferentes conocimientos que a veces escapan al del WebService en si.