Webservice =========== [Webservices](http://de.wikipedia.org/wiki/Webservice) bilden ein Softwaresystem, das entwickelt wurde, um kompatible Interaktion zwischen Maschinen über ein Netzwerk zu unterstützen. Im Zusammenhang mit Webanwendungen beziehen sie sich in der Regel auf eine Reihe von APIs, die über das Internet zugänglich sind und auf einem entfernten Rechner ausgeführt werden können, der den angeforderten Dienst beherbergt. Ein [flex](http://www.adobe.com/products/flex/)-basierter Client könnte zum Beispiel serverseitig Funktionen einer PHP-basierten Webanwendung aufrufen. Webservices bauen auf [SOAP](http://de.wikipedia.org/wiki/SOAP) als grundlegendem Kommunikations-Protokollstapel auf. Um die Verwirklichung von Webservices in einer Webanwendung zu vereinfachen, beinhaltet Yii [CWebService] und [CWebServiceAction]. Die APIs werden zu Klassen gruppiert, den sogenannten *Dienstanbietern* (engl.: service provider). Für jede Klasse erzeugt Yii eine [WSDL](http://www.w3.org/TR/wsdl)-Spezifikation, die die verfügbaren APIs und deren Aufruf von Clientseite beschreibt. Wenn eine API von einem Client aufgerufen wird, instanziiert Yii den enstprechenden Dienstanbieter und ruft die angeforderte API auf, um die Anfrage zu verarbeiten. > Note|Hinweis: [CWebService] basiert auf der [SOAP-Erweiterung](http://www.php.net/manual/de/ref.soap.php) von PHP. Stellen Sie sicher, dass diese Erweiterung aktiviert wurde, bevor Sie die Beispiele in diesem Abschnitt ausprobieren. Definieren eines Dienstanbieters -------------------------------- Wie erwähnt, ist ein Dienstanbieter eine Klasse mit entfernt aufrufbaren Methoden. Yii verwendet [Doc Comments](http://java.sun.com/j2se/javadoc/writingdoccomments/) und [Reflection von Klassen](http://www.php.net/manual/de/book.reflection.php) um herauszufinden, welche Methoden entfernt aufrufbar sind und wie deren Parameter und Rückgabewerte aussehen. Beginnen wir mit einem einfachen Dienst zur Abfrage von Börsenkursen. Mit diesem Dienst kann ein Client den Kurs einer bestimmten Aktie abfragen. Wir definieren den Dienstanbieter wie folgt. Beachten Sie, dass wir die Anbieterklasse von [CController] ableiten, dies ist aber nicht unbedingt nötigt. ~~~ [php] class StockController extends CController { /** * @param string das symbol der Aktie * @return float der Preis der Aktie * @soap */ public function getPrice($symbol) { $prices=array('IBM'=>100, 'GOOGLE'=>350); return isset($prices[$symbol])?$prices[$symbol]:0; //...Aktienpreis für $symbol zurückliefern } } ~~~ In diesem Beispiel deklarieren wir die Methode `getPrice` als API eines Webservices, indem wir sie in ihrem Doc Comment mit dem Tag `@soap` markieren. Wir stützen uns auf die Angaben im Doc Comment, um die Datentypen der Eingangsparameter und des Rückgabewerts zu bestimmen. Weitere APIs können auf ähnliche weise deklariert werden. Deklarieren einer Webservice-Action ----------------------------------- Nachdem wir den Dienstanbieter definiert haben, müssen wir ihn für Clients verfügbar machen. Im einzelnen heißt das, wir müssen eine Controller-Action erstellen, die diesen Dienst öffentlich bereitstellt. Dies kann einfach dadurch erreicht werden, indem wir eine [CWebServiceAction]-Action in einer Controllerklasse deklarieren. Für unser Beispiel bringen wir diese einfach in `StockController` unter: ~~~ [php] class StockController extends CController { public function actions() { return array( 'quote'=>array( 'class'=>'CWebServiceAction', ), ); } /** * @param string das symbol der Aktie * @return float der Preis der Aktie * @soap */ public function getPrice($symbol) { //...Aktienpreis für $symbol zurückliefern } } ~~~ Das ist alles, was wir tun müssen, um einen Webservice zu erstellen! Wenn wir die Action über die URL `http://hostname/pfad/zu/index.php?r=stock/quote` aufrufen, sehen wir eine Menge XML-Inhalte, die die WSDL für den von uns definierten Webservice darstellen. > Tip|Tipp: Standardmäßig geht [CWebServiceAction] davon aus, dass der aktuelle Controller auch der Dienstanbieter ist. Deshalb haben wir die Methode `getPrice` in der `StockController`-Klasse definiert. Einsatz des Webservice ---------------------- Um das Beispiel abzuschließen, erstellen wir noch einen Client, der den eben erstellten Webservice verwendet. Der Beispielclient wurde in PHP geschrieben, könnte aber auch in anderen Sprachen wie `Java`, `C#` oder `Flex` vorliegen. ~~~ [php] $client=new SoapClient('http://hostname/pfad/zu/index.php?r=stock/quote'); echo $client->getPrice('GOOGLE'); ~~~ Wenn Sie dieses Script im Web- oder Konsolenmodus aufurfen, sollten wir `350` als Preis für `GOOGLE` erhalten. Datentypen ---------- Beim deklarieren der Klassenmethoden und -eigenschaften, die von Ferne verfügbar gemacht werden sollen, müssen wir die Datentypen der Ein- und Ausgangsparameter angeben. Die folgenden Grundtypen können verwendet werden: - str/string: wird abgebildet auf `xsd:string`; - int/integer: wird abgebildet auf `xsd:int`; - float/double: wird abgebildet auf `xsd:float`; - bool/boolean: wird abgebildet auf `xsd:boolean`; - date: wird abgebildet auf `xsd:date`; - time: wird abgebildet auf `xsd:time`; - datetime: wird abgebildet auf `xsd:dateTime`; - array: wird abgebildet auf `xsd:string`; - object: wird abgebildet auf `xsd:struct`; - mixed: wird abgebildet auf `xsd:anyType`. Falls ein Typ nicht in der Liste dieser Grundtypen vorkommt, wird von einem aus Eigenschaften zusammengesetzten Typ (engl.: composite type) ausgegangen. Ein zusammengesetzter Typ wird als Klasse dargestellt, seine Eigenschaften als öffentliche und in den Doc Comments mit `@soap` markierte Eigenschaften dieser Klasse. Wir können auch den Typ Array verwenden, indem wir `[]` hinter einem Grundtyp oder einem zusammengesetzten Typ anhängen. Damit würde ein Array des angegebenen Typs angegeben. Untenstehend finden Sie ein Beispiel für die Web-API von `getPosts`, welches ein Array von `Post`-Objekten zurückliefert. ~~~ [php] class PostController extends CController { /** * @return Post[] eine Liste von Posts (Beiträgen) * @soap */ public function getPosts() { return Post::model()->findAll(); } } class Post extends CActiveRecord { /** * @var integer post ID * @soap */ public $id; /** * @var string post title (Beitragstitel) * @soap */ public $title; public static function model($className=__CLASS__) { return parent::model($className); } } ~~~ Abbildung von Klassen --------------------- Um die Parameter für einen zusammengesetzten Typ vom Client beziehen zu können, muss eine Anwendung eine Abbildung von WSDL-Typen auf die entsprechenden PHP-Klassen deklarieren. Dies geschieht, indem die Eigenschaft [classMap|CWebServiceAction::classMap] von [CWebServiceAction] konfiguriert wird. ~~~ [php] class PostController extends CController { public function actions() { return array( 'service'=>array( 'class'=>'CWebServiceAction', 'classMap'=>array( 'Post'=>'Post', // oder einfach nur 'Post' ), ), ); } ...... } ~~~ Abfangen entfernter Methodenaufrufe ----------------------------------- Ein Dienstanbieter kann die entfernten Methodenaufrufe abfangen, wenn er das Interface [IWebServiceProvider] implementiert. In [IWebServiceProvider::beforeWebMethod] kann der Anbieter die aktuelle Instanz von [CWebService] beziehen und den Namen der angeforderten Methode über [CWebService::methodName] ermitteln. Sie kann false zurückgeben, falls die entfernte Methode aus irgendeinem Grund nicht ausgeführt werden soll (z.B. bei unberechtigtem Zugriff).