Na sequência do artigo anterior, onde mostrámos como consumir WebServices com autenticação NTLM em PHP, apresentamos agora uma particularidade do método definido nesse artigo.
A dificuldade ocorre quando existe mais que uma operação, no WSDL, com o mesmo nome (ou seja, foram definidos overloaded methods). Para casos sem autenticação NTLM, a solução é simples, mas com NTLM a solução é um pouco mais complexa.
- Métodos com diferentes assinaturas (method overloading)
- Em PHP, como é possível especificar qual o métodos a consumir?
1. Métodos com diferentes assinaturas (method overloading)
Para perceberes o caso em questão, existem várias linguagens de programação que permitem aquilo a que se chama de method overload, o que significa que podes declarar vários métodos com o mesmo nome e a única diferença prende-se com os parâmetros recebidos.
Como exemplo, podes considerar as seguintes assinaturas de métodos C#:
array ListaOpcoes (int start, int end)
array ListaOpcoes (int start, int end, int pageNumber, int perPage)
Isto quer dizer que em linguagens como C# ou Java é possível teres vários métodos com o mesmo nome (no exemplo, ListaOpcoes) mas que recebem diferentes parâmetros.
Como nota, o PHP não permite overload de métodos.
2. Em PHP, como é possível especificar qual o métodos a consumir?
Na maioria dos casos, basta passar um objeto ou um array, com a estrutura correta, para que seja invocado o método pretendido. Para casos em que se use autenticação NTLM é preciso alguma imaginação.
2.1. WDSL com múltiplas definições de métodos
Neste momento apresento um caso real. Se tens um caso semelhante, a melhor forma de diagnóstico é analisar o WSDL (descrição em Inglês).
No meu caso o XML tinha, entre outras coisas, o seguinte conteúdo:
<?xml version="1.0" encoding="utf-8"?>
<wsdl:definitions xmlns:s="https://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tns="http://www.url.pt/webservices" xmlns:s2="http://www.url.pt/webservices/AbstractTypes" xmlns:s1="http://microsoft.com/wsdl/types/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" targetNamespace="http://www.url.pt/webservices" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
(....)
<s:element name="ListS">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="query" type="s:string" />
<s:element minOccurs="1" maxOccurs="1" name="pageNumber" type="s:int" />
<s:element minOccurs="1" maxOccurs="1" name="pageSize" type="s:int" />
<s:element minOccurs="0" maxOccurs="1" name="sortBy" type="s:string" />
</s:sequence>
</s:complexType>
</s:element>
<s:element name="List">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="processQuery" type="tns:ProcessQuery" />
</s:sequence>
</s:complexType>
</s:element>
<s:complexType name="ProcessQuery">
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="QueryGenericData" type="tns:QueryGenericData" />
<s:element minOccurs="1" maxOccurs="1" name="MyProcessesOnly" type="s:boolean" />
<s:element minOccurs="1" maxOccurs="1" name="IsAdminQuery" type="s:boolean" />
<s:element minOccurs="0" maxOccurs="1" name="Status" type="tns:ArrayOfString" />
<s:element minOccurs="1" maxOccurs="1" name="OrganizationName" type="s:boolean" />
<s:element minOccurs="1" maxOccurs="1" name="KeysOnly" type="s:boolean" />
<s:element minOccurs="0" maxOccurs="1" name="Classifications" type="tns:ArrayOfClassificationKey" />
<s:element minOccurs="0" maxOccurs="1" name="Classification" type="tns:ClassificationKey" />
<s:element minOccurs="0" maxOccurs="1" name="ProcessCode" type="s:string" />
(....)
</s:sequence>
</s:complexType>
(....)
<wsdl:message name="ListSSoapIn">
<wsdl:part name="parameters" element="tns:ListS" />
<wsdl:message name="ListSoapIn">
<wsdl:part name="parameters" element="tns:List" />
</wsdl:message>
(....)
<wsdl:operation name="List">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Get a result query in an XmlDocument.</wsdl:documentation>
<wsdl:input name="ListS" message="tns:ListSSoapIn" />
<wsdl:output name="ListS" message="tns:ListSSoapOut" />
</wsdl:operation>
<wsdl:operation name="List">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Get a result query in an DataSet.</wsdl:documentation>
<wsdl:input message="tns:ListSoapIn" />
<wsdl:output message="tns:ListSoapOut" />
</wsdl:operation>
(....)
</wsdl:definitions>
O WSDL começa por indicar os tipos complexos. O elemento ListS
é um tipo complexo composto por tipos simples (string, int, int, string) e o elemento List
é um tipo complexo composto por um único ProcessQuery
. Neste exemplo, não vamos dar interesse ao tipo de resultados.
A definição do ProcessQuery
é um tipo composto por outros tipos complexos e tipos simples.
Mais abaixo no WSDL são apresentadas as mensagens. Para este exemplo, o WSDL tinha duas mensagens: ListSSoapIn
que recebe um objeto do tipo tns:ListS
e outra mensagem, ListSoapIn
, que recebe um objeto do tipo tns:List
.
Por fim, percebemos que existem duas operações com o nome List
, sendo que uma tem como input a mensagem tns:ListSSoapIn
e outra tem como input a mensagem tns:ListSoapIn
.
2.2. Definir operação a consumir sem autenticação NTLM
Por norma é bastante simples indicar qual o método que se pretende consumir, bastando para isso passar o objeto correto na chamada do webservice. Por exemplo, para consumir o método List
, com a mensagem tns:ListS
, bastaria executar o seguinte código (exemplo usando o SoapClient):
$client = new SoapClient("my.wsdl");
$parameters = array(
'query' => '',
'pageNumber' => 1,
'pageSize' => 10,
'sortBy' => 'name
);
$result = $client->List($parameters);
Outra possibilidade é a criação de um objeto com as propriedades corretas, usando o stdClass
(mais informação neste post do Stackoverflow - em inglês). Por exemplo:
$client = new SoapClient("my.wsdl");
$parameters = new stdClass();
$parameters->query = '';
$parameters->pageNumber = 1;
$parameters->pageSize = 10;
$parameters->sortBy = 'name';
$result = $client->List($parameters);
2.3. Definir operação a consumir com autenticação NTLM
A invocação de uma operação especifica com autenticação NTLM é mais complexa. A razão é simples, para que a autenticação NTLM funcione é necessário extender o SoapClient
e, com isso, o método __doRequest()
, que é responsável pela execução dos pedidos ao WebService.
A forma como eu ultrapassei a questão foi através da definição do SoapAction
e a criação do envelope XML que é enviado. Deixei de usar a classe SoapClient e passei a fazer um cURL
para consumir o WebService. Aqui fica o código para consumir o método List
com o elemento tns:ProcessQuery
:
// Criação do envelope XML
$request = '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://www.url.pt/webservices">
<soapenv:Header/>
<soapenv:Body>
<web:List>
<web:processQuery>
<web:ProcessCode>' . $params->ProcessCode . '</web:ProcessCode>
<web:Year>' . $params->Year . '</web:Year>
<web:NumberFrom>' . $params->NumberFrom . '</web:NumberFrom>
<web:NumberTo>' . $params->NumberTo . '</web:NumberTo>
<web:Subject>' . $params->Subject . '</web:Subject>
</web:processQuery>
</web:List>
</soapenv:Body>
</soapenv:Envelope>';
// Cria o objeto cURL e define as propriedades por defeito
$soap_do = curl_init();
curl_setopt($soap_do, CURLOPT_URL, 'my.wsdl);
curl_setopt($soap_do, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($soap_do, CURLOPT_TIMEOUT, 10);
curl_setopt($soap_do, CURLOPT_RETURNTRANSFER, true);
curl_setopt($soap_do, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($soap_do, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($soap_do, CURLOPT_POST, true);
// Aqui definimos que o conteúdo do post é o envelope (XML) criado anteriormente
curl_setopt($soap_do, CURLOPT_POSTFIELDS, $request);
// Esta opção permite monitorizar os pedidos através do Fiddler
// @see https://www.telerik.com/fiddler
curl_setopt($soap_do, CURLOPT_PROXY, '127.0.0.1:8888');
// Neste momento estamos a definir qual é o correto SOAPAction. O SOAPAction deve ter o mesmo nome do elemento (s:element),
// neste caso seria List ou ListS.
curl_setopt($soap_do, CURLOPT_HTTPHEADER, array(
'Method: POST',
'User-Agent: PHP-SOAP-CURL',
'Content-Type: text/xml; charset=UTF-8',
'SOAPAction: "http://www.myurl.pt/webservices/List"',
'Accept-Encoding: gzip,deflate',
));
// Define o correcto encoding do pedido
curl_setopt($soap_do, CURLOPT_ENCODING, "gzip");
// Por fim, e sem esquecer que estamos a trabalhar com autenticação NTLM, temos de definir o tipo de
// autenticação e o utilizador e password
curl_setopt($soap_do, CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
curl_setopt($soap_do, CURLOPT_USERPWD, 'domain\user:password');
// Executa o pedido e obtem a resposta do WebService
$curlResponse = curl_exec($soap_do);
Como se pode analisar do código apresentado, a solução passa por criar o próprio envelope XML e criar todo o cURL manualmente. Desta forma, o $request
tem o formato correto e garantimos que o SOAPAction
(a operação) executado é o que pretendemos.
Na verdade a solução não é muito elegante devido à necessidade de definir o XML em vez de ser criado automaticamente. Mas este caso é, de facto, bastante específico e não deverá ser regra para os casos em que é possível usar o SoapClient.