Services Web : Utiliser SOAP avec PHP
SOAP, le Simple Object Access Protocol, est la centrale électrique des services web. C’est un protocole très adaptable et orienté objet qui existe dans plus de 80 implémentations sur des plate-formes très populaires, dont AppleScript, JavaScript et Cocoa. Il fournit une couche de communication flexible entre les applications, sans tenir compte de la plate-forme et du lieu. Compte tenu qu’ils parlent en SOAP tous les deux, une application web en PHP peut demander à une application base de données en C++ située sur un autre continent de rechercher le prix d’un livre et ainsi obtenir la réponse directement. Un autre article pour Développeur Internet (en anglais) montre comment utiliser SOAP avec AppleScript et Perl.
SOAP a été créé de manière collaborative en tant que protocole ouvert. Dès les débuts de son développement, XML-RPC fut rapidement adopté et il jouit de sa propre popularité en tant qu’alternative plus simple à SOAP. Ils encodent tous les deux les messages en XML et utilisent tous les deux HTTP pour les transporter. SOAP, cependant, peut utiliser d’autres protocoles de transport, offre de nombreuses fonctions élevées et son développement est rapide. (Pour en savoir plus sur SOAP et les Services Web, essayez la démystification très éducative de XML.com.)
Une transaction SOAP commence par l’appel qu’effectue une application à une procédure distante. Le script client SOAP encode alors la requête de procédure sous forme de charge utile XML et l’envoie via le protocole de transport à un script serveur. Le serveur analyse la requête et la passe à une méthode locale, qui renvoie le résultat à la fonction originale.
Il y a de nombreuses implémentations différentes de SOAP sous PHP. C’est en constante évolution : de nouvelles apparaissent et les anciennes ne sont plus maintenues ou disparaissent simplement. Au moment de cet article, l’implémentation PHP la plus viable de SOAP semble être SOAPx4 de Dietrich Ayala, aussi connue sous le nom de NuSOAP. Cette implémentation est la plus couramment utilisée et apparaît comme étant la plus aboutie et la plus activement maintenue, elle montre de réelles signes de robustesse et de popularité. Elle n’est pas complète—un certain nombre de fonctions, y compris une documentation complète, sont toujours en travaux—mais elle reste une solution SOAP très viable et facile à utiliser.
Installation
D’abord, vous devez faire en sorte que PHP soit configuré et qu’il soit opérationnel sur votre Mac. C’est facile à faire : jetez un oeil à notre tutoriel pour cela. Si vous souhaitez envoyer des messages SOAP via HTTPS, vous devrez inclure le module cURL dans votre installation de PHP (Voir php.net pour cela).
L’étape suivante consiste à installer NuSOAP. Téléchargez le package à partir du site du développeur. Décompressez le de façon à obtenir un dossier contenant la documentation ainsi que le fichier nusoap.php qui contient les classes actuelles dont nous aurons besoin. Pour les utiliser, placez nusoap.php dans votre chemin d’accès à PHP et incluez le dans les scripts que vous écrivez.
La classe de base est nusoap_base. De part son utilisation et celle de ses sous-classes, tout est possible. En guise d’exemple, je vais construire un script serveur SOAP et un script client, puis disséquer la transaction XML qu’ils envoient.
Un Serveur SOAP
Voici un serveur simple, écrit en PHP, qui prend en entrée un ISBN (International Standard Book Number), effectue une recherche dans une base de données imaginaires et renvoie le prix du livre correspondant. Dans ce serveur, j’utilise la classe soap_server et quatre méthodes de cette classe : le constructeur de soap_server, register, fault et service :
<?php
// fonction de récupération d'un prix à partir de la base de données
function lookup($ISBN) {
$query = "select price from books where isbn = ". $ISBN;
if (mysql_connect("localhost", "username", "passwd"))
else { $error = "Erreur de connexion à la base";
return $error; }
if (mysql_select_db("books"))
else { $error = "Base de données introuvable";
return $error; }
if ($result = mysql_query($query))
else { $error = "mysql_error()";
return $error; }
$price = mysql_result($result, 0, 0);
return $price;
}
// inclure les classes SOAP
require_once('nusoap.php');
// créer l'objet serveur
$server = new soap_server;
// enregistrer le service de recherche
$server->register('lookup');
// si la recherche échoue, retourner une erreur
if $price == 0 {
$error = "Erreur dans la recherche du prix";
}
if (isset($error)) {
$fault =
$server->fault('soap:Server','http://mydomain.com/booklookupscript.php',$err
or);
}
// envoyer le résultat sous forme de réponse SOAP via HTTP
$server->service($HTTP_RAW_POST_DATA);
?>
La première méthode utilisée est la constructeur de soap_server qui crée l’objet serveur qui fera tout le boulot à ma place. J’assigne cet objet à $server. Ensuite vient register, qui indique au serveur ce qu’il doit faire (dans ce cas, appeler la fonction lookup()). Le seul paramètre de la méthode est le nom de la fonction. Il y a d’autres paramètres optionnels qui peuvent être utilisés pour définir l’espace de nommage et les informations SOAPAction telles que définies dans les spécifications de SOAP, mais ils ne sont pas nécessaires dans cet exemple. La syntaxe générale de la méthode register est :
register(name, in, out, namespace, SOAPAction, style)
La premier paramètre est le seul qui soit obligatoire. in et out sont des tableaux de valeurs en entrée et en sortie ; namespace et SOAPAction sont utilisés en respect des spécifications de SOAP. Enfin, style est utilisé pour indiquer si les données envoyées sont des données XML litérales (ce qui est vrai par défaut et dans ces exemples) ou si ce sont des données applicatives séquencées en RPC.
Donc, la fonction est exécutée et la valeur retournée est passée à l’objet serveur. Puis la méthode service retourne une réponse SOAP au client qui a initié la requête. L’argument passé à la méthode service est $HTTP_RAW_POST_DATA.
Gestion des Erreurs
Les bases de données n’étant pas parfaites, le script comporte quelques lignes de gestion des erreurs. La fonction lookup contient trois pièges pour différents types d’erreurs relatives à la base de données MySQL. Chaque piège assigne une chaîne d’identification de l’erreur à la variable $error et retourne cette variable à la fonction principale. En plus, la fonction principale teste la variable $price pour vérifier qu’elle n’est pas nulle, ce qui indiquerait une entrée défectueuse dans la base de données.
Si l’un de ces pièges attrape une erreur, la méthode fault de NuSOAP est appelée. Cela stoppe l’exécution du script serveur et retourne les paramètres de la méthode au client dans la variable $fault. La syntaxe de la méthode fault est :
fault(faultcode, faultactor, faultstring, faultdetail)
Les deux premiers arguments sont requis, les autres sont optionnels. Pour l’argument faultcode, un code erreur lisible par une machine doit être fourni, tel que décrit dans les spécifications de SOAP. Il y a quatre codes erreurs prédéfinis dans les sépcifications : VersionMismatch, MustUnderstand, Client et Server. Ils doivent être données sous forme de noms qualifiés dans l’espace de nommage en les préfixant avec SOAP-ENV:. Une erreur VersionMismatch indique des espaces de nommage incompatibles. Une erreur MustUnderstand est utilisée lorsqu’elle arrive suite à incompréhension d’une entrée obligatoire d’en-tête. Client est utilisée lorsque l’erreur repose dans le message reçu du client. Et Server indique un problème rencontré lors des traitements effectués sur le serveur, dissocié du message SOAP en tant que tel. Ce dernier code est celui que j’utilise lorsqu’un problème survient lors de la recherche dans la base de données.
L’argument faultactor devrait contenir l’URI d’origine de l’erreur. Cela est plus important pour les transactions où de nombreux intermédiaires sont engagés. Dans cet exemple, j’utilise l’URI du script serveur. (Note : la documentation de NuSOAP implique que l’élément faultactor doit être positionné soit à “client” soit à “server.” Les spécifications de SOAP, cependant, indiquent qu’ils devraient être une URI).
faultstring et faultdetail sont positionnés à part pour expliquer l’erreur dans un langage humain. faultstring devrait être un message bref indiquant la nature du problème, tandis que faultdetail peut aller plus loin dans le détail—il peut même contenir un tableau avec des données spécifiques relatives à l’erreur. Dans mon exemple, je passe la chaîne $error à faultstring et j’omet de valoriser faultdetail.
Un Client SOAP
Maintenant, je vais écrire un client pour un serveur SOAP existant de façon à le voir en action. J’utiliserai le serveur les XMethods du serveur Barnes & Noble Price Quote, qui se comportent comme le serveur donné en exemple précédemment. Il prend un ISBN en entrée et retourne un prix de chez Barnes & Noble.
Le script client devra envoyer une requête contenant un ISBN puis analyser la réponse. Dans ce script, j’utilise la classe soapclient, son constructeur et call qui gère la définition de la requête et l’analyse de la réponse en un seul endroit. La seule méthode disponible sur le serveur est GetPrice qui ne prend qu’un seul argument, une chaîne appelée isbn. Elle retourne une variable à virgule flottante appelée return.
<?php
// inclure les classes SOAP
require_once('nusoap.php');
// définir le tableau des paramètres (numéro ISBN)
$param = array('isbn'=>'0385503954');
// définir le chemin d'accès vers l'application serveur
$serverpath ='http://services.xmethods.net:80/soap/servlet/rpcrouter';
// définir l'espace de nommage de la méthode
$namespace="urn:xmethods-BNPriceCheck";
// créer un objet client
$client = new soapclient($serverpath);
// faire l'appel
$price = $client->call('getPrice',$param,$namespace);
// si une erreur survient, afficher les infos de l'erreur
if (isset($fault)) {
print "Erreur : ". $fault;
}
else if ($price == -1) {
print "Le livre n'est pas dans la base de données.";
} else {
// sinon, afficher le résultat
print "Le prix du livre numéro ". $param[isbn] ." est ". $price . " $";
}
// tuer l'objet
unset($client);
?>
Le constructeur soapclient prend comme argument l’URL du serveur. Ayant ainsi l’objet serveur initialisé, je passe à la méthode call le nom de la fonction souhaitée (getPrice), les paramètres nécessaires (le tableau contenant la chaîne ISBN à rechercher) et l’espace de nommage requis de la méthode : urn:xmethods-BNPriceCheck.
Les paramètres de la méthode d’appel de soapclient sont : function name, parameter array, et trois autres paramètres optionnels : namespace, SOAPAction, et un tableau d’en-têtes. La définition du serveur spécifiera les paramètres nécessaires si’il y en a. Le serveur Barnes & Noble Price Quote requiert la définition d’un espace de nommage de la méthode (urn:xmethods-BNPriceCheck) mais pas de SOAPAction ni d’en-têtes SOAP. Les informations offertes par ce serveur et ce qu’il nécessite ont été glannées sur le listing du serveur sur l’index des XMethods des serveurs SOAP. (Ce serveur particulier est hébergé par XMethods, mais l’index liste une large variété de serveurs, sans tenir compte de l’hôte).
La méthode call du client effectue la transaction SOAP et retourne le contenu de la réponse du serveur dans la variable $price. Le script vérifie la présence de $fault, que le serveur retourne en cas d’erreur dans la transaction. Si la variable $fault est valorisée, le script affiche les informations relatives à l’erreur. S’il n’y a pas d’erreur, il vérifie que le prix retourné n’est pas à -1, ce qui indique que le livre demandé n’a pas été trouvé. Sinon, le prix est affiché.
Regardons de plus près la Transaction
Le réel message XML envoyé par le client vers le serveur ressemble à cela :
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <SOAP-ENV:Body> <ns1:getPrice xmlns:ns1="urn:xmethods-BNPriceCheck" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <isbn xsi:type="xsd:string">0385503954</isbn> </ns1:getPrice> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
La balise Envelope contient des pointeurs vers les définitions globales de l’espace de nommage. Elle comprend aussi des pointeurs vers le schéma d’enveloppe SOAP hébergé sur xmlsoap.org et vers la définition de schéma XML du W3C. Cela indique au serveur où récupérer les définitions des nombreuses balises qu’il utilise. La classe XMLSchema (qui est, au moment de cet article, seulement expérimentale) peut être utilisée pour fonctionner avec des aspects du schéma XML.
La définition de schéma est automatiquement positionnée par NuSOAP à http://www.w3.org/2001/XMLSchema. Si vous souhaitez changer ceci, vous devez valoriser la variable globale $XMLSchemaVersion :
$XMLSchemaVersion = 'http://www.my.org/MYSchema/';
Des propos sur les menus détails du schéma XML du W3C peuvent être trouvés dans le nouveau livre d’O'Reilly sur le sujet.
A l’intérieur de la balise Envelope il y a la balise Body qui contient le corps du message. Ses attributs sont déterminés par les paramètres de l’appel de fonction. Le nom de la méthode distante, l’espace de nommage de la méthode et le contenu réel du message—la chaîne ISBN—sont valorisés par le script client. NuSOAP détecte automatiquement le type de variable et incorpore l’espace de nommage du type (xsd:string) dans la balise isbn. Si une SOAPAction avait été positionnée dans le script, elle aurait apparu en tant qu’en-tête HTTP SOAPAction.
Le style d’encodage est réglé par défaut à http://schemas.xmlsoap.org/soap/encoding/. Cela est pré-réglé par NuSOAP dans l’élément SOAP-ENC du tableau publique appelé namespaces. Pour le changer, incluer simplement une ligne dans votre script :
$namespaces[SOAP-ENC] = 'http://my.special.encoding';
La même technique peut être utilisée pour changer d’autres valeurs d’espace de nommage si nécessaire. Les clés du tableau namespaces sont SOAP-ENV, xsd, xsi, SOAP-ENC et si, correspondant aux URI d’espace de nommage du schéma d’enveloppe, la définition du schéma XML (égale à $XMLSchemaVersion), l’instance de schéma XML, le style d’encodage et l’URI de test d’interopérabilité de SOAP, respectivement. Les réglages par défaut de ces données ne devraient pas être modifiés en règle générale.
La réponse XML du serveur ressemble à cela :
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema"> <SOAP-ENV:Body> <ns1:getPriceResponse xmlns:ns1="urn:xmethods-BNPriceCheck" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <return xsi:type="xsd:float">14.65</return> </ns1:getPriceResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
L’enveloppe est assez identique à celle de la requête, vous remarquerez cependant que le serveur utilise un schéma XML plus ancien que celui du client. Le corps est aussi similaire : l’espace de nommage de la méthode et le style d’encodage sont les mêmes. La balise du package ns1 comporte maintenant la Response accollée à son nom : <ns1:getPriceResponse>. Et là où la requête avait un élément appelé isbn, ici le coeur de la réponse est appelé return, et le type de donnée est spécifié en tant que float. PHP est un langage faiblement typé, donc NuSOAP assigne les types de variable automatiquement.
Conclusion
NuSOAP rend le travail avec SOAP très simple en gérant automatiquement la complexité, bien qu’il fournisse pas mal d’accès à sa flexibilité et ses nuances cachées. La méthode d’appel de la classe soapclient et la méthode d’enregistrement de la classe soap_server font la plupart des travaux que beaucoup d’autres implementations de SOAP vous font faire manuellement. NuSOAP offre quelques accès aux sous-couches maintenant et en autorisera encore plus au fur et à mesure que les développements progresseront.
Pour en apprendre plus sur les détails de fonctionnement de SOAP, référez-vous aux Spécifications SOAP et à la documentation de l’API qui accompagne NuSOAP. Si vous vous posez une question spécifique sur la manière dont NuSOAP gère les transactions SOAP, il peut être utile de jeter un oeil au fichier nusoap.php, qui est clairement organisé en classe et décemment commenté. Aller à la source devrait permettre de répondre à la plupart des questions.

Textes originaux en anglais sur developer.apple.com : Using SOAP with PHP
Chargement
Commentaires récents