36.3. Zend_XmlRpc_Server

36.3.1. Введение

Zend_XmlRpc_Server задуман как полнофункциональный XML-RPC сервер, следующий спецификациям на www.xmlrpc.com. Кроме того, он реализует метод system.multicall(), позволяющий добавлять несколько запросов в один.

36.3.2. Основы использования

Наиболее простой пример использования:

<?php
require_once 'Zend/XmlRpc/Server.php';
require_once 'My/Service/Class.php';

$server = new Zend_XmlRpc_Server();
$server->setClass('My_Service_Class');
echo $server->handle();

36.3.3. Структура сервера

Zend_XmlRpc_Server состоит из множества компонент от собственно сервера до объектов запросов, ответов и сообщений об ошибке.

Для загрузки Zend_XmlRpc_Server разработчик должен прикрепить классы или функции к серверу через методы setClass() и addFunction().

После этого можно передать объект Zend_XmlRpc_Request методу Zend_XmlRpc_Server::handle(); если он не был передан, то будет проинциализирован объект Zend_XmlRpc_Request_Http, при этом данные запроса берутся из php://input.

Затем Zend_XmlRpc_Server::handle() пытается определить подходящий обработчик, основываясь на запрошенном методе. После этого он возвращает основанный на Zend_XmlRpc_Response объект ответа или объект сообщения об ошибке Zend_XmlRpc_Server_Fault. Эти объекты имеют метод __toString(), который возвращает валидный XML-RPC ответ в формате XML, что позволяет выводить эти объекты непосредственно через echo().

36.3.4. Соглашения

Zend_XmlRpc_Server позволяет разработчикам прикреплять функции и методы класса, которые называются "диспетчерируемыми XML-RPC методами". Через Zend_Server_Reflection он проводит интроспекцию по всем прикрепленным методам, используя docblock'и функций и методов для установки текста справки и сигнатур методов.

Не обязательно, чтобы типы в XML-RPC один-в-один соответствовали типам в PHP. Тем не менее, для наилучшего результата код пытается угадать наиболее подходящий тип, основываясь на значениях дескрипторов @param и @return. Некоторые типы в XML-RPC не имеют эквивалентов в PHP и должны указываться в phpdoc. В их список входят:

  • dateTime.iso8601, дата в формате YYYYMMDDTHH:mm:ss

  • base64, данные, закодированные по алгоритму base64

  • struct, любой ассоциативный массив

Пример того, как указывается XML-RPC тип

<?php
/**
* Это пример функции
*
* @param base64 $val1 Закодированные в base64 данные
* @param dateTime.iso8601 $val2 Дата в ISO-формате
* @param struct $val3 Ассоциативный массив
* @return struct
*/
function myFunc($val1, $val2, $val3)
{
}

PhpDocumentor не проводит валидацию типов, определенных для параметров или возвращаемых значений, поэтому это не должно повлиять на вашу документацию по API. Указание типов необходимо, если сервер проводит валидацию передаваемых методу параметров.

Будет совершенно корректным с точки зрения синтаксиса определять набор возможных типов как для параметров, так и для возвращаемых значений; спецификация XML-RPC даже рекомендует, чтобы system.methodSignature возвращал массив всех возможных сигнатур метода (т.е. все возможные комбинации параметров и возвращаемых значений). Вы можете делать это точно так же, как обычно делаете для PhpDocumentor, используя оператор '|'.

<?php
/**
* Это пример функции
*
* @param string|base64 $val1 Строка или закодированные в base64 данные
* @param string|dateTime.iso8601 $val2 Строка или дата в ISO-формате
* @param array|struct $val3 Обычный нумерованный массив или ассоциативный массив
* @return boolean|struct
*/
function myFunc($val1, $val2, $val3)
{
}

Тем не менее, следует учесть, что множество сигнатур может сбить с толку разработчиков, использующих данный веб-сервис; иначе говоря, следует стремится к тому, чтобы XML-RPC метод имел только одну сигнатуру.

36.3.5. Использование пространства имен

В XML-RPC есть такое понятие, как пространства имен; по существу, это позволяет группировать методы посредством разделенных точкой пространств имен. Это позволяет предотвратить конфликты имен методов, предоставляемых разными классами. Например, обычно XML-RPC сервер предоставляет несколько методов в пространстве имен 'system':

  • system.listMethods

  • system.methodHelp

  • system.methodSignature

В нашем случае они соответствуют методам с теми же именами в Zend_XmlRpc_Server.

Если необходимо добавить пространства имен для обслуживаемых методов, то просто укажите пространство имен в качестве параметра при вызове соответствующего метода для прикрепления функции или класса:

<?php
// Все открытые методы в My_Service_Class можно будет вызывать как
// myservice.имя_метода
$server->setClass('My_Service_Class', 'myservice');

// Функцию 'somefunc' можно будет вызывать как funcs.somefunc
$server->addFunction('somefunc', 'funcs');

36.3.6. Специальные объекты запросов

В большинстве случаев вы можете использовать включенный по умолчанию в Zend_XmlRpc_Server тип запроса – Zend_XmlRpc_Request_Http. Тем не менее, может потребоваться использование XML-RPC через CLI, GUI или другие окружения, журналирование приходящих запросов. Для этого вы можете создавать специальные объекты запроса, которые наследуют от Zend_XmlRpc_Request. Важно помнить при этом, что методы getMethod() и getParams() должны быть реализованы таким образом, чтобы XML-RPC сервер мог получить из них ту информацию, которая необходима для обработки запроса.

36.3.7. Специальные объекты ответов

Как и в случае объектов запросов, Zend_XmlRpc_Server может возвращать специальные объекты ответов; по умолчанию возвращается объект Zend_XmlRpc_Response_Http, который отправляет соответствующий XML-RPC заголовок Content-Type. Одними из возможных целей использования специальных объектов являются журналирование ответов или отправка ответов обратно на STDOUT.

Для того чтобы использовать специальный класс ответа, используйте метод Zend_XmlRpc_Server::setResponseClass() до вызова метода handle().

36.3.8. Обработка исключений через сообщения об ошибке

Zend_XmlRpc_Server отлавливает исключения, сгенерированные вызываемым методом и генерирует ответ с сообщением об ошибке сразу, как только исключение поймано. Однако по умолчанию сообщение и код исключения не используются в ответе с сообщением об ошибке. Это сделано намеренно для того, чтобы защитить ваш код, т.к. многие исключения могут предоставлять информацию о коде приложения или среде выполнения, предназначенные разработчику.

Тем не менее, можно включать классы исключений в список разрешенных для отображения в ответах с сообщением об ошибке. Для этого используйте Zend_XmlRpc_Server_Fault::attachFaultException() для включения данного класса исключения в список разрешенных.

<?php
Zend_XmlRpc_Server_Fault::attachFaultException('My_Project_Exception');

Если вы используете класс исключения, от которого наследуют другие исключения в проекте, то можете cразу включить все "семейство" исключений в список разрешенных. Zend_XmlRpc_Server_Exceptions всегда находится в списке разрешенных исключений для того, чтобы сообщать об определенных внутренних ошибках (вызов несуществующего метода и т.д.).

На любое исключение, не включенное в список разрешенных, будет генерироваться ответ с кодом ошибки '404' и сообщением 'Unknown error'.

36.3.9. Кэширование определений сервера между запросами

Прикрепление большого количества классов к экземпляру XML-RPC сервера может отнимать много ресурсов – каждый класс должен проверяться с использованием Reflection API (через Zend_Server_Reflection), который создает список всех возможных сигнатур методов для передачи классу сервера.

Чтобы сократить потерю производительности, можно использовать Zend_XmlRpc_Server_Cache для кэширования определений сервера между запросами. Если комбинировать его с __autoload(), то это может дать значительный прирост производительности.

Пример использования:

<?php
require_once 'Zend/Loader.php';
require_once 'Zend/XmlRpc/Server.php';
require_once 'Zend/XmlRpc/Server/Cache.php';

function __autoload($class)
{
    Zend_Loader::loadClass($class);
}

$cacheFile = dirname(__FILE__) . '/xmlrpc.cache';
$server = new Zend_XmlRpc_Server();

if (!Zend_XmlRpc_Server_Cache::get($cacheFile, $server)) {
    require_once 'My/Services/Glue.php';
    require_once 'My/Services/Paste.php';
    require_once 'My/Services/Tape.php';

    $server->setClass('My_Services_Glue', 'glue');
    $server->setClass('My_Services_Paste', 'paste');
    $server->setClass('My_Services_Tape', 'tape');

    Zend_XmlRpc_Server_Cache::save($cacheFile, $server);
}

echo $server->handle();

В этом примере производится попытка получить определение сервера из файла xmlrpc.cache, находящегося в той же директории, что и скрипт. Если попытка не удалась, то загружаются нужные классы и прикрепляются к экземпляру сервера, затем создается новый файл кэша с определением сервера.

36.3.10. Примеры использования

Ниже приведено несколько примеров использования, показывающий полный набор возможных вариантов, доступных разработчикам. Примеры использования построены на основе предоставленных ранее примеров.

36.3.10.1. Основы использования

В примере ниже прикрепляется функция в качестве диспетчерируемого XML-RPC метода и обрабатываются входящие вызовы.

<?php
require_once 'Zend/XmlRpc/Server.php';

/**
 * Возвращает сумму MD5 переданного значения
 *
 * @param string $value Value to md5sum
 * @return string MD5 sum of value
 */
function md5Value($value)
{
    return md5($value);
}

$server = new Zend_XmlRpc_Server();
$server->addFunction('md5Value');
echo $server->handle();

36.3.10.2. Прикрепление класса

Пример ниже иллюстрирует прикрепление открытых методов класса как диспетчерируемых XML-RPC методов.

<?php
require_once 'Zend/XmlRpc/Server.php';
require_once 'Services/Comb.php';

$server = new Zend_XmlRpc_Server();
$server->setClass('Services_Comb');
echo $server->handle();

36.3.10.3. Прикрепление нескольких классов с использованием пространств имен

Пример ниже демонстрирует прикрепление нескольких классов, каждый со своим пространством имен.

<?php
require_once 'Zend/XmlRpc/Server.php';
require_once 'Services/Comb.php';
require_once 'Services/Brush.php';
require_once 'Services/Pick.php';

$server = new Zend_XmlRpc_Server();
$server->setClass('Services_Comb', 'comb');
$server->setClass('Services_Brush', 'brush');
$server->setClass('Services_Pick', 'pick');
echo $server->handle();

36.3.10.4. Указание исключений как используемых для ответов с сообщением об ошибке

Пример ниже позволяет любым наследующим от Services_Exception классам предоставлять свои коды и сообщения для подстановки в ответ с сообщением об ошибке.

<?php
require_once 'Zend/XmlRpc/Server.php';
require_once 'Zend/XmlRpc/Server/Fault.php';
require_once 'Services/Exception.php';
require_once 'Services/Comb.php';
require_once 'Services/Brush.php';
require_once 'Services/Pick.php';

// Включение Services_Exceptions в список разрешенных исключений
Zend_XmlRpc_Server_Fault::attachFaultException('Services_Exception');

$server = new Zend_XmlRpc_Server();
$server->setClass('Services_Comb', 'comb');
$server->setClass('Services_Brush', 'brush');
$server->setClass('Services_Pick', 'pick');
echo $server->handle();

36.3.10.5. Использование специальных объектов запроса

В примере ниже инстанцируется специальный объект запроса и передается серверу для обработки.

<?php
require_once 'Zend/XmlRpc/Server.php';
require_once 'Zend/XmlRpc/Server/Fault.php';
require_once 'Services/Request.php';
require_once 'Services/Exception.php';
require_once 'Services/Comb.php';
require_once 'Services/Brush.php';
require_once 'Services/Pick.php';

// Включение Services_Exceptions в список разрешенных исключений
Zend_XmlRpc_Server_Fault::attachFaultException('Services_Exception');

$server = new Zend_XmlRpc_Server();
$server->setClass('Services_Comb', 'comb');
$server->setClass('Services_Brush', 'brush');
$server->setClass('Services_Pick', 'pick');

// Создание объекта запроса
$request = new Services_Request();

echo $server->handle($request);

36.3.10.6. Использование специальных объектов ответа

Пример ниже демонстрирует указание специального класса ответа для возвращаемого ответа.

<?php
require_once 'Zend/XmlRpc/Server.php';
require_once 'Zend/XmlRpc/Server/Fault.php';
require_once 'Services/Request.php';
require_once 'Services/Response.php';
require_once 'Services/Exception.php';
require_once 'Services/Comb.php';
require_once 'Services/Brush.php';
require_once 'Services/Pick.php';

// Включение Services_Exceptions в список разрешенных исключений
Zend_XmlRpc_Server_Fault::attachFaultException('Services_Exception');

$server = new Zend_XmlRpc_Server();
$server->setClass('Services_Comb', 'comb');
$server->setClass('Services_Brush', 'brush');
$server->setClass('Services_Pick', 'pick');

// Создание объекта запроса
$request = new Services_Request();

// Должен использоваться специальный класс ответа
$server->setResponseClass('Services_Response');

echo $server->handle($request);

36.3.10.7. Кэширование определений сервера между запросами

Пример ниже демонстрирует кэширование определений сервера между запросами.

<?php
require_once 'Zend/XmlRpc/Server.php';
require_once 'Zend/XmlRpc/Server/Fault.php';
require_once 'Zend/XmlRpc/Server/Cache.php';
require_once 'Services/Request.php';
require_once 'Services/Response.php';
require_once 'Services/Exception.php';
require_once 'Services/Comb.php';
require_once 'Services/Brush.php';
require_once 'Services/Pick.php';

// Указание файла кэша
$cacheFile = dirname(__FILE__) . '/xmlrpc.cache';

// Включение Services_Exceptions в список разрешенных исключений
Zend_XmlRpc_Server_Fault::attachFaultException('Services_Exception');

$server = new Zend_XmlRpc_Server();

// Попытка получить определение сервера из кэша
if (!Zend_XmlRpc_Server_Cache::get($cacheFile, $server)) {
    $server->setClass('Services_Comb', 'comb'); 
    $server->setClass('Services_Brush', 'brush');
    $server->setClass('Services_Pick', 'pick');

    // Сохранение в кэш
    Zend_XmlRpc_Server_Cache::save($cacheFile, $server));
}

// Создание объекта запроса
$request = new Services_Request();

// Должен использоваться специальный класс ответа
$server->setResponseClass('Services_Response');

echo $server->handle($request);