26 sept 2009

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. 

10 comentarios:

Anónimo dijo...

Hola buen día
encontre de casualidad tu blog, quería agradecerte por el ejemplo, lo voy a probar con calma, despues te cuento como me fue
saludos desde el sur

Orlando dijo...

Hola!

De nada, es un gusto saber que te puede ser de utilidad. Espero tus comentarios para ver como te fue con el ejemplo y que otro tipo de ejemplos te gustaría encontrar en el blog.

Saludos.

Unknown dijo...

Llevo trabajando con servicios Web desde hace tres años aproximadamente, trabajando en .Net contra servicios Axis. Hace poco me pasaron un codigo en PHP para comprobar por que no funcionaba, usamos certificados para validar la conexion. El ejemplo que me pasaron de codigo me abrio los ojos a un mundo nuevo, paginas web ya que yo trabajo con aplicacion de escritorio. Pero es que tus tres articulos me han abierto los ojos a un nuevo canal a experimentar. Ahora he de montar un Server que reciba un string y lo reenvie a otro servicio, AXIS, usando certificados (PEM) y la respuesta redireccionarla hacia el cliente. Este ultimo articulo me ha ha aclarado como hacerlo. Pero tengo una pregunta para un servicio de un tipo que no encuentro informacion de como montarlo en PHP, se trata de utilizar el protocolo MTOM, podrias darme alguna pista?

Muchas gracias.

tecnico@lkxsoftware.es

Orlando dijo...

Hola Diego!

Te comento que no tengo experiencia en MTOM, por lo que no te voy a poder dar una respuesta de inmediato. Pero si me voy a poder a investigar para ver si es factible de implementarlo con PHP y NuSOAP.

Te voy a estar enviando un e-mail, para que me des algún ejemplo, si es que tienes, de SOAP ya comprimido. De esta forma puedo trabajar sobre un caso real, y poder hacer algunas pruebas.

Saludos.

Edgar Venom dijo...

que tal orlando, tu publicacion me a ayudao en un proyecto que tengo pero aun no comprendo bien lo de definir y consumir el web services actualmente esto en un caso de envio d datos a un web services atraves o utilizando PHP pero no he podido terminarlo ojala y me pudieras ayudar

Edgar Venom dijo...

que tal orlando, tu publicacion me a ayudao en un proyecto que tengo pero aun no comprendo bien lo de definir y consumir el web services actualmente esto en un caso de envio d datos a un web services pero no he podido terminarlo ojala y me pudieras ayudar

Humberto Alvarez dijo...

Orlando! muchas gracias, por el tutorial, es excelente. Tengo una duda, producto del desconocimiento total, en estos temas. Como puedo acceder, a cada elemento individual, de la respuesta; para formatear la salida a la GUI?

Me explico, la respuesta tras ejecutar el ws, es: Respuesta: Array ( [id] => 1 [nombre] => Blas [apellido] => Pascal [cuit] => 11-11111111-1 )

Pero solo deseo que se visualice, el Nombre y el Cuit; como puedo hacer esto?

Saludos y muchas gracias por tu desinteresada, ayuda.

PD: Estoy, esperando con ansia, el ejemplo 4.

Humberto Alvarez dijo...

Ampliando la pregunta anterior. No deseo modificar la estructura de la respuesta. Solo deseo saber como puedo obtener, un dato especifico, de la respuesta resultante de ejecutar el WS.

Orlando dijo...

Estimado Humberto,

En el proximo tutorial va a contener como se puede realizar la presentacion de la informacio que retorna un Web Sdervice. Ya tengo gran parte del tutorial armada, en cuanto tenga un poquito de tiempo lo voy a estar subiendo para que puedas ver un poco de como mostrar los datos.

Saludos.

Uva dijo...

Me estan sirviendo de mucho estos post!!! Muchisimas gracias!!!