Веб-сервіси
===========

[Веб-сервіс](http://uk.wikipedia.org/wiki/Веб-служба) — програмна система,
розроблена для забезпечення взаємодії між декількома компʼютерами через мережу. 
У веб-додатку це зазвичай набір API, який можна використовувати через інтернет для виконання 
дій на віддаленому сервері, обслуговуючому веб-сервіс.
Наприклад, клієнт, заснований на [Flex](http://www.adobe.com/products/flex/), може викликати функції, 
реалізовані на сервері у PHP-додатку. У якості базового рівня протоколу використовується [SOAP](http://uk.wikipedia.org/wiki/SOAP).

Для того, щоб спростити завдання створення веб-сервісу, в Yii включені
[CWebService] та [CWebServiceAction]. API згруповані по класах, які називаються
*провайдерами*. Для кожного класу Yii генерує [WSDL](http://www.w3.org/TR/wsdl),
що описує функціонал наданого API і правила його використання клієнтом.
При обробці виклику клієнта, Yii створює відповідний йому примірник провайдера, викликає метод API і відповідає на запит.

> Note|Примітка: Для роботи [CWebService] потрібно [розширення PHP SOAP](http://php.net/manual/en/ref.soap.php). 
Переконайтеся, що воно включено, перш, ніж пробувати приклади, описані далі.

Створення провайдера
--------------------

Як вже було описано, провайдер — це клас, який реалізує методи, які можуть бути викликані віддалено. 
Для того, щоб визначити, які методи можуть бути викликані віддалено і яке значення повертати, 
Yii використовує [спеціальні коментарі](http://java.sun.com/j2se/javadoc/writingdoccomments/) та
[reflection](http://php.net/manual/en/book.reflection.php).

Спробуємо реалізувати простий сервіс, що віддає інформацію про котирування акцій певної компанії. 
Для цього нам потрібно реалізувати провайдер, як показано нижче. 
Варто зазначити, що успадковуємо клас провайдера `StockController` від [CController]. 
Спадкування не є обовʼязковим.

~~~
[php]
class StockController extends CController
{
	/**
	 * @param string індекс підприємства
	 * @return float ціна
	 * @soap
	 */
	public function getPrice($symbol)
	{
		$prices=array('IBM'=>100, 'GOOGLE'=>350);
		return isset($prices[$symbol])?$prices[$symbol]:0;
	    //…повертаємо ціну для компанії з індексом $symbol
	}
}
~~~

Вище ми описали, що метод `getPrice` є частиною API веб-сервісу, позначивши його у коментарі тегом `@soap`. 
Там же ми описали типи параметрів і значення, що повертається.
Додаткові методи API можуть бути описані точно таким же чином.

Реалізація дії веб-сервісу
--------------------------

Після створення провайдера необхідно зробити його доступним для клієнтів.
Для цього необхідно описати дію контролера [CWebServiceAction].
У нашому прикладі ми використовуємо `StockController`:

~~~
[php]
class StockController extends CController
{
	public function actions()
	{
		return array(
			'quote'=>array(
				'class'=>'CWebServiceAction',
			),
		);
	}

	/**
	 * @param string індекс підприємства
	 * @return float ціна
	 * @soap
	 */
	public function getPrice($symbol)
	{
	    //…повертаємо ціну для компанії з індексом $symbol
	}
}
~~~

Це все, що потрібно для створення веб-сервісу. Тепер при зверненні до URL
`http://hostname/path/to/index.php?r=stock/quote`, ми отримаємо обʼємний XML, 
насправді є WSDL описаного нами веб-сервісу.

> Tip|Підказка: За замовчуванням, при використанні [CWebServiceAction]
мається на увазі, що поточний контролер є провайдером. Саме тому ми визначили метод
`getPrice` у класі `StockController`.

Використання веб-сервісу
------------------------

Для того, щоб наш приклад був повним, створимо клієнт, який використовує веб-сервіс, який ми тільки що створили. 
У прикладі клієнт буде написаний на PHP, але для його реалізації можна використовувати і інші мови, 
такі як `Java`, `C#`, `Flex` і т.д.

~~~
[php]
$client=new SoapClient('http://hostname/path/to/index.php?r=stock/quote');
echo $client->getPrice('GOOGLE');
~~~

Запустивши даний скрипт через браузер або у консолі, ви повинні отримати `350`,
що відповідає ціні акцій `GOOGLE`.

Типи даних
----------

При описі методів і властивостей класу, які повинні бути доступні через веб-сервіс, 
нам необхідно визначити типи параметрів і значень. 
Для цього можуть бути використані наступні типи:

   - str/string: відповідає `xsd:string`;
   - int/integer: відповідає `xsd:int`;
   - float/double: відповідає `xsd:float`;
   - bool/boolean: відповідає `xsd:boolean`;
   - date: відповідає `xsd:date`;
   - time: відповідає `xsd:time`;
   - datetime: відповідає `xsd:dateTime`;
   - array: відповідає `xsd:string`;
   - object: відповідає `xsd:struct`;
   - mixed: відповідає `xsd:anyType`.

Якщо тип не є одним із наведених вище, він сприймається як складений тип, що складається із властивостей. 
Цей тип відповідає класу, а його властивості — public-змінним класу, зазначених у коментарях `@soap`.

Також можна використовувати масиви. Для цього необхідно дописати `[]` 
у кінець примітивного або складеного типу. 
Таким чином ми отримаємо масив з елементами заданого типу.

Нижче наведено приклад визначення методу API `getPosts`, що повертає масив обʼєктів класу `Post`.

~~~
[php]
class PostController extends CController
{
	/**
	 * @return Post[] список записів
	 * @soap
	 */
	public function getPosts()
	{
		return Post::model()->findAll();
	}
}

class Post extends CActiveRecord
{
	/**
	 * @var integer ID запису
	 * @soap
	 */
	public $id;
	/**
	 * @var string заголовок запису
	 * @soap
	 */
	public $title;

	public static function model($className=__CLASS__)
	{
		return parent::model($className);
	}
}
~~~

Зіставлення класів
------------------

Для отримання від клієнта параметрів складеного типу, у додатку повинні бути задані відповідності типів WSDL класам PHP. 
Для цього необхідно налаштувати властивість [classMap|CWebServiceAction::classMap] класу [CWebServiceAction].

~~~
[php]
class PostController extends CController
{
	public function actions()
	{
		return array(
			'service'=>array(
				'class'=>'CWebServiceAction',
				'classMap'=>array(
					'Post'=>'Post',  // або просто 'Post'
				),
			),
		);
	}
	…
}
~~~

Перехоплення віддаленого виклику методу
---------------------------------------

Реалізуючи інтерфейс [IWebServiceProvider], провайдер може перехоплювати віддалені виклики методів. 
Використовуючи [IWebServiceProvider::beforeWebMethod], можна отримати поточний екземпляр [CWebService]. 
Через [CWebService::methodName] — назву метода, що викликається. 
Якщо метод з якихось причин (наприклад, відсутність прав на його виконання) не повинен бути викликаний,
необхідно повернути false.