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