Banner

Ultima revisión 25/01/2013

Paso de Parámetros en SOAP y PHP II: Estructuras Complejas

Si no sabéis mucho sobre SOAP o no habéis leído el artículo anterior Paso de Parámetros en SOAP y PHP I, os recomiendo que lo leáis para aclarar algunas ideas antes de entrar en más detalle.

Un error que cometen muchos diseñadores cuando van a programar un Servicio Web es intentar entender SOAP como si de un lenguaje tradicional se tratase. Digo esto porque uno, al pensar en Arrays, imagina una lista de elementos con nombre y, generalmente, no lo ve como una estructura de datos compleja.

A grandes rasgos un Array en SOAP se debe tratar como una estructura tipo Objeto con repeticiones. Es decir, primero deberemos crear una estructura tipo Objeto con claves y valores por defecto, después se lo asociaremos a un tipo de datos Array y finalmente usarremos esa estructura repetitiva como un Tipo de Datos definido por el Usuario.

Ejemplo 2: Paso de Parámetros de Tipo Estructura en SOAP

La archivo WSDL (soap.wsdl)

Como se ve a continuación, TENEMOS QUE DECLARAR LA SECCIÓN DE TYPES porque una estructura u objeto se compone de varios elementos simples.

Además es obligatorio usar la opción de xmlns:SOAP-ENC = "http://schemas.xmlsoap.org/soap/encoding/" ya que sin ella no podríamos manejar los arrays.

También, se puede observar que utilizamos atributos como minOccurs y maxOccurs para establecer los límites del array y nillable para indicar que puede tener valores nulos.

<?xml version="1.0" encoding="utf-8"?>
<!-- NOTAS IMPORTANTES -->
<!-- ***************** -->
<!-- SI SE CAMBIA EL SERVICIO WEB DE UBICACIÓN SE DEBE SUSTITUIR http://www.islavisual.com/ws POR LA URL QUE PROCEDA. -->
<!-- SI ADEMÁS SE UTILIZA UN ARCHIVO schema.xsd TAMBIÉN HAY QUE CAMBIARLO DÓNDE APAREZCA. -->
<definitions name="servicio" 
             targetNamespace='http://www.islavisual.com/ws/soap.wsdl'
             xmlns            ='http://schemas.xmlsoap.org/wsdl/'
             xmlns:http        ="http://schemas.xmlsoap.org/wsdl/http/"
             xmlns:soap        ="http://schemas.xmlsoap.org/wsdl/soap/"
             xmlns:SOAP-ENC    ="http://schemas.xmlsoap.org/soap/encoding/" 
             xmlns:xsd        ='http://www.w3.org/2001/XMLSchema'>
    
    <!--    SI SE DESEA DEFINIR UN SCHEMA EXTERNO
            ------------------------------------------------------------------------------------------------------------
            AQUÍ ESTÁ UNO DE LOS PUNTOS IMPORTANTES CON RESPECTO AL PASO DE PARÁMETROS SIMPLES (TIPO STRING, INT, DATE, ...)
            YA QUE AQUÍ SE DEFINEN TODOS LOS TIPOS DEFINIDOS POR EL USUARIO QUE SE VAN A USAR EN EL WEB SERVICE. -->
    <types>
        <xsd:schema targetNamespace='http://www.islavisual.com/ws'>
            <complexType name='Estructura'>
                <sequence>
                    <element name='param1' type='xsd:string' />
                    <element name='param2' type='xsd:string' />
                    <xsd:any maxOccurs="unbounded"/>
                </sequence>
            </complexType>
            
            <complexType name="ArrayOfCadenas">
                <complexContent>
                    <restriction base="soapenc:Array">
                        <sequence>
                            <element name="item" type="string" minOccurs="0" maxOccurs="unbounded" nillable="true" />
                        </sequence>
                        <attributeGroup ref="soapenc:arrayType" xsd:arrayType="xsd:string[]" />
                    </restriction>
                </complexContent>
            </complexType>
            
            <complexType name="ArrayEstructuras">
                <complexContent mixed="false">
                    <restriction base="soapenc:Array">
                        <sequence>
                            <element name="item2" type="Estructura" minOccurs="0" maxOccurs="unbounded" nillable="true" />
                        </sequence>
                        <attributeGroup ref="soapenc:arrayType" xsd:arrayType="Estructura[]" />
                    </restriction>
                </complexContent>
            </complexType>
        </xsd:schema>
    </types>
    <!-- fin TYPES -->
    
    <!--    AHORA DEBEMOS DEFINIR LO QUE EN SOAP SE DENOMINA MENSAJES. ESTOS MENSAJES SE CORRESPONDEN CON EL NOMBRE DE LA
            FUNCIÓN Y SUS PARÁMETROS DE LA CLASE DEL SOAP-SERVER. 
            EL name DEL message ES EQUIVALENTE AL NOMBRE DE LA FUNCIÓN. 
            EL name DE CADA part SE CORRESPONDE CON CADA UNO DE LOS PARÁMETROS DE LA FUNCIÓN. 
            EN EL ELEMENTO part:
                SI QUEREMOS DEFINIR PARÁMETROS DE ENTRADA LO HAREMOS CON EL ATRIBUTO type EN EL MENSAJE QUE TERMINA EN Request.
                SI QUEREMOS DEFINIR PARÁMETROS DE ENTRADA LO HAREMOS CON EL ATRIBUTO type EN EL MENSAJE QUE TERMINA EN Response.-->
    
    <!-- MESSAGE estaActivo -->
    <message name='estaActivoRequest'>
        <part name='params' type='xsd:ArrayEstructuras' /> <!-- ESTE ES OTRO PUNTO CLAVE EN ESTE EJEMPLO -->
    </message>
    <message name='estaActivoResponse'>
        <part name='result' type='xsd:string' />
    </message>
    <!-- fin MESSAGE estaActivo -->
    
    <!--    DEFINICIÓN DE LAS OPERACIONES PERMITIDAS Y MENSAJES INVOLUCRADOS (PETICIÓN Y RESPUESTA, ...).
            NORMALMENTE    TANTO input COMO output TENDRÁN CORRESPONDENCIAS CON LOS MENSAJES DEFINIDOS EN LA ZONA DE MENSAJES
            ANTES DEFINIDA. -->
            
    <portType name='servicioPortType'>
        <!-- OPERACION estaActivo -->
        <operation name='estaActivo'>
            <input message='tns:estaActivoRequest' />
            <output message='tns:estaActivoResponse' />
        </operation>
        <!-- fin OPERACION estaActivo -->
    </portType>
    
    <!--    AHORA ESPECIFICAMOS LOS PROTOCOLOS DE COMUNICACIÓN USADOS. DICHO DE OTRA MANERA ES LA DEFINICIÓN DEL FORMATO DE 
            CADA MENSAJE Y DETALLES DEL PROTOCOLO DE CADA PortType.
            AQUÍ DECLARAMOS UNA OPERACIÓN, LE ASOCIAMOS LA FUNCIÓN DE LA CLASE Y, SU ENTRADA Y SALIDA -->
            
    <binding name='servicioBinding' type='tns:servicioPortType'>
        <soap:binding style='document' transport='http://schemas.xmlsoap.org/soap/http'/>
        <!-- OPERACION estaActivo -->
        <operation name='estaActivo'>
            <soap:operation soapAction='http://www.islavisual.com/ws#estaActivo' />
            <input>
                <soap:body use='literal' encodingStyle='http://schemas.xmlsoap.org/soap/encoding/' namespace='http://www.islavisual.com/ws' />
            </input>
            <output>
                <soap:body use='literal' encodingStyle='http://schemas.xmlsoap.org/soap/encoding/' />
            </output>
        </operation>
        <!-- fin OPERACION estaActivo -->
    </binding>
    
    <!-- AHORA REALIZAMOS LA DEFINICIÓN DEL CONJUNTO DE PUERTOS Y DIRECCIÓN DE LOS MISMOS. -->
    <service name='servicio'>
        <port binding='tns:servicioBinding' name='servicioPort'>
            <soap:address location='http://www.islavisual.com/ws/soapServer.php'/>
        </port>
    </service>
</definitions>

La archivo client.php

Podemos declarar las opciones de la conexión estableciendo un array bastante simple:

$options = array(
    // Opciones frecuentes
    'trace' => true,
    'exceptions' => true,
    'wsdl_cache' => WSDL_CACHE_NONE,
    'features' => SOAP_SINGLE_ELEMENT_ARRAYS,
    'soap_version'   => SOAP_1_2,

    // Credenciales de Autentificación para peticiones SOAP.
    'login' => 'username',
    'password' => 'password',
    
    // Codificación de la conexión
    'encoding'=>'UTF-8',
    
    // URL del Proxy. No se debe poner aquí el http ó https ya que no funcionaría.
    'proxy_host' => 'www.islavisual.com', 
    'proxy_port' => 44300,
    
    // Credenciales de Autentificación para el Proxy.
    'proxy_login' => NULL,
    'proxy_password' => NULL);

Declaramos el array a enviar:

$params = array("param1"=>"valor1", "param2"=>"valor2");

Establecemos la conexión:

$client = new SoapClient("http://www.islavisual.com/ws/soap.wsdl", $options );

Hacemos la llamada a la función deseada. En este caso es una función muy simple que sólo nos devuelve el TimeStamp de Unix si recibe el valor 'Hello' y NULL si no coincide.

$soapstruct = new SoapVar($params, SOAP_ENC_OBJECT, "params", "http://www.islavisual.com/ws/schema.xsd");
$client->isAlive( new SoapParam($soapstruct, "message"));

Y finalmente procedemos a establecer las cabeceras y la respuesta del Servidor:

header ("Content-Type:text/xml; charset=utf-8");
echo $client->__getLastResponse();

La archivo server.php

Primero podemos configurar algunas variables del Servidor aunque no es necesario:

ini_set("soap.wsdl_cache_enabled", "1");
ini_set("soap.soap.wsdl_cache_dir", "/tmp");
ini_set("soap.wsdl_cache_ttl", "86400");

Segundo declaramos la clase con las funciones a incorporar a nuestro Web Service:

class SOAPFunctions {
    private $_SOAPSERVER = NULL;
    private $_HEADERVARS = "";
    private $_params     = array();
    private $_USER         = "";
    private $_PASSWORD     = "";

    public function __construct($soapServer_resource){
        $this->_SOAPSERVER     = $soapServer_resource;
        $this->_USER        = $_SERVER['PHP_AUTH_USER'];
        $this->_PASSWORD    = $_SERVER['PHP_AUTH_PW'];
    }

    public function isAlive($params){
        foreach($params as $key=>$value){
            $msg .= "- ".$key."=".$value."<br />";    
        }

        return $msg;
    }
}

Finalmente establecemos el Constructor de SoapServer y configuramos unas pocas propiedades:

$wsdl = "http://".$_SERVER['SERVER_NAME'].dirname($_SERVER['SCRIPT_NAME'])."/";
$soap_server  = new SoapServer($wsdl."soap.wsdl");
$soap_server->setClass("SOAPFunctions", $soap_server);
$soap_server->handle();

  • Con setClass le indicamos cuál es la clase que tiene nuestros métodos.
  • Con handle le indicamos que procese la petición SOAP y nos devuelva una respuesta.

Ahora si lo queréis probar sólo tendréis que ejecutar o llamar al client.php