# Zend_XmlRpc_Client

## Введение

Zend Framework поддерживает клиентское использование удаленных XML-RPC сервисов через пакет `Zend_XmlRpc_Client`. Его основные возможности включают в себя автоматическое преобразование типов между PHP и XML-RPC, прокси-объект сервера и доступ к средствам интроспекции на сервере.

## Вызов методов

Конструктор `Zend_XmlRpc_Client` принимает URL удаленного XML-RPC сервера в качестве первого параметра. Новый экземпляр класса может использоваться для вызова любых удаленных методов этого сервера.

Для вызова удаленного метода через клиентa XML-RPC инстанцируйте его и используйте его метод `call()`. В примере ниже используется демонстрационный XML-RPC сервер на веб-сайте Zend Framework. Вы можете использовать его для тестирования или изучения компонент `Zend_XmlRpc`.

### Вызов метода XML-RPC

    $client = new Zend_XmlRpc_Client('http://framework.zend.com/xmlrpc');

    echo $client->call('test.sayHello');

    // hello

Значение XML-RPC, возвращаемое при вызове удаленного метода, будет автоматически приведено к эквивалентному типу в PHP. В примере выше возвращается строка (тип String в PHP), и она уже готова к применению.

Первый параметр метода `call()` принимает имя удаленного метода, вызов которого требуется. Если удаленный метод требует каких-либо параметров, то они могут быть переданы методу `call()` через второй необязательный параметр в виде массива значений для последующей передачи удаленному методу:

### Вызов метода XML-RPC с параметрами

    $client = new Zend_XmlRpc_Client('http://framework.zend.com/xmlrpc');

    $arg1 = 1.1;
    $arg2 = 'foo';

    $result = $client->call('test.sayHello', array($arg1, $arg2));

    // возвращаемый результат имеет "родной" для PHP тип

Если удаленный метод не требует параметров, то этот необязательный параметр можно опустить или передать пустой массив. Массив параметров для удаленного метода может содержать значения "родного" для PHP типа, объекты `Zend_XmlRpc_Value`, либо и то и другое вместе.

Метод `call()` будет автоматически преобразовывать ответ XML-RPC и возвращать его в эквивалентном "родном" для PHP типе. Кроме этого, можно получить объект `Zend_XmlRpc_Response` для возвращенного значения, вызвав метод `getLastResponse()` после вызова `call()`.

## Типы и их преобразование

Некоторые удаленные методы требуют передачи параметров при вызове. Они передаются методу `call()` объекта `Zend_XmlRpc_Client` в виде массива во втором параметре. Любой параметр может быть передан в "родном" для PHP типе, который будет автоматически преобразован в соответствующий тип XML-RPC, или как объект, представляющий определенный тип в XML-RPC (один из объектов `Zend_XmlRpc_Value`).

### Параметры в "родном" для PHP типе

Параметры могут передаваться методу `call()` как переменные "родного" для PHP типа, это могут быть типы String, `integer`, `float`, Boolean, Array или `object`. В этом случае каждый из этих типов будет автоматически определен и преобразован в один из типов XML-RPC согласно следующей таблице:

#### Преобразование типов PHP и XML-RPC

Тип в PHP | Тип в XML-RPC
--- | ---
integer | int
double | double
boolean | boolean
string | string
array | array
array (ассоциативный) | struct
object | array

#### Какому типу будет соответствовать пустой массив?

Передача пустого массива методу XML-RPC несет в себе потенциальную проблему, т.к. он может быть представлен и массивом, и структурой. `Zend_XmlRpc_Client` в этом случае делает запрос к методу сервера `system.methodSignature` для определения требуемого типа аргумента и производит соответствующее преобразование.

Но такое решение само по себе тоже может быть источником проблем. Во-первых, сервера, которые не поддерживают метод `system.methodSignature`, будут журналировать это как ошибочные вызовы, в этом случае `Zend_XmlRpc_Client` будет производить преобразование значения к типу array в XML-RPC. Кроме того, это приводит к дополнительным вызовам к удаленному серверу в случае передачи аргументов в виде массивов.

Для того, чтобы полностью отключить эти вызовы, вы можете вызвать метод `setSkipSystemLookup()` до собственно запроса к методу XML-RPC:

    $client->setSkipSystemLookup(true);
    $result = $client->call('foo.bar', array(array()));

### Параметры в виде объектов Zend_XmlRpc_Value

Параметры могут также создаваться как экземпляры `Zend_XmlRpc_Value` для точного указания типа XML-RPC. Основные причины для этого:

- Вы хотите быть уверенными в том, что процедуре передается корректный тип параметра (т.е. процедура требует целочисленное значение, а вы можете получать его из БД в виде строки)

- Удаленная процедура требует тип `base64` или `dateTime.iso8601` (которых нет среди "родных" для PHP типов).

- Автоматическое преобразование может работать неправильно (например, вы хотите передать пустую структуру XML-RPC в качестве параметра. Пустая структура представляется в PHP пустым массивом, но если вы передаете пустой массив в качестве параметра, то он преобразовывается в массив XML-RPC, т.к. не является ассоциативным массивом)

Есть два способа создания объектов `Zend_XmlRpc_Value` ― непосредственное инстанцирование одного из подклассов `Zend_XmlRpc_Value` и использование статического фабричного метода `Zend_XmlRpc_Value::getXmlRpcValue()`.

#### Объекты Zend_XmlRpc_Value для типов XML-RPC

Тип XML-RPC | Константа `Zend_XmlRpc_Value` | Объект `Zend_XmlRpc_Value`
--- | --- | ---
int | `Zend_XmlRpc_Value::XMLRPC_TYPE_INTEGER` | `Zend_XmlRpc_Value_Integer`
double | `Zend_XmlRpc_Value::XMLRPC_TYPE_DOUBLE` | `Zend_XmlRpc_Value_Double`
boolean | `Zend_XmlRpc_Value::XMLRPC_TYPE_BOOLEAN` | `Zend_XmlRpc_Value_Boolean`
string | `Zend_XmlRpc_Value::XMLRPC_TYPE_STRING` | `Zend_XmlRpc_Value_String`
base64 | `Zend_XmlRpc_Value::XMLRPC_TYPE_BASE64` | `Zend_XmlRpc_Value_Base64`
dateTime.iso8601 | `Zend_XmlRpc_Value::XMLRPC_TYPE_DATETIME` | `Zend_XmlRpc_Value_DateTime`
array | `Zend_XmlRpc_Value::XMLRPC_TYPE_ARRAY` | `Zend_XmlRpc_Value_Array`
struct | `Zend_XmlRpc_Value::XMLRPC_TYPE_STRUCT` | `Zend_XmlRpc_Value_Struct`

#### Автоматическое преобразование

Когда создается новый объект `Zend_XmlRpc_Value`, его значение устанавливается в "родном" для PHP типе. Тип в PHP будет преобразован к определенному типу средствами PHP. Например, если в качестве значения для объекта `Zend_XmlRpc_Value_Integer` была передана строка, то она будет преобразована через `(int)$value`.

## Прокси-объект сервера

Другим способом вызова удаленных методов через клиента XML-RPC является использование "заместителя" сервера. Это PHP-объект, который предоставляет интерфейс к удаленному пространству имен XML-RPC, делая работу с ним максимально близкой к работе с обычным объектом в PHP.

Для того, чтобы инстанцировать "заместителя" сервера, вызовите метод `getProxy()` объекта `Zend_XmlRpc_Client`. Он вернет объект класса `Zend_XmlRpc_Client_ServerProxy`. Любые вызовы методов прокси-объекта сервера будет перенаправлены к удаленному серверу, параметры могут передаваться так же, как и для любых других методов в PHP.

### Прокси-объект к пространству имен по умолчанию

    $client = new Zend_XmlRpc_Client('http://framework.zend.com/xmlrpc');

    // Создание прокси-объекта к пространству имен по умолчанию
    $server = $client->getProxy();

    $hello = $server->test->sayHello(1, 2);
    // test.Hello(1, 2) возвращает "hello"

Метод `getProxy()` принимает необязательный аргумент, указывающий, к какому пространству имен следует создать прокси-объект. Если этот аргумент не был указан, то то будет использоваться пространство имен по умолчанию. В следующем примере используется пространство имен `test`:

### Прокси-объект к произвольному пространству имен

    $client = new Zend_XmlRpc_Client('http://framework.zend.com/xmlrpc');

    // Создание прокси-объекта к пространству имен "test"
    $test  = $client->getProxy('test');

    $hello = $test->sayHello(1, 2);
    // test.Hello(1,2) возвращает "hello"

Если удаленный сервер поддерживает сколько угодно вложенные пространства имен, то они также могут использоваться через прокси-объект сервера. Например, если сервер в примере выше имеет метод `test.foo.bar()`, то он может вызываться следующим образом: `$test->foo->bar()`.

## Обработка ошибок

При вызове методов XML-RPC могут могут быть ошибки двух типов: HTTP и XML-RPC. `Zend_XmlRpc_Client` распознает оба типа, позволяя обнаруживать и отлавливать их независимо друг от друга.

### Ошибки HTTP

Если произошла ошибка HTTP - например, удаленный HTTP-сервер вернул код `404 Not Found`, - то будет сгенерировано исключение `Zend_XmlRpc_Client_HttpException`.

#### Обработка ошибок HTTP

    $client = new Zend_XmlRpc_Client('http://foo/404');

    try {

        $client->call('bar', array($arg1, $arg2));

    } catch (Zend_XmlRpc_HttpException $e) {

        // $e->getCode() возвращает 404
        // $e->getMessage() возвращает "Not Found"

    }

Независимо от того, какой клиент XML-RPC используется, всякий раз, когда происходит ошибка HTTP, генерируется исключение `Zend_XmlRpc_Client_HttpException`.

### Ошибки XML-RPC

Ошибка XML-RPC аналогична исключению в PHP. Это специальный тип, возвращаемый при вызове метода XML-RPC и включающий в себя код и сообщение ошибки. Ошибки XML-RPC обрабатываются по-разному, в зависимости от контекста использования `Zend_XmlRpc_Client`.

Если используется метод `call()` или прокси-объект сервера, то ошибка XML-RPC приведет к тому, что будет сгенерировано исключение `Zend_XmlRpc_Client_FaultException`. Код и сообщение исключения будут в точности соответствовать значениям в возвращенном ответе с сообщением об ошибке.

#### Обработка ошибок XML-RPC

    $client = new Zend_XmlRpc_Client('http://framework.zend.com/xmlrpc');

    try {

        $client->call('badMethod');

    } catch (Zend_XmlRpc_FaultException $e) {

        // $e->getCode() возвращает 1
        // $e->getMessage() возвращает "Unknown method"

    }

Если для выполнения запроса используется метод `call()`, то в случае ошибки будет сгенерировано исключение `Zend_XmlRpc_FaultException`. Объект `Zend_XmlRpc_Response`, содержащий возвращенную ошибку, можно также получить через метод `getLastResponse()`.

Если для выполнения запроса используется метод `doRequest()`, то исключение не генерируется. Вместо этого будет возвращен объект `Zend_XmlRpc_Response`, содержащий возвращенную XML-RPC ошибку. Проверить, содержит ли объект ошибку, можно через метод `isFault()` объекта `Zend_XmlRpc_Response`.

## Интроспекция сервера

Некоторые XML-RPC сервера поддерживают интроспекцию методов под пространством имен `system.`. `Zend_XmlRpc_Client` предоставляет специальную поддержку для серверов с этой возможностью.

Экземпляр `Zend_XmlRpc_Client_ServerIntrospection` может быть получен через вызов метода `getIntrospector()` класса `Zend_XmlRpcClient`. Далее он может использоваться для выполнения операций интроспекции на сервере.

## От запроса к ответу

Метод `call()` экземпляра `Zend_XmlRpc_Client` в процессе выполнения строит объект запроса (`Zend_XmlRpc_Request`) и передает его другому методу `doRequest()`, который возвращает объект ответа (`Zend_XmlRpc_Response`).

Метод `doRequest()` также доступен для непосредственного использования:

### Выполнение запроса

    $client = new Zend_XmlRpc_Client('http://framework.zend.com/xmlrpc');

    $request = new Zend_XmlRpc_Request();
    $request->setMethod('test.sayHello');
    $request->setParams(array('foo', 'bar'));

    $client->doRequest($request);

    // $server->getLastRequest() возвращает экземпляр Zend_XmlRpc_Request
    // $server->getLastResponse() возвращает экземпляр Zend_XmlRpc_Response

После того, как через клиента был вызван метод XML-RPC (через методы `call()`, `doRequest()` или через прокси-объект сервера), всегда можно получить объекты последнего запроса и ответа на него через методы `getLastRequest()` и `getLastResponse()` соответственно.

## HTTP-клиент и тестирование

Ни в одном из предыдущих примеров не указывался HTTP-клиент. В этом случае создается новый экземпляр `Zend_Http_Client` с настройками по умолчанию и автоматически используется клиентом `Zend_XmlRpc_Client`.

HTTP-клиент может быть получен в любое время через метод `getHttpClient()`. В большинстве случаев достаточно использование HTTP-клиента по умолчанию. Тем не менее, метод `setHttpClient()` позволяет установить HTTP-клиент, отличный от принятого по умолчанию.

`setHttpClient()` может быть полезен при unit-тестировании. При совместном использовании с `Zend_Http_Client_Adapter_Test` можно имитировать удаленные сервисы для тестирования. В качестве примера реализации рассмотрите unit-тесты для `Zend_XmlRpc_Client`, входящие в поставку Zend Framework.