# Servicios Web Un **servicio web** es un método que permite que **dos equipos intercambien información a través de una red informática**. Al utilizar servicios web, el **servidor** puede **ofrecer un punto de acceso** a la información que quiere compartir. De esta forma controla y facilita el acceso a la misma por parte de otras aplicaciones. **Los clientes** del servicio, por su parte, **no necesitan conocer la estructura interna de almacenamiento**. En lugar de tener que programar un mecanismo para localizar la información, tienen un punto de acceso directo a la que les interesa. Los servicios Web permiten que nuestro sistema sea más escalable, ya que podemos implementar diferentes clientes usando la misma lógica, por ejemplo implementando una app móvil y una web que usen ese mismo servicio. ## REST Servicios Web RESTful que se basan en la arquitectura REST (REpresentational State Transfer) donde el cliente invoca la ejecución de un determinado servicio mediante el uso de su identificador único (URI). Usa las operaciones propias del protocolo HTTP como son: POST, GET, PUT y DELETE. Cada operación de la API expone una URL o endPoint único. Está arquitectura devuelve la respuesta generalmente en JSON, lo que permite que cualquier lenguaje de programación lo utilice de forma sencilla, aunque tambíen pueden enviar HTML o XML. ### Características Existen al menos dos cuestiones que debería resolver un servicio web para poder funcionar correctamente: - **Cómo se transmite la información**. Si se va a usar HTTP para las peticiones y las respuestas, el cliente y el servidor tendrán que ponerse de acuerdo en la forma de enviar unas y otras. - **Cómo se publican las funciones a las que se puede acceder en un servidor determinado**. Este punto es opcional, pero muy útil. Es decir, el cliente puede saber que la función del servidor que tiene que utilizar se llama "getPVPArticulo()", y que debe recibir como parámetro el código del artículo. Pero si no lo sabe, sería útil que hubiera un mecanismo donde pudiera consultar las funciones que existen en el servidor y cómo se utiliza cada una. Cada uno de los métodos que podemos utilizar hoy en día para crear un servicio web responde a estas preguntas de formas distintas. **Para la primera cuestión**, nosotros veremos el protocolo **SOAP**, que utiliza el lenguaje XML para intercambiar información. En cuanto a la **segunda cuestión**, la resolveremos con un lenguaje llamado **WSDL**, que también está basado en XML y fue creado para describir servicios web, es decir, indicar cómo se debe acceder a un servicio y utilizarlo. ## SOAP (Intercambio de Información) Es un protocolo que indica cómo deben ser los mensajes que se intercambien el servidor y el cliente, cómo deben procesarse éstos, y cómo se relacionan con el protocolo que se utiliza para transportarlos de un extremo a otro de la comunicación (en el caso de los servicios web, este protocolo será HTTP). Aunque nosotros vamos a utilizar HTTP para transmitir la información, **SOAP no requiere el uso de un protocolo concreto para transmitir la información**. SOAP se limita a definir las reglas que rigen los mensajes que se deben intercambiar el cliente y el servidor. El nombre SOAP surgió como **acrónimo de "Simple Object Access Protocol"**, pero, **a partir de la versión 1.2** del protocolo, el nombre SOAP **ya no se refiere a nada en concreto**. Al igual que su antecesor, **XML-RPC**, SOAP **utiliza XML**. En un mensaje SOAP, como mínimo debe figurar un elemento "Envelope", que es lo que identifica al documento XML como un mensaje SOAP, y donde se deben declarar al menos los siguientes espacios de nombres: ```xml= <soap:Envelope soap="http://schemas.xmlsoap.org/soap/envelope/" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> ``` El espacio de nombres que se utilice para el elemento Envelope indica la versión del protocolo SOAP utilizado. En la versión 1.1 (la del ejemplo anterior), el espacio de nombres es: http://schemas.xmlsoap.org/soap/envelope/. En la versión 1.2 se debe utilizar: http://www.w3.org/2003/05/soap-envelope. De las **implementaciones de SOAP** que podemos usar **con PHP**, cabe destacar tres: **NuSOAP, PEAR::SOAP y PHP SOAP**. Las tres nos permiten crear tanto un cliente como un servidor SOAP, pero existen algunas características que las diferencias: - **PHP SOAP** es la implementación de SOAP que se incluye con PHP a partir de la versión 5 del lenguaje. En versiones anteriores se tenía que recurrir a otras opciones para trabajar con SOAP. Es una extensión nativa (escrita en lenguaje C) y por tanto más rápida que las otras posibilidades. Su gran inconveniente es que no permite la generación automática del documento WSDL una vez programado el servidor SOAP correspondiente. - **NuSOAP** es un conjunto de clases programadas en PHP que ofrecen muchas funcionalidades para utilizar SOAP. Al contrario que PHP SOAP, funcionan también con PHP4, y además permite generar automáticamente el documento WSDL correspondiente a un servicio web. - **PEAR::SOAP** es un paquete PEAR que permite utilizar SOAP con PHP a partir de su versión 4. Al igual que NuSOAP, también está programado en PHP. ### Descripción del servicio: WSDL WSDL es un lenguaje basado en XML que utiliza unas reglas determinadas para generar el documento de descripción de un servicio web. Una vez generado, ese documento se suele poner a disposición de los posibles usuarios del servicio (normalmente se accede al documento WSDL añadiendo "?wsdl" a la URL del servicio). El espacio de nombres de un documento WSDL es: http://schemas.xmlsoap.org/wsdl/, aunque en un documento WSDL se suelen utilizar también otros espacios de nombres. La estructura de un documento WSDL es la siguiente: ```xml= <definitions name="…" targetNamespace="http://…" tns="http://…" xmlns="http://schemas.xmlsoap.org/wsdl/" … > <types> … </types> <message> … </message> <portType> … </portType> <binding> … </binding> <service> … </service> </definitions> ``` El objetivo de cada una de las secciones del documento es el siguiente: - **types**. Incluye las definiciones de los tipos de datos que se usan en el servicio. - **message**. Define conjuntos de datos, como la lista de parámetros que recibe una función o los valores que devuelve. - **portType**. Cada portType es un grupo de funciones que implementa el servicio web. Cada función se define dentro de su portType como una operación (operation). - **binding**. Define cómo va a transmitirse la información de cada portType. - **service**. Contiene una lista de elementos de tipo port. Cada port indica dónde (en qué URL) se puede acceder al servicio web. Existen servicios web sencillos a los que puedes pasar como parámetro un número o una cadena de texto, y te devuelven también un dato de un tipo simple, como un número decimal. Igualmente existen también servicios web más elaborados, que pueden requerir o devolver un array de elementos, o incluso objetos. Para crear y utilizar estos servicios, deberás definir los tipos de elementos que se transmiten: de qué tipo son los valores del array, o qué miembros poseen los objetos que maneja. La definición de tipos en WSDL se realiza utilizando la etiqueta types. Veamos un ejemplo: ```xml= <types> <xsd:schema targetNamespace="http://localhost/dwes/ut6"> <xsd:complexType name="direccion"> <xsd:all> <xsd:element name="ciudad" type="xsd:string" /> <xsd:element name="calle" type="xsd:string" /> <xsd:element name="numero" type="xsd:string" /> <xsd:element name="piso" type="xsd:string" /> <xsd:element name="CP" type="xsd:string" /> </xsd:all> </xsd:complexType> <xsd:complexType name="usuario"> <xsd:all> <xsd:element name="id" type="xsd:int" /> <xsd:element name="nombre" type="xsd:string" /> <xsd:element name="direccion" type="tns:direccion" /> <xsd:element name="email" type="xsd:string" /> </xsd:all> </xsd:complexType> </xsd:schema> </types> ``` En el código anterior, se definen dos tipos de datos usando XML Schema: direccion y usuario. De hecho, los tipos dirección y usuario son la forma en que se definen en WSDL las clases para transmitir la información de sus objetos. En WSDL, las clases se definen utilizando los tipos complejos de XML Schema. Al utilizar all dentro del tipo complejo, estamos indicando que la clase contiene esos miembros, aunque no necesariamente en el orden que se indica. El otro tipo de datos que necesitaremos definir en los documentos WSDL son los arrays. Para definir un array, no existe en el XML Schema un tipo base adecuado que podamos usar. En su lugar, se utiliza el tipo Array definido en el esquema enconding de SOAP. ```xml= <xsd:complexType name="ArrayOfusuario"> <xsd:complexContent> <xsd:restriction base="soapenc:Array"> <xsd:attribute ref="soapenc:arrayType" arrayType="tns:usuario[]" /> </xsd:restriction> </xsd:complexContent> </xsd:complexType> ``` Al definir un array en WSDL, se debe tener en cuenta que: - El atributo arrayType se utiliza para indicar qué elementos contendrá el array. - Se debe añadir al documento el espacio de nombres SOAP encoding: ```xml= <definitions name="WSDLusuario" targetNamespace="http://localhost/dwes/ut6" xmlns:tns="http://localhost/dwes/ut6" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" . . . > ``` - El nombre del array debería ser ArrayOfXXX, dónde XXX es el nombre del tipo de elementos que contiene el array. En muchas ocasiones no será necesario definir tipos propios, y por tanto en el documento WSDL no habrá sección types; será suficiente con utilizar alguno de los tipos propios de XML Schema, como xsd:string, xsd:float o xsd:boolean. Siguiendo con los usuarios que acabamos de definir, podríamos crear en el servicio web una función getUsuario() para dar acceso a los datos de un usuario. Como parámetro de entrada de esa función vamos a pedir el "id" del usuario, y como valor de salida se obtendrá un objeto "usuario". Por tanto, debemos definir los siguientes mensajes: ```xml= <message name="getUsuarioRequest"> <part name="id" type="xsd:int"/> </message> <message name="getUsuarioResponse"> <part name="getUsuarioReturn" type="tns:usuario"/> </message> ``` Como ves, normalmente por cada función del servicio web se crea un mensaje para los parámetros de entrada, y otro para los de salida. Dentro de cada mensaje, se incluirán tantos elementos part como sea necesario. Cada mensaje contendrá un atributo name que debe ser único para todos los elementos de este tipo. Además, es aconsejable que el nombre del mensaje con los parámetros de entrada acabe en Request, y el correspondiente a los parámetros de salida en Response. En un documento WSDL podemos especificar dos estilos de enlazado: document o RPC. La selección que hagamos influirá en cómo se transmitan los mensajes dentro de las peticiones y respuestas SOAP. ```xml= <SOAP-ENV:Body> <producto> <codigo>KSTMSDHC8GB</codigo> </producto> </SOAP-ENV:Body> ``` Y un mensaje con estilo RPC sería por ejemplo: ```xml= <SOAP-ENV:Body> <ns1:getPVP> <param0 type="xsd:string">KSTMSDHC8GB</param0> </ns1:getPVP> </SOAP-ENV:Body> ``` El estilo de enlazado RPC está más orientado a sistemas de petición y respuesta que el document (más orientado a la transmisión de documentos en formato XML). En este estilo de enlazado, cada elemento message de WSDL debe contener un elemento part por cada parámetro, y dentro de éste indicar el tipo de datos del parámetro mediante un atributo type. Además, cada estilo de enlazado puede ser de tipo encoded o literal. Al indicar encoded, estamos diciendo que vamos a usar un conjunto de reglas de codificación, como las que se incluyen en el propio protocolo SOAP, para convertir en XML los parámetros de las peticiones y respuestas. El ejemplo anterior de RPC es en realidad RPC/encoded. Un ejemplo de un mensaje SOAP con estilo RPC/literal sería: ```xml= <SOAP-ENV:Body> <ns1:getPVP> <param0>KSTMSDHC8GB</param0> </ns1:getPVP> </SOAP-ENV:Body> ``` --- **Las funciones** que creas en un servicio web, se conocen con el nombre de operaciones en un documento WSDL. En lugar de definirlas una a una, es necesario **agruparlas** en lo que en WSDL se llama **portType**. ```xml= <portType name="usuarioPortType"> <operation name="getUsuario"> <input message="tns:getUsuarioRequest"/> <output message="tns:getUsuarioResponse"/> </operation> </portType> ``` A no ser que estés generando un servicio web bastante complejo, el documento WSDL contendrá un único portType. **Cada portType debe contener un atributo name** con el nombre (único para todos los elementos portType). **Cada elemento operation** también debe **contener un atributo name**, que se corresponderá con el nombre de la función que se ofrece. Además, en función del tipo de operación de que se trate, contendrá: - Un elemento **input** para indicar **funciones que no devuelven valor** (su objetivo es sólo enviar un mensaje al servidor). - **Un elemento input y otro output**, en este orden, para el caso más habitual: funciones que reciben algún parámetro, se ejecutan, y **devuelven un resultado**. Es posible (pero muy extraño) encontrase funciones a la inversa: sólo con un parámetro output o con los parámetros output e input por ese orden. Por tanto, al definir una función (un elemento operation) se debe tener cuidado con el orden de los elementos input y output. Normalmente, los elementos **input y output contendrán un atributo message** para hacer referenciaa un mensaje definido anteriormente. En el elemento **binding** es dónde debes **indicar que el estilo de enlazado** de tu documento sea RPC/encoded. Aunque es posible crear documentos WSDL con varios **elementos binding**, **la mayoría contendrán solo uno** (si no fuera así, sus atributos name deberán ser distintos). ![](https://i.imgur.com/4eI6tsa.png) Para el **portType** anterior, podemos crear un elemento **binding** como el siguiente: ```xml= <binding name="usuarioBinding" type="tns:usuarioPortType"> <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http" /> <operation name="getUsuario"> <soap:operation soapAction="http://localhost/dwes/ut6/getUsuario.php?getUsuario" /> <input> <soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://localhost/dwes/ut6" /> </input> <output> <soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://localhost/dwes/ut6" /> </output> </operation> </binding> ``` Fíjate que el atributo type hace referencia al portType creado anteriormente. El siguiente elemento indica el tipo de codificación (RPC) y, mediante la URL correspondiente, el protocolo de transporte a utilizar (HTTP). Obviamente, deberás añadir el correspondiente espacio de nombres al elemento raíz: ```xml= <definitions name="WSDLusuario" targetNamespace="http://localhost/dwes/ut6" xmlns:tns="http://localhost/dwes/ut6" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" . . . > ``` El elemento **soap:operation** debe contener un **atributo soapAction** con la URL para esa función (operation) en particular. Dentro de él habrá normalmente un elemento input y otro output (los mismos que en la operation correspondiente). En ellos, mediante los atributos del elemento soap:body, se indica el estilo concreto de enlazado (encoded con su encondingStyle correspondiente). Por último, falta definir el elemento **service**. Normalmente sólo encontraremos un elemento **service** en cada documento WSDL. En él, **se hará referencia al binding anterior** utilizando un elemento port, y se **indicará la URL** en la que se puede acceder al servicio. Ejemplo: ```xml= <service name="usuario"> <port name="usuarioPort" binding="tns:usuarioBinding"> <soap:addresslocation="http://localhost/dwes/ut6/getUsuario.php"/> </port> </service> ``` Para finalizar, veamos cómo quedaría el documento WSDL correspondiente a un servicio con una única función encargada de devolver el PVP de un producto de la tienda web a partir de su código. ```xml= <?xml version="1.0" encoding="UTF-8" ?> <definitions name="WSDLgetPVP" targetNamespace="http://localhost/dwes/ut6" xmlns:tns="http://localhost/dwes/ut6" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xmlns="http://schemas.xmlsoap.org/wsdl/"> <message name="getPVPRequest"> <part name="codigo" type="xsd:string" /> </message> <message name="getPVPResponse"> <part name="PVP" element="xsd:float" /> </message> <portType name="getPVPPortType"> <operation name="getPVP"> <input message="tns:getPVPRequest" /> <output message="tns:getPVPResponse" /> </operation> </portType> <binding name="getPVPBinding" type="tns:getPVPPortType"> <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http" /> <operation name="getPVP"> <soap:operation soapAction="http://localhost/dwes/ut6/getPVP.php?getPVP"/> <input> <soap:body namespace="http://localhost/dwes/ut6" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" / </input> <output> <soap:body namespace="http://localhost/dwes/ut6" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" / </output> </operation> </binding> <service name="getPVPService"> <port name="getPVPPort" binding="tns:getPVPBinding "> <soap:address location="http://localhost/dwes/ut6/getPVP.php" /> </port> </service> </definitions> ``` ## PHP SOAP Para utilizar SOAP en PHP vamos a aprender a utilizar la extensión que viene incluida con el lenguaje a partir de su versión 5: **"PHP SOAP"**. En el momento de escribir este texto, es compatible con las versiones "SOAP 1.1" y "SOAP 1.2", así como con "WSDL 1.1", aunque **no permite la generación automática del documento WSDL** a partir del servicio web programado. Para poder usar la extensión, deberás comprobar si ya se encuentra disponible (por ejemplo, consultando la salida obtenida por la función phpinfo()): **Normalmente la extensión SOAP formará parte de PHP** (se habrá compilado con el ejecutable) y podrás utilizarla directamente. Si no fuera así, deberás instalarla y habilitarla en el fichero "php.ini". Las **dos clases principales** que deberás utilizar en tus aplicaciones son: - **"SoapClient"**: Te permitire comunicarte con un servicio web - **"SoapServer"**: Podrás crear tus propios servicios. El estilo de enlazado es **"document/literal"** (recuerda que nosotros vimos el "RPC/encoded" solamente), por lo que **los elementos de tipo "message" tienen un formato distinto**. Sin embargo, en base a su contenido (fíjate en los elementos que terminan en "Soap") se puede deducir también que: - El nombre de la función a la que debes llamar es "wstitulosuni". ```xml= <wsdl:operation name="wstitulosuni"> ``` - Como parámetro de entrada le tienes que pasar un elemento de tipo "wstitulosuni" (dos string), y devolverá un elemento "wstitulosuniResponse" (un array). ```xml= <wsld:message name="wstitulosuniSoapIn"> <wsld:part name="parameters" element="tns:wstitulosuni"/> </wsld:message> <wsld:message name="wstitulosuniSoapOut"> <wsld:part name="parameters" element="tns:wstitulosuniResponse"/> </wsld:message> . . . <wsld:portType name="pub_gestdocenteSoap"> . . . <wsld:operation name="wstitulosuni"> <wsld:input message="tns:wstitulosuniSoapIn"/> <wsld:output message="tns:wstitulosuniSoapOut"/> </wsld:operation> . . . </wsld:portType> ``` - La URL para acceder al servicio, la puedes ver más abajo, de igual manera puedes observar que el servicio se ofrece para versión 1.1 y 1.2 de SOAP. ```xml= <wsld:service name="pub_gestdocente"> <wsld:port name="pub_gestdocenteSoap" binding="tns:pub_gestdocenteSoap"> <soap:address location="https://cvnet.cpd.ua.es/servicioweb/publicos/pub_gestdo </xsld:port> <xsld:port name="pub_gestdocenteSoap12" binding="tns:pub_gestdocenteSoap12"> <xsld:address location="https://cvnet.cpd.ua.es/servicioweb/publicos/pub_gest </wsld:port> . . . </wsldservice> ``` Con la información anterior, para utilizar el servicio desde PHP creas un nuevo objeto de la clase "SoapClient". Como el servicio tiene un documento WSDL asociado, en el constructor le indicas dónde se encuentra: ```php= $cliente=new SoapClient("https://cvnet.cpd.ua.es/servicioweb/publicos/pub_gestdocente.asmx?ws") ``` Y para realizar la llamada a la función "wstitulosuni", incluyes los parámetros en un array: ```php= $parametros=[ 'plengua'=>'es', 'pcurso'=>'2019' ]; $titulos=$cliente->wstitulosuni($parametros); ``` La llamada devuelve un objeto de una clase predefinida en PHP llamada: StdClass. Si hacemos "var_dump($titulos)" obtenemos lo siguiente: ```php= object(stdClass)[2] public 'wstitulosuniResult' => object(stdClass)[3] public 'ClaseTitulosUni' => array (size=113) ``` Vemos que nos ha devuelto un objeto stdClass dentro podemos observar que nos ha devuelto 113, títulos. Si recorremos $titulos con el código siguiente: ```php= foreach($titulos as $k=>$v){ foreach($v as $k1=>$v1){ echo var_dump($v1); } } ``` El constructor de la clase **"SoapClient"** puede usarse de dos formas: - Indicando un documento WSDL: la extensión SOAP examina la definición del servicio y establece las opciones adecuadas para la comunicación, con lo cual el código necesario para utilizar un servicio es bastante simple. - Sin indicarlo: si no indicas en el constructor un documento WSDL, el primer parámetro debe ser "null", y las opciones para comunicarse con el servicio las tendrás que establecer en un array que se pasa como segundo parámetro. **Wsdl2PhpGenerator**: Facilita el desarrollo con SoapClient si disponemos de WSLD. Su uso es muy sencillo: se ejecuta pasándole como parámetro la URL en que se encuentra el documento WSDL, y como resultado genera un fichero con código PHP. **Lo podemos instalar independientemente o con "composer"**. Se recomienda hacerlo de esta última manera pues podemos indicar entre otras cosas el "namespace" para las clases que se creen. En la carpeta "public" nos crearemos el archivo "generar.php" para generarnos las clases a partir del documento . (Una vez generadas deberíamos borrar este archivo) Si consultas la documetación del proyecto es fácil crear este archivo, fíjate en su contenido: ```php= <?php //utilizamos el autoload de composer require '../vendor/autoload.php'; use Wsdl2PhpGenerator\Generator; use Wsdl2PhpGenerator\Config; $generator = new Generator(); $generator->generate( new Config([ 'inputFile' => "https://cvnet.cpd.ua.es/servicioweb/publicos/pub_gestdocente.asmx?wsd 'outputDir' => '../src', //directorio donde vamos a generar las clases 'namespaceName' => 'Clases' //namespace que vamos a usar con ellas (lo indicamos en ]) ); ``` Podrás observar que ahora en "src" tenemos una serie de clases creadas. Ya estamos en condiciones de traernos los datos. Nos creamos el archivo "titulaciones2019.php" por ejemplo con el contenido siguiente: ```php= <?php require '../vendor/autoload.php'; use Clases\Pub_gestdocente; use Clases\wstitulosuni; $service = new Pub_gestdocente(); $request=new wstitulosuni('es', '2020'); $titulos=$service->wstitulosuni($request); var_dump($titulos); ``` La diferencia es que ahora $titulos es un array de objetos de la clase: "ArrayOfClaseTitulosUni" y no de la clase especial "stdclass". Si estás usando un documento WSDL para acceder al servicio web, la clase SoapClient implementa dos métodos que muestran parte de la información que contiene: - **__getTypes**: Los tipos de datos definidos por el servicio. - **__getFunctions**: las funciones que ofrece Donde las funciones aparecen duplicadas, dado que el servicio web, como ya vimos, ofrece dos versiones de la misma: una para SOAP 1.1 y otra para SOAP 1.2. Para habilitar las funciones de depuración, cuando hagas la llamada al constructor de la clase SoapClient, debes utilizar la opción trace en el array de opciones del segundo parámetro. ```php= $cliente = new SoapClient( "https://cvnet.cpd.ua.es/servicioweb/publicos/pub_gestdocente.asmx?wsdl", array('trace'=>true) ); ``` ## Creación de un sitio web En PHP SOAP, para crear un servicio web, debes utilizar la clase SoapServer. En una carpeta crearemos las carpetas "clienteSoap" y "servidorSoap". Dentro de "servidorSoap" crearemos el archivo "servidor.php" con el contenido siguiente: ```php= <?php class Operaciones { public function resta($a, $b){ return $a - $b; } public function suma($a, $b){ return $a + $b; } public function saludo($texto){ return "Hola $texto"; } } $uri='http://localhost/unidad6/servidorSoap'; $parametros=['uri'=>$uri]; try { $server = new SoapServer(NULL, $parametros); $server->setClass('Operaciones'); $server->handle(); } catch (SoapFault $f) { die("error en server: " . $f->getMessage()); } ``` El código anterior crea un servicio web con tres funciones: suma, resta y saludo. Cada función recibe unos parámetros y devuelve un valor. Para añadir las funciones de la clase "Operaciones" a nuestro "Servidor Soap" fíjate que hemos añadido "$server->setClass('Operaciones)" . Si hubiésemos implementados las funciones directamente, sin usar una clase, tendríamos que haberlas añadido usando "$server->addFunction('nombre')" para cada función, antes de "$server->handle()". El método handle es el encargado de procesar las peticiones, recogiendo los datos que se reciban utilizando POST por HTTP. Para consumir este servicio, en la carpeta "clienteSoap" crearemos el fichero "consumir.php" necesitas escribir el siguiente código: ```php= <?php $url = 'http://localhost/unidad6/servidorSoap/servidor.php'; $uri = 'http://localhost/unidad6/servidorSoap'; $paramSaludo = ['texto' => "Manolo"]; $param = ['a' => 51, 'b' => 29]; try { $cliente = new SoapClient(null, ['location' => $url, 'uri' => $uri, 'trace'=>true]); } catch (SoapFault $ex) { echo "Error: ".$ex->getMessage(); } $saludo = $cliente->__soapCall('saludo', $paramSaludo); $suma = $cliente->__soapCall('suma', $param); $resta=$cliente->__soapCall('resta', $param); echo $saludo. " La suma es: $suma y la resta es: $resta"; //También podríamos hacer $saludo=$cliente->saludo("Manolo"); ``` El servicio que has creado no incluye un documento WSDL para describir sus funciones. Sabes que existen los métodos suma, resta y saludo, y los parámetros que debes utilizar con ellos, porque conoces el código interno del servicio. Un usuario que no tuviera esta información, no sabría cómo consumir el servicio. Fíjate que utilizamos el método mágico "__soapCall()", básicamente le paso el nombre del método que estoy llamando y los parámetros de dicho método en un array. Podíamos haber hecho directamente: "$cliente->suma(4, 5), $cliente->resta(4,5), $cliente->saludo("Nombre")". Al igual que sucedía con SoapClient al programar un cliente, cuando utilizas SoapServer puedes crear un servicio sin documento WSDL asociado, o indicar el documento WSDL correspondiente al servicio; pero antes deberás haberlo creado. El primer parámetro del constructor indica la ubicación del WSDL correspondiente. El segundo parámetro es una colección de opciones de configuración del servicio. Si existe el primer parámetro, ya no hace falta más información. PHP SOAP utiliza la información del documento WSDL para ejecutar el servicio. Si, como en el ejemplo, no existe WSDL, deberás indicar en el segundo parámetro al menos la opción "uri", con el espacio de nombres destino del servicio. Para crear un documento WSDL de descripción del servicio, tendrás que seguir los pasos vistos anteriormente. El elemento raíz del documento será: ```xml= <definitions name="Operaciones" targetNamespace="http://localhost/unidad6/servidorSoap/servidor.php" xmlns:tns="http://localhost/unidad6/servidorSoap/servidor.php" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns="http://schemas.xmlsoap.org/wsdl/" > ``` En este caso no necesitas definir ningún tipo nuevo, por lo que no tendrás sección types. Los elementos message serán: ```xml= <message name="restaIn"> <part name="a" type="xsd:float"/> <part name="b" type="xsd:float"/> </message> <message name="restaOut"> <part name="return" type="xsd:float"/> </message> <message name="sumaIn"> <part name="a" type="xsd:float"/> <part name="b" type="xsd:float"/> </message> <message name="sumaOut"> <part name="return" type="xsd:float"/> </message> <message name="saludoIn"> <part name="texto" type="xsd:string"/> </message> <message name="saludoOut"> <part name="return" type="xsd:string"/> </message> ``` El portType: ```xml= <portType name="OperacionesPort"> <operation name="resta"> <input message="tns:restaIn"/> <output message="tns:restaOut"/> </operation> <operation name="suma"> <input message="tns:sumaIn"/> <output message="tns:sumaOut"/> </operation> <operation name="saludo"> <input message="tns:saludoIn"/> <output message="tns:saludoOut"/> </operation> </portType> ``` Suponiendo que el servicio web está en el fichero "servidor.php", dentro de las carpetas "servidorSoap" y "unidad6" la parte correspondiente al "binding" será: ```xml= <binding name="OperacionesBinding" type="tns:OperacionesPort"> <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http" /> <operation name="resta"> <soap:operation soapAction="http://localhost/unidad6/servidorSoap/servidor.php#resta" <input> <soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://localhost/unidad6/ServidorSoap/servidor.php" /> </input> <output> <soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://localhost/unidad6/ServidorSoap/servidor.php" /> </output> </operation> <operation name="suma"> <soap:operation soapAction="http://localhost/unidad6/servidorSoap/servidor.php#suma" <input> <soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://localhost/unidad6/servidorSoap/servidor.php" /> </input> <output> <soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://localhost/unidad6/servidorSoap/servidor.php" /> </output> </operation> <operation name="saludo"> <soap:operation soapAction="http://localhost/unidad6/servidorSoap/servidor.php#saludo"> <input> <soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/ namespace="http://localhost/unidad6/servidorSoap/servidor.php" /> </input> <output> <soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://localhost/unidad6/servidorSoap/servidor.php" /> </output> </operation> </binding> ``` Y para finalizar, el elemento service: ```xml= <service name="OperacionesService"> <port name="OperacionesPort" binding="tns:OperacionesBinding"> <soap:address location="http://localhost/unidad6/servidorSoap/servidor.php"/> </port> </service> ``` Vimos que es aconsejable definir una clase que implemente los métodos que queramos publicar en el servicio. ```php= class Operaciones{ public function resta($a, $b) { return $a - $b; } public function suma($a, $b){ return $a + $b; } public function saludo($texto){ return "Hola $texto"; } } ``` Y que al hacerlo de esta forma, en lugar de añadir una a una las funciones, podemos añadir la clase completa al servidor utilizando el método setClass de SoapServer. Lo ideal es que la clase con las funciones la implementemos en un archivo aparte y la llamemos con "require". ```php= require '../src/Operaciones.php'; $uri='http://localhost/unidad6/servidorSoap'; $parametros=['uri'=>$uri]; try { $server = new SoapServer(NULL, $parametros); $server->setClass('Operaciones'); $server->handle(); } catch (SoapFault $f) { die("error en server: " . $f->getMessage()); } ``` Aunque como ya sabes, "PHP SOAP" no genera el documento WSDL de forma automática para los servicios que crees, existen algunos mecanismos que nos permiten generarlo, aunque siempre es aconsejable revisar los resultados obtenidos antes de publicarlos. Una de las formas más sencillas es utilizar la librería php2wsdl, disponible desde Composer. Esta librería revisa los comentarios que hayas añadido al código de la clase que quieras a publicar (debe ser una clase, no funciones aisladas), y genera como salida el documento WSDL correspondiente. Para que funcione correctamente, es necesario que los comentarios de las clases sigan un formato específico: el mismo que utiliza la herramienta de documentación PHPDocumentor. Los comentarios se deben ir introduciendo en el código distribuidos en bloques, y utilizando ciertas marcas específicas como "@param" para indicar un parámetro y "@return" para indicar el valor devuelto por una función, además para indicar los métodos que queremos que se generen en WSDL utilizar usaremos "@soap". ```php= class Operaciones{ /** * @soap * @param float $a * @param float $b * @return float */ public function resta(float $a, float $b) :float{ return $a - $b; } /** * @soap * @param float $a * @param float $b * @return float */ public function suma($a, $b){ return $a + $b; } /** * @soap * @param string $texto * @return string */ public function saludo($texto){ return "Hola $texto"; } } ``` Para utilizar Php2wsdl lo integraremos a nuestro proyecto con Composer, ademas utilizaremos el "autoload" y espacios de nombre. Seguiremos la estructura de siempre, una carpeta "src" para las clases, la carpeta "public" donde estará "consumir.php" y la carpeta "servidorSoap" donde estará el archivo "servidor.php". Para integrarlo podemos hacerlo en el "composer init" buscando la dependencia o a posteriori con el comando: "composer require php2wsdl/php2wsdl". Para generar el documento WSDL a partir de la clase Operaciones anterior, debes crear un nuevo fichero, por ejemplo hemos creado en la carpeta "public" el fichero "generarwsdl.php" con el siguiente código: ```php= <?php require '../vendor/autoload.php'; use PHP2WSDL\PHPClass2WSDL; $class = "Clases\\Operaciones"; $uri = 'http://localhost/unidad6/servidorSoap/servidorW.php'; $wsdlGenerator = new PHPClass2WSDL($class, $uri); $wsdlGenerator->generateWSDL(true); $fichero = $wsdlGenerator->save('../servidorSoap/servicio.wsdl'); ``` Es decir, crear un nuevo objeto de la clase PHPClass2WSDL, e indicar como parámetros: - El nombre de la clase que gestionará las peticiones al servicio. - El URI en que se ofrece el servicio. A continuación llamamos al método "generateWSDL()" con "true" esto es para que solo convierta los métodos públicos con el parámetro "@soap". El método "save()" obtiene como salida el documento WSDL de descripción del servicio. Le indicamos nombre y ruta donde guardarlo, en la ruta elegida el Apache tiene que tener permiso de escritura, si no, no podrá crearlo. Cuando esté listo, publícalo con tu servicio. Para ello, copia el fichero obtenido en una ruta accesible vía web, e indica la URL en que se encuentra cuando instancies la clase SoapServer. Nos creamos el archivo "servidorW.php" para utilizar el archivo WSDL, que acabamos de generar. ```bash= $server = new SoapServer("http://localhost/unidad6/servidorSoap/servicio.wsdl"); ``` Si añades a la URL del servicio el parámetro GET wsdl, verás el fichero de descripción del servicio. Se deja de ejemplo el código de como quedaría el servidor y un archivo "consumir.php" donde vemos algunos ejemplos de como consumir los servicios: ```php= <!-- servidor.php --> <?php require '../vendor/autoload.php'; $url = "http://localhost/unidad6/servidorSoap/servicio.wsdl"; try { $server = new SoapServer($url); $server->setClass('Clases\Operaciones'); $server->handle(); } catch (SoapFault $f) { die("error en server: " . $f->getMessage()); } ``` ```php= <!-- consumir.php --> <?php $url = "http://localhost/unidad6/servidorSoap/servicio.wsdl"; try { $cliente = new SoapClient($url); } catch (SoapFault $ex) { die("Error en el cliente: " . $ex->getMessage()); } //Vemos las funciones que nos ofrece el servicio var_dump($cliente->__getFunctions()); echo "<br>"; //una manera de llamar a suma echo $cliente->suma(20, 40); echo "<br>"; //otra forma de llamar a suma $para = ['a' => 12, 'b' => 45]; echo $cliente->__soapCall('suma', $para); ``` En la variable "$url" del archivo "consumir.php" podríamos haber puesto $url="http://localhost/unidad6/servidorSoap/servidorW.php?wsdl" y hubiese funcionado igual. ## Preguntas 1. Relaciona las siglas con aquello a que hacen referencia: | Siglas | Relación | Significado | | -------- | -------- | -------- | | SOAP | 3 | 1. Protocolo para transmitir páginas web. | | HTTP | 1 | 2. Protocolo para ejecutar código de forma remota.| | RPC | 2 | 3. Protocolo para intercambiar información en un servicio web.| | WSDL | 4 | 4. Lenguaje para describir servicios web.| 2. El elemento Envelope debe figurar como raíz en un mensaje SOAP, y obligatoriamente deberá contener un elemento: - [ ] Header - [x] Body 3. En la sección types de un documento WSDL, se deben definir: - [ ] Todos los tipos de elementos que se usen en el servicio web. - [X] Los tipos de elementos compuestos que se usen en el servicio web, como los objetos y arrays. 4. En un documento WSDL, cada una de las funciones que implementa el servicio se refleja en un elemento de tipo: - [x] operation - [ ] portType 5. Relaciona los elementos que componen un documento WSDL, con la parte del servicio web a que hacen referencia: | Siglas | Relación | Significado | | -------- | -------- | -------- | | types | 4 | 1. Conjunto de datos, como las listas de parámetros de las funciones. | | definitions | 3 | 2. Caracterización del servicio, que incluye la URL de acceso. | | message | 1 | 3. Elemento raíz de un documento WSDL.| | port | 2 | 4. Definición de los tipos de elementos usados en el servicio.| 6. El principal inconveniente de la extensión PHP SOAP es que: - [ ] No ofrece un interface de programación orientado a objetos. - [x] Una vez que has programado un servicio web, no permite generar de forma automática el documento WSDL. 7. Al utilizar la clase SoapServer para crear un servicio web: - [X] Si no creas y le asocias un documento WSDL, deberás indicar las opciones del mismo en la llamada al constructor "SoapServer". - [ ] Debes indicar la ubicación del documento WSDL de descripción del servicio. 8. Relaciona los términos siguientes con el concepto a que hacen referencia: | Elemento | Relación | Hace referencia a | | -------- | -------- | -------- | | wsdl2php | | 1. Herramienta para generar documentación, a partir de unas clases PHP adecuadamente documentadas. | | PHPDocumentor | 1 | 2. Método de la clase "Php2wsdl" que genera el documento WSDL. | | php2wsdl | | 3. Herramienta para crear un documento WSDL, a partir de un servicio web programado como una clase PHP adecuadamente documentada. | | save() | | 4. Herramienta para construir clases PHP a partir de un documento WSDL. | 9. Al utilizar la clase "SoapClient" para comunicarte con un servicio web: - [x] La herramienta wsdl2phpgenerator no es necesaria para obtener las opciones de configuración del mismo a partir del documento wsdl. - [ ] Si el servicio dispone de una descripción en formato WSDL, puedes utilizar la herramienta wsdl2phpgenerator para no especificar a mano las opciones del mismo en la llamada al constructor de la clase SoapClient ###### tags: `DAW` `DSW` `PHP` `UT7`