• PHP5 - Einstieg in SOAP

    1. Einleitung

    Ich will euch mit diesem Tutorial zeigen, wie einfach es doch ist mit SOAP bestimmte Informationen zu bekommen. Durch PHP5 wurde es ziemlich erleichtert. Ich werde mit euch einen Clienten erstellen, der auf einen SOAP-Server zugreift und seine Daten sendet und wieder welche empfängt. Dazu will ich euch auch ein bekanntes Beispiel zeigen. Jeder von euch hat bestimmt schonmal von BabelFish gehört, dem Übersetzungstool von Altavista. Dies werden wir mit SOAP ansprechen und somit unseren eigenen kleinen Übersetzer bauen.
    Ausserdem werde ich euch erklären, wie ihr euren eigenen SOAP-Server der Aussenwelt zur Verfügung stellt unter der Verwendung von WSDL-Dateien. Erklärung gibt es dazu noch später.


    2. Was ist SOAP?

    Ausgesprochen heisst es "Simple Object Access Protocol" und meiner Meinung nach kann man mit SOAP einfache Kommunikation zwischen Servern durchführen. Von einer simplen Abfrage von Daten bis zur Fernsteuerung des Computers ist alles möglich.
    Die Daten, welche SOAP erwartet und zurückgibt, sind in XML geschrieben. Aber wenn man die Fkt. von PHP5 benutzt sollte dies eigentlich nicht interessieren.


    3. Hinweis für dieses Tutorial

    Vorraussetzung für dieses Tutorial ist, dass man PHP5 installiert hat.

    Für Linux-Nutzer: Ihr müsst SOAP mit kompiliert haben. Aber leider bin ich kein Linux-Nutzer, um genauere Informationen zu geben.

    Für Windows-Nutzer: Man muss eigentlich nur noch in der php.ini diese SOAP-Extension aktivieren ( "extension=php_soap.dll")
    Nun hat man alles, was man dafür braucht, um mit SOAP loszulegen.


    4. Das erste Skript

    Ich versuche euch nun anhand von BabelFish zu erklären, wie man leicht einen Clienten schreibt. Es gibt Dateien, in der zB. definiert ist, welche Fkt. über SOAP ansprechbar sind. Diese Dateien tragen meist die Endung WSDL. Sie benutzen die Scriptsprache XML und definieren alles, um es euch zu erleichtern, diesen Service zu benutzen. Die WSDL-Datei für BabelFish findet ihr unter http://www.xmethods.net/sd/2001/BabelFishService.wsdl
    Am Anfang sieht es ein wenig kryptisch aus, aber die Erklärung kommt später, wenn wir unseren eigenen SOAP-Server basteln.

    Fangen wir nun an. Wir wollen ein kleines Skript schreiben, welches "Hallo Welt" in das Englische übersetzt.

    Das erste was wir machen müssen ist ein SoapClient-Objekt zu erstellen
    PHP-Code:
    <?php
    $client 
    = new SoapClient('http://www.xmethods.net/sd/2001/BabelFishService.wsdl');
    ?>
    Wie ihr seht brauchen wir nun wieder diese Definitionsdatei, um dem Objekt $client zu sagen, welche Funktionen es haben wird. Der SOAP-Server von BabelFish hat lediglich nur eine Fkt. namens "BabelFish". Später, wenn wir unseren eigenen Server skripten werdet ihr merken, wie ihr das aus dieser WSDL-Datei auslesen könnt.

    Nun müssen wir nur noch die Anfrage senden und den Rückgabewert, unseren englischen Text, ausgeben.
    PHP-Code:
    <?php
    $result 
    $client->BabelFish('de_en''Hallo Welt');
    echo 
    $result;
    ?>
    Der erste Parameter gibt der Funktion an, welche Sprache ihr in Welche übersetzen wollt. Also in unserem Fall wollen wir ja das Deutsche ins Englische übersetzen. Der zweite Parameter ist der zu übersetzende Text. Richtig heissen die Parameter "translationmode" und "sourcedata". Wieder erkennt man, dass diese Namen auch in der Definitionsdatei auftauchen (solange man diese WSDL-Datei noch offen hat).
    Somit haben wir schon unser erstes SOAP-Skript geschrieben. Ist doch garnicht mal so schwer

    5. Das zweite Skript

    Der Nachteil des ersten Skriptes ist, dass SOAP diese WSDL-Datei komplett vom Server lädt und das bei großen Datenmengen ziemlich in die Performance geht. Man kann dies auch anders bewerkstelligen.
    Ich zeige euch das wieder an dem Beispiel von BabelFish.

    Als erstes müssen wir wieder ein SoapClient-Objekt erstellen. Diesmal aber nicht über die Definitionsdatei, sondern direkt an dem Skript, was uns die Übersetzung liefern soll.
    PHP-Code:
    <?php
    $client 
    = new SoapClient(NULL
    array( 
    "location" => "http://services.xmethods.net/perl/soaplite.cgi",      //Dieses Skript übersetzt uns unseren Text
    "uri" => "urn:xmethodsBabelFish",                 //Ein Namespace
    "style" => SOAP_RPC,                               //Art der Handhabung, hier Methodenaufruf (Remote Procedure Call)
    "use" => SOAP_ENCODED                     //Verschlüsselte Übertragung
    ));
    ?>
    Also wenn ihr nun von eurem Geschäftspartner nur die Skript-Adresse und die zugehörigen Funktionen bekommt, welche ein SOAP-Server freigegeben hat, müsst ihr das so bewerkstelligen.
    Nun wollen wir ja auch noch unseren Text senden und das Übersetzte empfangen.
    PHP-Code:
    <?php
    $parameters 
    = array(                 // Ein Array mit den Paramtern für die Funktion 'BabelFish'
    new SoapParam('de_en''translationmode'),    // erster Parameter SoapParam('Wert', 'Variablenname')
    new SoapParam('Hallo Welt''sourcedata'));    //zweiter Paramter

    $result $client->__call(  
    "BabelFish",                         //Die Methode, die ihr aufrufen wollt
    $parameters,                        //Paramter, die die Funktion verlangt
    array(                             //Optionen für SOAP
    "uri" => "urn:xmethodsBabelFish",             //wieder dieses Namespace
    "soapaction" => "urn:xmethodsBabelFish#BabelFish"     //keine Ahnung muss aber rein
    ));

    echo 
    $result;
    ?>
    Ja es sieht verwirrend aus. Das Wichtigste was ihr beachten müsst ist, dass die Parameter in einem Array übergeben werden.


    6. Der erste Server

    Nun wollen vielleicht paar von euch auch einen SOAP-Server aufbauen, weil man ja auch mit SOAP remoting betreiben kann. Um einen Server zu erstellen, müsst ihr einfach ein SoapServer-Objekt erstellen (wer hätte das gedacht )

    Wir wollen am Anfang ein kleinen Server erstellen, der eine Fkt. hat. Diese addiert einfach nur die 2 Paramter.

    server.php:
    PHP-Code:
    <?php
    function  addiere($sum1$sum2) {
        return 
    $sum1 $sum2;
    }

    $server = new SoapServer(NULL,
     array(
    'uri' => "http://{uri}/"));                //{uri} müsst ihr ersetzen mit den pfad 
    $server->addFunction('addiere');            //Funktion zum Server hinzufügen
    $server->handle();                     //Hier wird die Abfrage abgearbeitet
    ?>
    Die Fkt. 'handle' muss bei einem Server immer dabei sein, damit eure Abfragen, die ihr an den Server schickt auch abgeabeitet werden.

    Der Client sieht nun folgendermassen aus.
    client.php
    PHP-Code:
    <?php
    $client 
    = new SoapClient(NULL
    array( 
    "location" => "http://{url}/server2.php",          //{url} müsst ihr durch den Pfad ersetzen
    "uri" => "urn:xmethodsTestServer",
    "style" => SOAP_RPC
    "use" => SOAP_ENCODED
    ));

    $parameters = array(
    new 
    SoapParam('10''sum1'),
    new 
    SoapParam('20''sum2'));

    $result $client->__call(  
    "addiere"
    $parameters,
    array(
    "uri" => "urn:xmethodsTestServer",             
    "soapaction" => "urn:xmethodsTestServer#addiere"     //irgendein Platzhalter
    ));

    echo 
    $result;
    ?>
    War ja eigentlich ein Kinderspiel

    7. Aufbau einer WSDL-Datei

    Nun wollt ihr aber bestimmt nicht immer soviel eingeben, nur um 2 Zahlen zu addieren. Deswegen widmen wir uns nun der Definitionsdatei. Am Anfang wird es jedem ein wenig verwirrend vorkommen, aber ich hoffe, dass ihr nach dieser Erklärung auch die Definitionsdatei von BabelFish versteht.

    Die WSDL-Datei von BabelFish fängt damit an.

    BabelFishService.wsdl:
    Code :
    1
    2
    3
    4
    5
    6
    
    <?xml version="1.0"?>
    <definitions name="BabelFishService"                        //Name der Definition
    xmlns:tns="http://www.xmethods.net/sd/BabelFishService.wsdl"            //der Pfad der WSDL-Datei
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns="http://schemas.xmlsoap.org/wsdl/">
    Der Rest, was ich nicht kommentiert habe, sind die Standards für die WSDL-Sprache. Also dies muss auf jeden Fall immer am Anfang hin, damit SOAP auch was damit anfangen kann. Der Name ist auch frei wählbar. Er hat nichts entscheidentes zu sagen, so dass ihr nichts falsch machen könnt.

    Nun kommen wir zu einem wichtigen Teil dieser Datei.
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
        <message name="BabelFishRequest">
            <part name="translationmode" type="xsd:string"/>
            <part name="sourcedata" type="xsd:string"/>
        </message>
        <message name="BabelFishResponse">
            <part name="return" type="xsd:string"/>
        </message>
        <portType name="BabelFishPortType">
            <operation name="BabelFish">
                <input message="tns:BabelFishRequest" />
                <output message="tns:BabelFishResponse" />
            </operation>
        </portType>
    Wir widmen uns erstmal den Zeilen, wo portType steht. Da wird festgelegt, welche Funktion welche "Message" bekommt. Also in unserem Beispiel bekommt die Operation "BabelFish" eine Input Message und eine Output Message zugeschrieben. Als Operation wird die eigentlich Funktion bezeichnet, also "BabelFish". Auf Deutsch heisst das, dass hier festgelegt wird, dass die Message "BabelFishRequest" der Funktionsaufruf an den Server ist (Eingangsnachricht) und die Message "BabelFishResponse" den Rückgabewert darstellt (Ausgangsnachricht). Nun zu den Message-Zeilen darüber. Dort werden die einzelnen Nachrichten definiert. Mit der Eigenschaft "name" wird der Name für diese Nachricht festgelegt.
    Die Zeile mit dem "part" bestimmt die Variablen, die übergeben werden.
    Code :
    1
    2
    3
    4
    
        <message name="BabelFishRequest">
            <part name="translationmode" type="xsd:string"/>
            <part name="sourcedata" type="xsd:string"/>
        </message>
    Die Nachricht heisst "BabelFishRequest", also unsere eigendliche Funktion, mit den Paramtern "translationmode" und "sourcedata" vom type string. Somit weiß SOAP wie die Funktion aussehen soll.
    Code :
    1
    2
    3
    
        <message name="BabelFishResponse">
            <part name="return" type="xsd:string"/>
        </message>
    Dies ist die Rückgabefunktion. Hier ist erkenntlich, dass man ein Rückgabewert hat, der vom Typ string ist. Somit haben wir schonmal die Variablen und die Funktionen definiert.
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
        <binding name="BabelFishBinding" type="tns:BabelFishPortType">
            <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
            <operation name="BabelFish">
                <soap:operation soapAction="urn:xmethodsBabelFish#BabelFish"/>
                <input>
                    <soap:body use="encoded" namespace="urn:xmethodsBabelFish" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
                </input>
                <output>
                    <soap:body use="encoded" namespace="urn:xmethodsBabelFish" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
                </output>
            </operation>
        </binding>
    Hier wird die Methode festgelegt, inwieweit der Client mit dem Server kommuniziert und was für eine Verschlüsselung genommen wird. Die Anpassung kommt dann später, wenn wir für unseren Test-Server solch eine Datei schreiben. Dieser Teil ist auch nicht so kompliziert, wie ihr später an unser eigenen WSDL-Datei sehen werdet.

    Nun fehlt nur noch das Wissen, mit welche Datei kommunizieren muss. Dies wird im Service-Tag festgelegt .
    Code :
    1
    2
    3
    4
    5
    6
    7
    
        <service name="BabelFishService">
            <documentation>Translates text of up to 5k in length, between a variety of languages.</documentation>
            <port name="BabelFishPort" binding="tns:BabelFishBinding">
                <soap:address location="http://services.xmethods.net:80/perl/soaplite.cgi"/>
            </port>
        </service>
    </definitions>
    Zwischen den Documentation-Tags könnt ihr irgendwelche nützlichen Hinweise reinschreiben. Sozusagen das Kommentar vom Verfasser des Serverskriptes. Wichtig ist aber auf jedenfall die Zeile mit "<soap:address location...". Dort wird nun die eigentliche Serverdatei festgelegt. Nun weiß SOAP auch, wohin es die Anfrage schicken muss.

    So ich hoffe nun weiß jeder, wie er solch eine Datei lesen muss. Es komm vielleicht noch ein wenig kompliziert vor, aber wenn man sich mehrere Dateien angeschaut hat kommt man schnell hinter das Prinzip.

    8. Die eigene WSDL-Datei

    Nun wollen wir für unseren eigenen Server auch so eine Datei erstellen, damit wir uns das arbeiten erleichtern können und somit auch den Server nach aussen hin benutzerfreundlicher machen.

    testserver.wsdl:
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    
    <?xml version ='1.0' encoding ='UTF-8' ?> 
    <definitions name='TestServer' 
      xmlns:tns=' [url]http://example.com/testserver.wsdl[/url] ' 
      xmlns:soap='http://schemas.xmlsoap.org/wsdl/soap/' 
      xmlns:xsd='http://www.w3.org/2001/XMLSchema' 
      xmlns:soapenc='http://schemas.xmlsoap.org/soap/encoding/' 
      xmlns:wsdl='http://schemas.xmlsoap.org/wsdl/' 
      xmlns='http://schemas.xmlsoap.org/wsdl/'> 
     
    <message name='addiereAnfrage'> 
      <part name='sum1' type='xsd:float'/>
      <part name='sum2' type='xsd:float'/>
    </message> 
    <message name='addiereAntwort'> 
      <part name='Result' type='xsd:float'/> 
    </message> 
     
    <portType name='TestServerPortType'> 
      <operation name='addiere'> 
        <input message='tns:addiereAnfrage'/> 
        <output message='tns:addiereAntwort'/> 
      </operation> 
    </portType> 
     
    <binding name='TestServerBinding' type='tns:TestServerPortType'> 
      <soap:binding style='rpc' 
        transport='http://schemas.xmlsoap.org/soap/http'/> 
      <operation name='addiere'> 
        <soap:operation soapAction='urn:xmethodsTestServer#addiere'/> 
        <input> 
          <soap:body use='encoded' namespace='urn:xmethodsTestServer' 
            encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/> 
        </input> 
        <output> 
          <soap:body use='encoded' namespace='urn:xmethodsTestServer' 
            encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/> 
        </output> 
      </operation> 
    </binding> 
     
    <service name='TestServerService'> 
      <port name='TestServerPort' binding='TestServerBinding'> 
        <soap:address location='http://{url}/server.php'/> 
      </port> 
    </service> 
    </definitions>
    Der {url}-Teil muss natürlich wieder ersetzt werden.

    Wer die Erklärung oben verstanden hat wird auch recht schnell die Veränderungen sehen und verstehen. Nun müssen wir nur noch unseren clienten auf diese Datei anpassen

    client.php:
    PHP-Code:
    <?php
    $client 
    = new SoapClient('http://{url}/testserver.wsdl');  //{url} wie immer ersetzen

    $result $client->addiere(1020);

    echo 
    $result;
    ?>
    9. Fehlerbehandlung in SOAP

    Zu guter Letzt schreibe ich noch was zur Fehlerhandhabung.
    Nehmen wir mal ein Beispiel. Du stellst ein Skript als SOAP-Server zu Verfüguung, welches in einer Datenbank nach Artikeln sucht. Nun gibt der Client aber keine Artikelbeschreibung an, wonach es dir nicht möglich ist, diese Abfrage durchzuführen. Dafür stellt SOAP extra ein Errorhandler bereit, wo man Errorcodes zurück an den Clienten schicken kann.
    Bauen wir es am Besten gleich mal in unseren TestServer ein:
    PHP-Code:
    <?php
    function  addiere($sum1$sum2) {
        if(isset(
    $sum1) && isset($sum2)) 
            return 
    $sum1 $sum2;
        else
            return new 
    SoapFault('Client''Eingabe nicht korrekt''server.php''');
    }
    ?>
    Schauen wir uns zuerst die Klasse SoapFault an.
    Der erste Parameter ist der Fehler Code. Allgemein wird entweder 'Client' oder 'Server' benutzt. Client bedeutet dann soviel, dass der Fehler beim Clienten lag und Server demzufolge beim Server. Ihr könnt aber auch andere FehlerCodes nehmen.
    Der zweite Parameter beinhaltet die Beschreibung des Fehlers.
    Der dritte Parameter beinhaltet den Auslöser, also die Datei. Dieser Parameter ist optional.
    Den vierten Paramter kann man auch nutzen, um weitere Informationen an den Clienten zu schicken. Er besitzt den Typ Mixed, also kann er alle möglichen Typen annehmen. Dieser Parameter ist ebenso optional.

    Nun muss der Client aber darauf auch reagieren können. Dafür besitzt SOAP die Fkt. is_soap_fault(). Dabei ist zu beachten, dass die Option "exceptions" mit dem Wert 0 bei der Objekterstellung übergeben wird, weil sonst ein richtiger Fehler von SOAP erzeugt wird und man somit keine benutzerdefinierte Ausgabe vom Fehler machen kann.

    client.php:
    PHP-Code:
    <?php
    $client 
    = new SoapClient('http://{url}/testserver.wsdl', array('exceptions' => 0));   //{url} wie immer ersetzen; Option 'exceptions' wir mit Wert 0 übergeben

    $result $client->addiere(1020);

    if(
    is_soap_fault($result)) {
        echo 
    "FehlerCode: "$result->faultcode"\n";
        echo 
    "Beschreibung: "$result->faultstring"\n";
        echo 
    "Sender: "$result->faultactor"\n";
    } else {
        echo 
    $result;
    }
    ?>
    So das wars schon. Wie ihr seht gibt die Klasse SoapFault ein Objekt zurück.



    So das war mein Tutorial und ich hoffe ich konnte euch SOAP in den Grundzügen erklären. Weitere Infos gibts natürlich auf www.php.net und weitere WSDL-Dateien findet ihr unter www.xmethods.net
     


     
    Kommentare 2 Kommentare
    1. Avatar von BrainLight
      BrainLight -
      Hallo KoMtuR.

      Auch wenn dieses Tutorial "etwas" älter ist, widme ich mich gerade diesem.
      Vielen Dank für dieses im Voraus.

      Ich habe gerade versucht das Tutorial direkt via OOP umzusetzen, doch leider stoße ich bei Punkt 6 an die Grenzen meines Verständnisses in der Materie SOAP.

      In dem Tutorial wird auf einmal schlagartig von der Datei server2.php geschrieben. Wo kommt diese her?

      Grüße
      BL
    1. Avatar von hariboaner
      hariboaner -
      Hallo,

      das war wohl ein (beabsichtigter?) Schreibfehler. Es muss der komplette Pfad der Serverdatei drinstehen, also in diesem Fall wäre es http://{url}/server.php

      Gruß
      hariboaner