Einen PHP-SOAP-Webservice erstellen und verwenden

Zum Erstellen eines SOAP-Webservices unter PHP gibt es einige Möglichkeiten. Eine davon wäre sicherlich bei Null zu beginnen und alles selbstständig zu programmieren. Eine andere Möglichkeit wiederrum ist die Verwendung von NuSoap; dies werde ich auch hier in diesem Beitrag tun. Was benötigt man zunächst einmal für einen NuSoap-PHP-Webservice? Nun zunächst einmal benötigt man einen Webspace auf dem PHP läuft und auch eine Datenbank wie z.B. MySQL ist an dieser Stelle hilfreich. Verwendet werden kann der Webservice anschließend in jeder SOAP-kompatiblen Programmiersprache. Ich werde hierfür C# verwenden.

Für den ersten Schritt sollten wir uns die Library für NuSoap herunterladen. Dies ist unter folgender URL möglich: http://sourceforge.net/projects/nusoap/ . Hier wird ausschließlich der „lib“-Ordner für die Referenzierung benötigt. Diesen packen wir auch direkt auf unseren Webserver, auf dem später der Webservice laufen soll.

Im nächsten Schritt erstellen wir unsere „index.php“-Datei. Diese bildet später den Einstiegspunkt für unseren Webservice. Bei mir wird diese Datei später hier erreichbar sein: https://kruse-familie.eu/soap/ (alle weitere Erklärungen werden auf dieser URL basieren). Diese füllen wir zunächst einmal mit den NuSoap-Referenzen.

<?php 
	require_once("lib/nusoap.php");
?>

An dieser Stelle noch als Tipp: Es ist hilfreich eine „index.php“-Datei auch im „lib“-Ordner zu erstellen. Diese könnte eine Umleitung auf den Soap-Webservice beinhalten.

<?php 
	header('Location: https://kruse-familie.eu/soap/');
	exit;
?>

Nun, wenn das erledigt ist, können wir mit der eigentlichen Definition unseres Webservices beginnen. Hierzu benötigen wir zum einen den Namespace unseres Webservices, den Namen sowie das Encoding sollten gesetzt werden. Dies geschieht mit den folgenden Zeilen Code:

	$namespace = "https://kruse-familie.eu/soap/index.php";
	$server = new soap_server();
	$server->soap_defencoding = 'UTF-8'; 

	$server->configureWSDL("NuSoapWebservice");
	$server->wsdl->schemaTargetNamespace = $namespace;

Damit ist eigentlich auch bereits das Grundlegende definiert. Im nächsten Schritt können wir bereits damit beginnen, unsere Datentypen sowie Methoden zu definieren. Bevor wir dies jedoch tun, sollten wir am Ende(!) unserer „index.php“-Datei noch die WSDL-Definition treffen, denn ohne funktioniert SOAP nicht. Dies erledigen wir mit den folgenden Zeilen:

	$POST_DATA = isset($GLOBALS['HTTP_RAW_POST_DATA']) 
					 ? $GLOBALS['HTTP_RAW_POST_DATA'] : '';
					
	$server->service($POST_DATA);                
	exit();

Wenn auch dies erledigt ist, können wir kontrollieren, was wir bereits sehen. Denn zu diesem Zeitpunkt müsste uns die „index.php“-Datei bereits mit einem Webservice ohne Inhalt begrüßen.

Anschließend müssen wir uns überlegen, wie wir unsere Datentypen und Methoden definieren wollen. Da es bei einem Webservice schnell dazu kommen kann, dass dieser sehr groß wird, bin ich persönlich ein Fan davon die weiteren Definitionen sauber in eigenen Dateien zu trennen. Hierfür habe ich den beiden Ordner „types“ sowie „functions“ angelegt. Diese beiden Ordner wurden jeweils mit einer Umleitungs-„index.php“-Datei ausgestattet und werden später die einzelnen Typen und Funktionen beinhalten. Bei den Typen wird hier zunächst ein Standard-Rückgabetyp definiert, da bei mir jede Methode eine Rückgabe tätigt. Der Standard-Rückgabetyp hat hierbei keine Werte in der Rückgabe, sondern nur Informationen darüber, ob die Webservice-Methode erfolgreich durchgeführt werden konnte, sowie etwaige Fehlermeldungen. Hierfür habe ich zunächst die Datei „WebServiceReturnData.php“ im „types“-Ordner angelegt. Diese sieht bei mir wie folgt aus:

<?php
	$server->wsdl->addComplexType('WebServiceReturnData', 'complexType', 'struct', 'all', '',  
				array('HasError' => array('name' => 'HasError', 'type' => 'xsd:boolean'), 
					  'UserErrorCode' => array('name' => 'UserErrorCode', 'type' => 'xsd:int'), 
					  'DetailedErrorMessage' => array('name' => 'DetailedErrorMessage', 'type' => 'xsd:string'), 
					  'HasDetailedErrorMessage' => array('name' => 'HasDetailedErrorMessage', 'type' => 'xsd:boolean')));	
?>

Wieder in der „index.php“-Datei angekommen, wird hier diese Datei mittels require_once eingebunden.

	require_once("types/WebServiceReturnData.php");

Wenn wir nun unseren Webservice aufrufen, sehen wir, dass bereits ein Typ in der WSDL-Datei definiert worden ist. Nun wie funktioniert die Typen-Definition? Wir gehen hierbei wie folgt vor: Wir rufen auf unserer Instanz des soap_server’s das Hinzufügen eines Typen zur WSDL-Datei auf. Dieser Typ bekommt zunächst einen Namen, in unserem Fall „WebServiceReturnData“. Nach dem Namen folgt ein Standard-Parameter für „normale“-Datentypen. Anschließend folgt ein Array der Inhalte unseres Datentypen. In diesem Fall beinhaltet unser Datentyp „WebServiceReturnData“ die Propertys „HasError“ vom Typ Boolean, „UserErrorCode“ vom Typ Integer, „DetailedErrorMessage“ vom Typ String und „HasDetailedErrorMessage“ vom Typ Boolean. Die Syntax  zur Definition bleibt hierbei immer gleich.

Nun habe ich „normale“-Datentypen erwähnt. Normal deshalb, weil es kein Array-Typ ist, denn diese werden anders definiert. Und das „normal“ ist in Anführungsstrichen, da es kein Standarddatentyp wie Integer, String oder Boolean ist. Die Standarddatentypen haben hierbei immer den Namespace „xsd:“ vorweg, wie oben an unseren Typen zu sehen ist. Wenn wir jetzt jedoch einen Typen hätten, der wiederrum unseren Typen inne hätte, hätte dieser das Präfix „tns:“ um sich so von den Vordefinierten zu unterscheiden.

Kommen wir aber zunächst zum Aufbau von Arrays. Diese sehen zwar etwas anders aus, sind aber im Prinzip nicht sonderlich schwer zu verstehen. Da in vielen Funktionalitäten typische String-Arrays sinnvoll und nützlich sind, werden wir uns einen solchen definieren. Hierzu legen wir zunächst einmal die Datei „ArrayOfString.php“ im „types“-Ordner an, welche wir anschließend wieder in unserer „index.php“-Datei referenzieren.

<?php
	$server->wsdl->addComplexType('ArrayOfString', 'complexType', 'array', '', 'SOAP-ENC:Array',
				array(), array(array('ref' => 'SOAP-ENC:arrayType', 'wsdl:arrayType' => 'xsd:string[]')), 'xsd:string'); 
?>

Was hier direkt auffällt, ist der Unterschied „array“ statt „struct“ und, dass es ein paar Parameter mehr gibt. Auch die Definition des Typen-Arrays sieht ein wenig anders aus. Wichtig hierbei ist jedoch nur, dass das „xsd:string[]“ sowie „xsd:string“ (bei unseren Datentypen später „tns:UnserTyp[]“ und „tns:UserTyp“) identisch sind. Anschließend kann das ganze Array unter „tns:ArrayOfString“ in anderen Typen weiter verwendet werden.

Soviel erstmal zu Grundlegenden Datentyp-Definitionen unter NuSoap. Nun brauchen wir allerdings auch noch Funktionen, damit wir überhaupt Aufrufe auf unseren Webservice starten können. Anders als bei Datentypen benötigen unsere Funktionen jedoch neben der Definition auch noch eine Implementierung. Bevor wir jedoch unsere Methode definieren, definieren wir uns noch einen weiteren „ReturnData“-Typ. In diesem Fall den Typ „WebServiceReturnDataOfString“, der zusätzlich zu den obigen Standard-Rückgabeinformationen auch noch einen Rückgabewert in Form eines Strings liefert (bitte daran denken,dass nach der Definition des neuen Typens auch dieser wieder in der „index-php“-Datei zu referenzieren ist).

<?php
	$server->wsdl->addComplexType('WebServiceReturnDataOfString', 'complexType', 'struct', 'all', '',  
				array('ReturnData' => array('name' => 'ReturnData', 'type' => 'xsd:string'), 
					  'HasError' => array('name' => 'HasError', 'type' => 'xsd:boolean'), 
					  'UserErrorCode' => array('name' => 'UserErrorCode', 'type' => 'xsd:int'), 
					  'DetailedErrorMessage' => array('name' => 'DetailedErrorMessage', 'type' => 'xsd:string'), 
					  'HasDetailedErrorMessage' => array('name' => 'HasDetailedErrorMessage', 'type' => 'xsd:boolean')));	
?>

So haben wir aber endlich alles, um unsere erste Funktionalität zu implementieren. Legen wir uns zunächst einmal eine „Hallo.php“-Datei im „functions“-Ordner an. Hier werden wir zunächst unsere Methode, ähnlichen wie bei den Datentypen definieren und anschließend PHP üblich implementieren. Anschließend wird auch diese Datei wiederrum in der „index.php“-Datei referenziert.

<?php
	$server->register('Hello', 
					  array('firstname' => 'xsd:string', 'lastname' => 'xsd:string'), 
					  array('return' => 'tns:WebServiceReturnDataOfString'), 
					  $namespace, false, 'rpc', 'encoded', 
					  'Says hello to a person.'); 
					  
	function Hello($firstname, $lastname) 
	{
		try
		{
			$result = 'Hello ' .$firstname. ' ' .$lastname;			
			return array(
				'ReturnData' => $result, 
				'HasEmptyResult' => count($result) == 0, 
				'HasError' => false, 
				'UserErrorCode' => -1, 
				'DetailedErrorMessage' => NULL, 
				'HasDetailedErrorMessage' => false
			);
		}
		catch(Exception $e)
		{
			return array(
				'ReturnData' => NULL, 
				'HasEmptyResult' => true, 
				'HasError' => true, 
				'UserErrorCode' => 1, 
				'DetailedErrorMessage' => $e->getMessage(), 
				'HasDetailedErrorMessage' => strlen($e->getMessage()) > 0
			);
		}
	}
?>

Sieht erstmal nach viel aus, ist es aber im Endeffekt gar nicht. Als erstes haben wir hier die Definition der Methode. Diese wird auf unserer Instanz des soap_server’s mit dem Namen „Hello“ definiert. Direkt danach folgt das Array mit den Parametern für die Methode und anschließend das Array für den Rückgabetypen, was hier unser WebServiceReturnDataOfString ist. Anschließend werden noch ein paar Standardangaben mit der abschließenden Beschreibung der Methode getroffen. Bei der Implementierung der Methode wird nur eine simple String-Verkettung durchgeführt und diese in unseren Rückgabetypen gepackt. Das Erstellen unserer WebServiceReturnData-Antworten lässt sich im übrigen sehr schön in einzelne Methoden auslagern. Zum Beispiel so:

	function GetResult($res)
	{	
		return array(
			'ReturnData' => $res, 
			'HasEmptyResult' => count($res) == 0, 
			'HasError' => false, 
			'UserErrorCode' => -1, 
			'DetailedErrorMessage' => NULL, 
			'HasDetailedErrorMessage' => false
		);
	}

	function GetErrorResult($errorCode, $errorText)
	{
		return array(
			'ReturnData' => NULL, 
			'HasEmptyResult' => true, 
			'HasError' => true, 
			'UserErrorCode' => $errorCode, 
			'DetailedErrorMessage' => $errorText, 
			'HasDetailedErrorMessage' => strlen($errorText) > 0
		);
	}

	function GetDefaultResult()
	{
		return array(
			'HasError' => false, 
			'UserErrorCode' => -1, 
			'DetailedErrorMessage' => NULL, 
			'HasDetailedErrorMessage' => false
		);
	}

	function GetDefaultResultWithError($errorCode, $errorMessage)
	{
		return array(
			'HasError' => true, 
			'UserErrorCode' => $errorCode, 
			'DetailedErrorMessage' => $errorMessage, 
			'HasDetailedErrorMessage' => strlen($errorMessage) > 0
		);
	}

Damit wäre die Implementierung unserer „Hello“-Funktionalität auch gleich viel kleiner, nämlich nur noch so:

	function Hello($firstname, $lastname) 
	{
		try
		{
			$result = 'Hello ' .$firstname. ' ' .$lastname;			
			return GetResult($result);
		}
		catch(Exception $e)
		{
			return GetErrorResult(1, $e->getMessage());
		}
	}

Sieht doch schon gleich mal besser aus. Nun hätten wir auch schon einen Webservice, auf den wir einen Aufruf starten könnten. Dies lässt sich auch – gerade in C# – sehr einfach testen und nutzen. Hierzu erstellen wir uns ein normales C#-Projekt im Visual Studio. Der Einfachheit halber, habe ich hier nur eine Konsolenanwendung erstellt. Hier müssen wir nun zunächst die WSDL-Datei unseres Webservices referenzieren. Dazu machen wir einen Rechtsklick auf unser C#-Projekt, wählen Dienstverweis hinzufügen aus, um hier direkt auf „Erweitert…“ zu drücken. Im nun aufgehenden Fenster drücken wir wiederrum direkt auf „Webverweis hinzufügen…“, wo wir nun endlich unsere URL eingeben können. Bei mir wäre die URL hierbei die folgende: https://kruse-familie.eu/soap/index.php?wsdl

Geben wir dem Ganzen noch einen anständigen Namen, wie z.B. „NuSoapWebservice“ und klicken auf „Verweis hinzufügen“. Nun können wir in unserem C#-Sourcecode eine Instanz des Webservices erstellen und dort unsere „Hello“-Funktion aufrufen. Die Implementierung unter C# könnte dabei wie folgt aussehen:

        static void Main()
        {
            NuSoapWebservice.NuSoapWebservice webservice = new NuSoapWebservice.NuSoapWebservice();
            WebServiceReturnDataOfString returnData = webservice.Hello("Sebastian", "Kruse");

            if (returnData.HasError)
            {
                Console.WriteLine("Ein Fehler ist beim Webservice aufgetreten!");

                if (returnData.HasDetailedErrorMessage)
                    Console.WriteLine(returnData.DetailedErrorMessage);
            }
            else
            {
                Console.WriteLine(returnData.ReturnData); //Wird "Hello Sebastian Kruse" ausgeben.
            }

            Console.ReadLine();
        }

Hier wird, wie bereits erwähnt, zunächst eine Instanz unseres Webservices erzeugt und anschließend die Funktion „Hello“ aufgerufen. Die Rückgabe wird anschließend verarbeitet und ausgegeben.

Somit sind wir nun in der Lage NuSoap-Webservices zu erstellen, Typen und Funktionen dort zu definieren und anschließend in C# zu verwenden. Was natürlich jetzt noch fehlt ist die Absicherung des Webservices mittels Authentifizierung. Hierfür reichen allerdings die Möglichkeiten von einer HTTP-Authentifizierung, über eine Header-Authentifizierung bis hin zu simplen Abfragen in jeder Methode. Da es hier also mehr oder weniger immer auf den speziellen Anwendungsfall angepasst werden muss, werde ich dies hier nicht weiter ausführen.