Webservice mit PHP

Kürzlich stand ich vor der Aufgabe einen SOAP Server mit PHP aufzusetzen. Nach längerer Recherche im Netz hat es letztendlich doch funktioniert, aber ich konnte nirgends ein vollständiges Tutorial finden, dass all meine Bedürfnisse abdeckte. Nachdem diese Schnitzeljagd nun schon zum wiederholten Male stattfand habe ich jetzt beschlossen selbst diese Informationen in gesammelter Form wiederzugeben.
Zunächst mal eine kurze Einführung für alle die in Besitz eines VServer oder Root-Server sind, wie man PHP 7 und einen Apache Server aufsetzt.
Installieren von PHP 7:
sudo apt-get install apache2
sudo apt-get install php7.0
Direkt danach ist der Server startklar. Man kann mit dem folgenden Skript ziemlich einfach testen ob die Installation erfolgreich war. Die Funktion phpinfo() gibt
Zum Test kann man zunächst noch ein Skript aufrufen das die installierten Funktionen ausgibt.
<?php
phpinfo();
?>
Soweit so gut. Die Installation hat funktioniert.
Nun erstellen wir unsere Schnittstellen Definition. Ich mache das Beispiel mit Absicht ein klein wenig komplexer um im voraus Fragen zum Mapping beantworten zu können.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions
name="HelloService"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://thomas-hoermann.de/ws/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://thomas-hoermann.de/ws/">
<wsdl:types>
<xsd:schema targetNamespace="http://thomas-hoermann.de/ws/">
<xsd:element name="authenticateRequest">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="username" type="xsd:string" />
<xsd:element name="password" type="xsd:string" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="authenticateResponse" type="xsd:string"></xsd:element>
<xsd:element name="getDataRequest">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="key" type="xsd:string" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="getDataResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="data1" type="xsd:string" />
<xsd:element name="data2" type="xsd:int" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
</wsdl:types>
<wsdl:message name="authenticateRequest">
<wsdl:part element="tns:authenticateRequest" name="request" />
</wsdl:message>
<wsdl:message name="authenticateResponse">
<wsdl:part element="tns:authenticateResponse" name="response" />
</wsdl:message>
<wsdl:message name="getDataRequest">
<wsdl:part element="tns:getDataRequest" name="request" />
</wsdl:message>
<wsdl:message name="getDataResponse">
<wsdl:part element="tns:getDataResponse" name="response" />
</wsdl:message>
<wsdl:portType name="HelloService">
<wsdl:operation name="authenticate">
<wsdl:input message="tns:authenticateRequest" />
<wsdl:output message="tns:authenticateResponse" />
</wsdl:operation>
<wsdl:operation name="getData">
<wsdl:input message="tns:getDataRequest" />
<wsdl:output message="tns:getDataResponse" />
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="HelloServiceSOAP" type="tns:HelloService">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="authenticate">
<soap:operation
soapAction="http://thomas-hoermann.de/ws/authenticate" />
<wsdl:input>
<soap:body use="literal" />
</wsdl:input>
<wsdl:output>
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="getData">
<soap:operation
soapAction="http://thomas-hoermann.de/ws/getData" />
<wsdl:input>
<soap:body use="literal" />
</wsdl:input>
<wsdl:output>
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="HelloService">
<wsdl:port binding="tns:HelloServiceSOAP"
name="HelloServiceSOAP">
<soap:address location="http://thomas-hoermann.de/ws/" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
Die Schnittstelle hat zwei Funktionen:
  • authenticate
  • getData
Diese binden wir nun in das PHP Script ein.
<?php
class ServiceClass
{
    public function authenticate($params)
    {
        $response = 'Username: '.$params->username.' Password: '.$params->password;
        return $response;
    }
    public function getData($params)
    {
        $result->data1 = $params->key;
        $result->data2 = 42;
        return $result;
    }
}
ini_set("soap.wsdl_cache_enabled", 0);
$soapserver = new SoapServer('hello.wsdl');
$soapserver->setClass('ServiceClass');
$soapserver->handle();
?>
Erster Versuch: Webservice aufgerufen.
500 Internal Server Error
Was ist passiert. Wsdl Falsch? PHP Skript fehlerhaft? Nein. Ein Blick in das Fehlerlog verrät die Antwort.
> cat /var/log/apache2/error.log
PHP Fatal error: Uncaught Error: Class 'SoapServer' not found in /var/www/html/soapServer.php:25\nStack trace:\n#0 {main}\n thrown in /var/www/html/soapServer.php on line 25
Das Modul SoapServer ist nicht installiert. Kurzerhand das Soap Modul für PHP installiert
sudo apt-get install php7.0-soap
sudo service apache2 restart
kurzer gegencheck in phpinfo(), SoapServer enabled.
Die WSDL wird vom Webservice angezeigt.
https://thomas-hoermann.de/ws/?wsdl
Dann geht es auch schon zum ersten Test per SoapUI. Ich erstelle ein neues Projekt mittels des oben genannten Links zur WSDL und es wird mir bereits ein Voraus gefülltes XML angezeigt.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://thomas-hoermann.de/ws/">
    <soapenv:Header/>
    <soapenv:Body>
        <ws:authenticateRequest>
            <username>me</username>
            <password>streng-geheim</password>
        </ws:authenticateRequest>
    </soapenv:Body>
</soapenv:Envelope>
Daraus erfolgt dann die Antwort
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://thomas-hoermann.de/ws/">
    <SOAP-ENV:Body>
        <ns1:authenticateResponse>Username: me Password: streng-geheim</ns1:authenticateResponse>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Somit ist mein Tutorial auch schon fertig. An dieser Stelle möchte ich noch auf ein paar Kleinigkeiten hinweisen die ich teilweise in anderen Einführungen vermisst habe. Das Mapping der Funktionsnamen funktioniert, wenn man die setClass Methode verwendet 1 zu 1. Der Parameter $params ist ein komplexes PHP Objekt, ausgehend von dem *Request Knoten. Die Kinder können über ‚->‘ angesprochen werden. Selbiges gilt auch für *Response.