jeudi 21 mai 2009

Créer un WSDL facilement avec Zend Framework

Par le passé, je dois avouer avoir fait une tentative, une seule, pour créer à la main un fichier WSDL (Web Services Description Language) et je me suis promis que plus jamais je ne voulais revivre cette expérience. Certains outils existent pour automatiser cette pénible tâche et ceux que j'avais testé jusqu'à maintenant généraient un fichier .wsdl statique.

Pour expliquer simplement, un fichier WSDL comprend une structure XML qui définit les fonctions accessibles par le service, ses paramètres, les types et ce qui est retourné. Lorsque le comportement d'une des fonctions du service web change, on est obligé soit de modifier à la main la description, soit de regénérer le fichier WSDL et le redéposer sur le serveur.

Jusqu'à ce qu'on découvre l'objet Zend_Soap_AutoDiscover du Zend Framework. Sa force : être en mesure d'inspecter une classe au runtime et de générer à la volée le fichier WSDL correspondant.

Voici un petit tutoriel pour créer un premier service web, réduit à sa plus simple expression. J'offrirai le service de retourner le pourcentage de taxe associé au nom envoyé par le client.

1. Créer une classe qui offrira les services

Ici, ça peut être n'importe quelle classe, pas nécessairement une construite spécifiquement pour le service web. Une seule condition doit être respectée : suivre le format de documentation des docblocks car AutoDiscover se base sur commentaires pour construire la description WSDL.

Chaque fonction de la classe doit être précédée d'un docblock qui doit suivre la syntaxe suivante :
  • @param type param_name
  • @return type
Comme dans l'exemple ci-dessous :
// Ex: fichier "tax.class.php"

class Tax {

/**
* Return tax value
* @param string $type
* @return float
*/
public function getTaxValue($type = 'TPS'){
// récupérer les taxes d'une quelconque façon,
// par exemple d'une base de données

// Taxe sur les Produits et Services
if($type == 'TPS'){
return 5.0;
}
// Taxe de Vente du Québec
elseif ($type == 'TVQ'){
return 7.5;
}
else {
return 0;
}

}
}
Si vous rencontrez l'erreur "Fatal error: Uncaught SoapFault exception: [WSDL] SOAP-ERROR: Parsing WSDL: Missing <message>...", c'est probablement que votre docblock est mal structuré. Le commentaire doit absolument débuter par /**, avec deux astérisques (dans Eclipse, vous devriez voir une différence dans la coloration syntaxique).

2. Manipuler les requêtes du côté serveur

Comme c'est vous qui offrez un service, vous devrez préparer une page qui effectuera les manipulations des requêtes clients. Ce fichier aura deux utilités :
  • retourner la description du WSDL lorsqu'un paramètre sera passé
  • traiter la demande
// Ex: fichier "service.php"

require_once('Zend/Soap/AutoDiscover.php');
require_once('Zend/Soap/Server.php');
require_once('tax.class.php');

if(isset($_GET['wsdl'])){
// inspecter la classe Tax et retourner la description
$wsdl = new Zend_Soap_AutoDiscover();
$wsdl->setClass('Tax');
$wsdl->handle();
}
else{
// traitement
$server = new Zend_Soap_Server('http://localhost/WSDL/service.php?wsdl');
$server->setClass('Tax');
$server->handle();
}
Si l'erreur suivante se produit "Fatal error: Uncaught exception 'Zend_Soap_Server_Exception' with message 'SOAP extension is not loaded.'", c'est que l'objet Zend_Soap_Server utilise l'extension php_soap.so (ou .dll). Activez la dans php.ini.

Vous êtes maintenant prêts à fournir les taux de taxes.

3. Créer une requête en tant que client

Probablement qu'en temps normal, ce ne sera pas vous qui utiliserez votre service web mais mieux vaut le tester avant de l'offrir à vos clients. Ceux-ci pourront utiliser le langage de leur choix pour faire appel à votre service, mais nous, comme nous utilisons déjà le Zend Framework, nous allons nous servir d'un autre objet au nom évocateur de Zend_Soap_Client. Cet objet peut aussi être utilisé lorsque vous voudrez vous-même utiliser un service offert par un autre fournisseur. Rappelez-vous que du point de vue du client, il ne voit pas que c'est programmé en PHP, ils ne voient que la définition WSDL pour pouvoir communiquer avec lui.

Une fois la connection établie, le client peut appeler les fonctions comme un objet. Ici, il aura accès à la fonction getTaxValue.
// Ex: fichier "appel.php"

require_once('Zend/Soap/Client.php');

try {
$client = new Zend_Soap_Client('http://localhost/WSDL/service.php?wsdl');

echo 'La TPS est de : ' . $client->getTaxValue('TPS') . ' %';
echo 'La TVQ est de : ' . $client->getTaxValue('TVQ') . ' %';
}
catch(Zend_Exception $e){
echo $e->getMessage();
}
Ce qui devrait afficher ceci :
La TPS est de : 5 %
La TVQ est de : 7.5 %

18 commentaires:

  1. excellent article.

    Par contre dans la partie client, il est inutile de faire un include tax.class.php. Cette class est seulement côté server.

    @++

    RépondreEffacer
  2. ça fonctionne nickel.
    C'est simple. C'est ludique.

    Je dis bravo et merci (je vais pouvoir aborder sereinement soap sur mon projet)

    Vlad

    RépondreEffacer
  3. Bonjour, lorsque je fais tourner votre code g un message d'erreur dont je n'arrives pas à identifier la cause. Auriez-vous une idée pour m'orienter?

    Merci.
    Claude
    ---------------> erreur:

    Exception information:

    Message: Wrong Version
    Stack trace:

    #0 /var/www/virgox/library/Zend/Soap/Client.php(1113): SoapClient->__soapCall('getTaxValue', Array, NULL, NULL, Array)
    #1 /var/www/virgox/application/modules/default/controllers/IndexController.php(35): Zend_Soap_Client->__call('getTaxValue', Array)
    #2 /var/www/virgox/application/modules/default/controllers/IndexController.php(35): Zend_Soap_Client->getTaxValue('TPS')
    #3 /var/www/virgox/library/Zend/Controller/Action.php(513): IndexController->cliAction()
    #4 /var/www/virgox/library/Zend/Controller/Dispatcher/Standard.php(289): Zend_Controller_Action->dispatch('cliAction')
    #5 /var/www/virgox/library/Zend/Controller/Front.php(954): Zend_Controller_Dispatcher_Standard->dispatch(Object(Zend_Controller_Request_Http), Object(Zend_Controller_Response_Http))
    #6 /var/www/virgox/library/Zend/Application/Bootstrap/Bootstrap.php(97): Zend_Controller_Front->dispatch()
    #7 /var/www/virgox/library/Zend/Application.php(366): Zend_Application_Bootstrap_Bootstrap->run()
    #8 /var/www/virgox/public/index.php(50): Zend_Application->run()
    #9 {main}

    Request Parameters:

    array (
    'controller' => 'index',
    'action' => 'cli',
    'module' => 'default',
    )

    RépondreEffacer
  4. ...suite...

    je pense que c'est lié au cache. G l'impression que les dernières modifs du wsdl lorsque je modifie la classe de service (Tax) ne sont pas prises immédiatement en compte. Le lendemain cela fonctionnait.

    Merci.
    Claude

    RépondreEffacer
  5. Tant mieux si c'était lié au cache, ça doit dépendre du setup de chaque serveur.

    RépondreEffacer
  6. Bonjour,

    le fichier service.php fait il partie de Zend ? Je ne le trouve nulle part et il n'est a priori pas expliqué. Hors tout le problème que je rencontre avec SOAP c'est la génération du fichier WSDL

    A+

    RépondreEffacer
  7. Le fichier service.php, c'est l'équivalent du contenu qui est expliqué au point 2.

    RépondreEffacer
  8. OK, je voulais absolument qu'il y ait un serveur.php...

    Merci pour la réponse, ça fonctionne.

    RépondreEffacer
  9. S'il vous plait,
    Est que je peux avoir les noms de chaque fichiers, et comment les utiliser
    Merci beaucoup pour votre aide

    RépondreEffacer
  10. Bien sûr. À titre d'exemple, j'ai ajouté des noms génériques : tax.class.php, service.php et appel.php.

    RépondreEffacer
  11. Bonjour,

    Ça fait maintenant un moment que j'utilise ce système (qui fonctionne très bien en PHP). Mais je suis confronté maintenant à un nouveau problème.

    Je tente de créer un client ASP (classic) mais le format WSDL de Zend_Soap_AutoDiscover n'est pas reconnu.

    En effet, le format du fichier WSDL pour l'ASP est différent du celui généré par Zend_Soap_AutoDiscover.

    exemple : http://ws.textanywhere.net/ta_SMS.asmx?wsdl

    Ma question est la suivant : avec Zend_Soap_AutoDiscover, peut-on créer ce genre de format ? pour que mon WebService PHP puisse etre lu par le PHP et l'ASP ??

    Merci.

    Olivier

    RépondreEffacer
  12. Salut Olivier, je n'ai jamais travaillé avec un client SOAP à partir d'ASP classique. Mais je soupçonne que c'est la version de SOAP qui cause problème. Le standard actuel est 1.2 (par défaut dans Zend mais tu peux le faire basculer à 1.1). Tu pourrais regarder comment est fait l'implémentation de la portion client en PHP et tenter de le reproduire en ASP.

    $url = 'http://abc.com/somservice.asmx?wsdl';
    $options = array("soap_version" => SOAP_1_1);
    $client = new Zend_Soap_Client($url,$options);

    Exemple tiré d'ici.

    RépondreEffacer
  13. Salut, Et merci pour ta réponse.

    Niveau Client, je sais qu'on peut spécifier la version. Mais comment déterminer la version de mon fichier WSDL ?

    Merci

    Olivier

    RépondreEffacer
  14. Bonjour

    Article très intéressant :)

    Petite question : vous utilisez un chemin relatif commencant par Zend/ dans vos includes. Soit! j'adapte mon chemin relatif en conséquence mais je tombe sur une erreur identique à l'intérieur même des classes du framework.

    Dois-je modifier l'ensemble des classes du framework comme je l'ai fait avec mon service ? ou y-a-t-il une astuce dont je n'aurais pas connaissance ?

    RépondreEffacer
  15. après moultes recherches ces dernières 48h, j'ai enfin trouvé la réponse à mon problème ...

    pour ceux qui se poseraient la même question que moi ("Il n'y a pas de question idiote, seulement une réponse idiote" Albert Einstein),
    voici la solution :

    Au début du document racine, ajouter cette instruction
    set_include_path('.' . PATH_SEPARATOR . 'E:/ZF/library' . PATH_SEPARATOR . get_include_path());

    A quoi cela correspond ?
    En fait, on inclue dans les chemins de recherche par défaut le répertoire courant ('.'), le répertoire contenant les classes Zend ('E:/ZF/library') et on rappelle les chemins par défaut déjà déclarés (get_include_path())

    En espérant avoir été clair pour les débutants Zend comme moi ;)

    RépondreEffacer
  16. Trop bon votre tutoriel. Depuis hier je me casse le tête sur zend pour produire un webservice. Mon problème est maintenant résolu. Merci !

    RépondreEffacer
  17. Salut Infinite. Très bon tuto. Mon soucis est la fameuse "SOAP-ERROR: Parsing WSDL". Le WSDL s'affiche bien. Je suis en phase de développement en local sous Debian. Que dois-je indiquer dans mon hosts?
    Voici ce que j'ai actuellement: 127.0.1.1 nom_domaine.com alias
    Pour atteindre le soap, je tape l'url:
    nom_domaine.com/webservice/soap.
    Est-ce correct ou dois-je ajouter quelque chose?

    RépondreEffacer