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

[Веб-сервис](http://ru.wikipedia.org/wiki/Веб_сервис) — программная система,
разработанная для обеспечения взаимодействия между несколькими компьютерами через
сеть. В веб-приложении это обычно набор API, который можно использовать через
интернет для выполнения действий на удалённом сервере, обслуживающем веб-сервис.
К примеру, клиент, основанный на [Flex](http://www.adobe.com/products/flex/), может
вызывать функции, реализованные на сервере в PHP-приложении. В качестве базового
уровня протокола используется [SOAP](http://en.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.