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).

<div class="revision">$Id: topics.webservice.txt 1808 2010-02-17 21:49:42Z qiang.xue $</div>
