Das große kleine SOAP Webservice How-To

In den letzten Jahren meiner Tätigkeit als PHP-Freelancer habe ich immer wieder mit Entwicklern zusammen gearbeitet, die erstaunt waren, was die PHP eigene SOAP Implementierung so alles kann. Ja, sie staunten auch, was die Implementierung alles nicht kann. In diesem Artikel soll es allerdings erstmal darum gehen, wie man eine SOAP XML Client Anbindung sauber mit PHP umsetzt. Die Umsetzung wird objektorientiert sein und natürlich wird dieses kleine How-To auch als GitHub Repository verfügbar sein.

Als Basis für unsere Client Anbindung soll der frei zugängliche Webservice zur Überprüfung von ISBN Nummern her halten. Ihr sendet zehn- oder dreizehnstellige ISBN Nummern an den Service und dieser sagt Euch, ob die gesendeten Daten valide sind.

Fangen wir also zunächst mit den Grundlagen an. SOAP bedeutet ausgeschrieben einfach Simple Object Access Protocol. Ein simples Netzwerkprotokoll, welches dazu dient Daten auszutauschen. Hierzu wird XML verwendet, welches nach den festgelegten Regeln der W3C einzusetzen ist.

Die PHP SoapClient Klasse

Bereits seit PHP5 bringt PHP seine eigene SoapClient Klasse mit. Diese Klasse ist manchmal nicht verfügbar. Hier gilt es zu prüfen, ob die Soap Extension in der php.ini aktiviert ist. Hierzu in der php.ini einfach nach folgender Zeile suchen oder den Provider anschreiben, ob die Soap Extension aktiviert werden kann.

;extension=soap

Entfernt einfach das Semikolon zu Beginn der Zeile und speichert die php.ini. Danach muss der Server, auf dem PHP läuft, neu gestartet werden. Auf einer lokalen XAMPP Entwicklungsumgebung wäre das der Apache. Danach ist die PHP Soap Extension in PHP verfügbar.

Beginnen wir also mit der eigentlichen SoapClient Klasse.

<?php
declare(strict_types=1);
namespace MMNewmedia\Soap;

use SoapClient;

class IsbnSoapClient extends SoapClient
{
    public function __construct(string $wsdl, array $options = [])
    {
        $options = array_merge([
            'cache_wsdl' => WSLD_CACHE_NONE,
            'exceptions' => true,
            'soap_version' => SOAP_1_1,
            'trace' => true,
        ], $options);

        parent::__construct($wsdl, $options);
    }
}

Wir vererben direkt von der PHP eigenen SoapClient Klasse. Unser Soap Client hat allerdings Standard Optionen, die über den $options Parameter auch wieder überschrieben werden können.

Bei der Initialisierung wird unser Soap Client mit den Entwicklungs-Optionen gestartet. Die WSDL Datei wird nicht zwischengespeichert. Dies ergibt nur bedingt Sinn, da wir lediglich einen bereits fertig gestellten Service konsumieren. Wenn ihr aber vor habt einen eigenen Soap Server aufzusetzen, ergibt es durchaus Sinn diese Option zu nutzen, da sich die WSDL Datei während des Entwicklungsprozesses durchaus mal ändern kann. Weiterhin wird der Soap Client so eingestellt, dass er Exceptions im Falle eines auftretenden Fehlers wirft. Die zu verwendende Soap Version wird auf 1.1 festgelegt und das Tracing wird eingeschaltet, so dass wir im Zweifelsfall mit den Methoden __getLastRequest() und __getLastResponse() überprüfen können, wie das zuletzt gesendete XML und das zuletzt empfangene XML aussehen.

Funktionen und Typen

In diesem Schritt geht es um die Funktionen und Typen des ISBN Webservices. Es ist elementar wichtig die bereitgestellten Funktionen des Webservices zu kennen. Hierzu bedient sich der PHP SoapClient an der angegebenen WSDL Datei und kann die Webservice Funktionen mittels __getFunctions() als Array zurückgeben. In diesem Array sind sowohl die Funktionen als auch die Parameter für die Funktionen benannt.


namespace Marcel;
use MMNewmedia\Soap\IsbnSoapClient;

$wsdl = 'http://webservices.daehosting.com/services/isbnservice.wso?WSDL';
$client = new IsbnSoapCLient($wsdl);

var_dump($client->__getFunctions());

Mit unserem Soap Client können wir im oben gezeigten Beispiel die Funktionen ermitteln. Das Ergebnis sollte dann wie folgt aussehen.

array(4) {
  [0]=>
  string(62) "IsValidISBN13Response IsValidISBN13(IsValidISBN13 $parameters)"
  [1]=>
  string(62) "IsValidISBN10Response IsValidISBN10(IsValidISBN10 $parameters)"
}

Somit wissen wir, dass unser Webservice zwei Funktionen IsValidISBN13 und IsValidISBN10 besitzt, die mit den entsprechenden Parametern aufgerufen werden können. Der PHP Soap Client kann die hier aufgelisteten Funktionen wie eigene Methoden aufrufen. Jetzt müssen für nur noch ermitteln, wie genau die Parameter IsValidISBN13 und IsValidISBN10 aussehen. Glücklicherweise hält der PHP Soap Client auch hierfür eine entsprechende Funktion bereit. Mit __getTypes() können wir sämtliche im Webservice verwendete Typen ermitteln.


namespace Marcel;
use MMNewmedia\Soap\IsbnSoapClient;

$wsdl = 'http://webservices.daehosting.com/services/isbnservice.wso?WSDL';
$client = new IsbnSoapCLient($wsdl);

var_dump($client->__getTypes());

Das Ergebnis aus diesem Funktionsaufruf sollte wie folgt aussehen.

array(4) {
  [0]=>
  string(39) "struct IsValidISBN13 {
 string sISBN;
}"
  [1]=>
  string(62) "struct IsValidISBN13Response {
 boolean IsValidISBN13Result;
}"
  [2]=>
  string(39) "struct IsValidISBN10 {
 string sISBN;
}"
  [3]=>
  string(62) "struct IsValidISBN10Response {
 boolean IsValidISBN10Result;
}"
}

Dieses Ergebnis lässt sich wie folgt interpretieren: Für den gesamten Webservice existieren vier Structs. Ein Struct kann als PHP Value Object interpretiert werden. Also eine einfache Klasse, die Daten hält. Die beiden Structs IsValidISBN13 und IsValidISBN10 werden, wie zuvor ermittelt, als Parameter für die Webservice Funktionen IsValidISBN13 und IsValidISBN10 benötigt. Beim Ermitteln der Webservice Funktionen haben wir auch die beiden Return Types IsValidISBN13Response und IsValidISBN10Response gesehen, die die entsprechenden Responses der Funktionsaufrufe darstellen.

Daraus ergeben sich folgende PHP Value Objects. Exemplarisch habe ich diese mal lediglich für die Webservice Funktion IsValidISBN13 hier dargestellt.

<?php
declare(strict_types=1);
namespace MMNewmedia\Entity;

class IsValidISBN13
{   
    protected string $sISBN;
    
    public function getIsbn(): string
    {
        return $this->sISBN;
    }
    
    public function setIsbn(string $sIsbn): self
    {
        $this->sISBN = $sIsbn;
        return $this;
    }
}

Das entsprechende Gegenstück dazu ist das Vaue Object für die Response.

<?php
declare(strict_types=1);
namespace MMNewmedia\Entity;

class IsValidISBN13Response implements JsonSerializable
{
    protected ?bool $IsValidISBN13Result;
    
    public function getIsValidISBN13Result(): ?bool
    {
        return $this->IsValidISBN13Result;
    }
    
    public function setIsValidISBN13Result(bool $isValidISBN13Result): self
    {
        $this->IsValidISBN13Result = $isValidISBN13Result;
        return $this;
    }
}

Diese beiden Value Objects entsprechen den über den Webservice ermittelnten Typen und können somit für den Webservice verwendet werden. Für die Typen IsValidISBN10 und IsValidISBN10Response müssen ebenfalls als entsprechende PHP Value Objects angelegt werden. Sofern dies getan ist, kommen wir zum nächsten Schritt.

Die Classmap des Soap Clients

Da wir nun Funktionen und Typen des Webservices kennen und daraus unsere Value Objects erstellt haben, können wir eine weitere Option des PHP Soap Clients nutzen. Die classmap Option der SoapClient Klasse ermöglicht es, dass unsere PHP Value Objects für die entsprechenden Typen des Webservices genutzt werden. Der Client ist somit in der Lage aus einem Objekt die entsprechende XML Struktur zu erstellen. Anders herum ist er auch in der Lage aus einer XML Response das entsprechende PHP Objekt zu bilden.

Diese Option ist mit Vorsicht zu genießen. Bei einfachen Webservices wie diesem funktioniert die Zuweisung von einfachen PHP Value Objects zu den in der WSDL Datei deklarierten Typen reibungslos. Bei aufwendigeren Datenstrukturen, die über die WSDL Datei oder in den darin manchmal verlinkten XSD Dateien beschrieben sind, wird dies nicht mehr funktionieren. In diesem Fall empfehle ich sowohl die Eigenschaften als auch die Objekte als SoapVar Objekte an den Soap Client zu übergeben. Dazu wird es aber in einem später folgenden Blog Artikel mehr geben.

Erweitern wir also unseren Soap Client um die Classmap Option.

<?php
declare(strict_types=1);
namespace MMNewmedia\Soap;

use MMNewmedia\Entity\IsValidISBN10;
use MMNewmedia\Entity\IsValidISBN10Response;
use MMNewmedia\Entity\IsValidISBN13;
use MMNewmedia\Entity\IsValidISBN13Response;
use SoapClient;

class IsbnSoapClient extends SoapClient
{
    public function __construct(string $wsdl, array $options = [])
    {
        $options = array_merge([
            'cache_wsdl' => WSLD_CACHE_NONE,
            'classmap' => [
                'IsValidISBN13' => IsValidISBN13::class,
                'IsValidISBN13Response' => IsValidISBN13Response::class,
                'IsValidISBN10' => IsValidISBN10::class,
                'IsValidISBN10Response' => IsValidISBN10Response::class,
            ],
            'exceptions' => true,
            'soap_version' => SOAP_1_1,
            'trace' => true,
        ], $options);

        parent::__construct($wsdl, $options);
    }
}

Im gezeigten Code Beispiel weisen wir den ermittelten Typen die entsprechenden Klassennamen der PHP Value Objects zu. Im Grunde genommen sind wir jetzt fertig. Unser SoapClient kann nun alles, was wir für diesen einfachen Webservice benötigen.

Der Webservice in Aktion

Was macht das oben beschriebene nun wirklich? Schauen wir uns das ganze mal in Aktion an.

<?php
namespace Marcel;
use MMNewmedia\Entity\IsValidISBN13;
use MMNewmedia\Soap\IsbnSoapClient;

$wsdl = 'http://webservices.daehosting.com/services/isbnservice.wso?WSDL';
$client = new IsbnSoapCLient($wsdl);

$isValidISBN13 = (new IsValidISBN13())
    ->setISBN('9783864906466');

$response = $client->IsValidISBN13($isValidISBN13);

var_dump(
    $client->__getLastRequest(), 
    $client->__getLastResponse(),
    $response
);

In diesem Beispiel benutzen wir die IsValidISBN13 Webservice Funktion. Wie zuvor schon beschrieben, kann diese Funktion wie eine klasseneigene Methode des Soap Clients aufgerufen werden. Wir senden eine valide 13-stellige ISBN an den Webservice. Die Erwartungshaltung ist natürlich, dass diese auch valide ist. Würde diese Bindestriche enthalten, wäre das erwartete Ergebnis nicht valide.

Die Ergebnisse des Aufrufs sehen wie folgt aus.

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://webservices.daehosting.com/ISBN">
    <SOAP-ENV:Body>
        <ns1:IsValidISBN13>
            <ns1:sISBN>9783864906466</ns1:sISBN>
        </ns1:IsValidISBN13>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <m:IsValidISBN13Response xmlns:m="http://webservices.daehosting.com/ISBN">
            <m:IsValidISBN13Result>true</m:IsValidISBN13Result>
        </m:IsValidISBN13Response>
    </soap:Body>
</soap:Envelope>
object(MMNewmedia\Entity\IsValidISBN13Response)#3 (1) {
  ["IsValidISBN13Result":protected]=>
  bool(true)
}

Zunächst hat unser Soap Client aus dem IsValidISBN13 Objekt eine valide XML Struktur für das Request erzeugt. Das erzeugte XML wird dann an den Webservice gesendet. Die erhaltene XML Response ist valide und sagt, dass die gesendete ISBN valide ist. Unser Soap Client wandelt die erhaltene XML Response wegen unserer Classmap automatisch in die für den Response-Typen angegebene Klasse um. Das Objekt IsValidISBN13Response enthält also die empfangenen Daten aus dem XML. Mit dem Objekt können wir dann ganz elegant weiter arbeiten.

Fazit

Wie ihr also seht, ist der PHP eigene Soap Client schon ganz schön gut aufgestellt. Die Zeiten, in denen man zusätzliche Bibliotheken zum Konsum von Webservices benötigte, sind einfach vorbei. Allerdings ist die PHP Dokumentation zum Thema SOAP nach wie vor ein wenig uneindeutig bis mangelhaft. Ohne grundlegendes Know How, wie ein SOAP Webservice funktioniert und welche Inhalte in einer WSDL Datei enthalten sind, wird es oft schwer bis unmöglich einen SOAP XML Webservice sauber aufzustellen.

Das Git Repository

Natürlich gibt es auch ein komplettes Repository, welches ihr Euch über Git herunter laden könnt. Hier geht es zum Repository. Fühlt Euch frei zu kommentieren, Änderungen vorzuschlagen oder Kritik zu üben.

4 Gedanken zu „Das große kleine SOAP Webservice How-To“

Kommentar verfassen

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.