Веб-сервисы =========== [Веб-сервис](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.