<?xml version="1.0" encoding="UTF-8"?>
<!-- Reviewed: no -->
<sect1 id="zend.xmlrpc.client">
    <title>Zend_XmlRpc_Client</title>

    <sect2 id="zend.xmlrpc.client.introduction">
        <title>Introduction</title>

        <para>
            Zend Framework provides support for consuming remote <acronym>XML-RPC</acronym>
            services as a client in the <classname>Zend_XmlRpc_Client</classname>
            package. Its major features include automatic type conversion
            between <acronym>PHP</acronym> and <acronym>XML-RPC</acronym>, a server proxy object,
            and access to server introspection capabilities.
        </para>

    </sect2>

    <sect2 id="zend.xmlrpc.client.method-calls">
        <title>Method Calls</title>

        <para>
            The constructor of <classname>Zend_XmlRpc_Client</classname> receives the
            <acronym>URL</acronym> of the remote <acronym>XML-RPC</acronym> server endpoint as its
            first parameter. The new instance returned may be used to call any number of
            remote methods at that endpoint.
        </para>

        <para>
            To call a remote method with the <acronym>XML-RPC</acronym> client, instantiate it
            and use the <methodname>call()</methodname> instance method. The code sample
            below uses a demonstration <acronym>XML-RPC</acronym> server on the Zend Framework
            website. You can use it for testing or exploring the
            <classname>Zend_XmlRpc</classname> components.
        </para>

        <example id="zend.xmlrpc.client.method-calls.example-1">
            <title>XML-RPC Method Call</title>
            <programlisting language="php"><![CDATA[
$client = new Zend_XmlRpc_Client('http://framework.zend.com/xmlrpc');

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

// hello
]]></programlisting>
        </example>

        <para>
            The <acronym>XML-RPC</acronym> value returned from the remote method call will be
            automatically unmarshaled and cast to the equivalent <acronym>PHP</acronym> native
            type. In the example above, a <acronym>PHP</acronym> <type>String</type> is returned
            and is immediately ready to be used.
        </para>

        <para>
            The first parameter of the <methodname>call()</methodname> method receives the
            name of the remote method to call. If the remote method requires
            any parameters, these can be sent by supplying a second, optional
            parameter to <methodname>call()</methodname> with an <type>Array</type> of
            values to pass to the remote method:
        </para>

        <example id="zend.xmlrpc.client.method-calls.example-2">
            <title>XML-RPC Method Call with Parameters</title>
            <programlisting language="php"><![CDATA[
$client = new Zend_XmlRpc_Client('http://framework.zend.com/xmlrpc');

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

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

// $result is a native PHP type
]]></programlisting>
        </example>

        <para>
            If the remote method doesn't require parameters, this optional
            parameter may either be left out or an empty <methodname>array()</methodname>
            passed to it. The array of parameters for the remote method can
            contain native <acronym>PHP</acronym> types, <classname>Zend_XmlRpc_Value</classname>
            objects, or a mix of each.
        </para>

        <para>
            The <methodname>call()</methodname> method will automatically convert the
            <acronym>XML-RPC</acronym> response and return its equivalent <acronym>PHP</acronym>
            native type. A <classname>Zend_XmlRpc_Response</classname> object for the return value
            will also be available by calling the <methodname>getLastResponse()</methodname>
            method after the call.
        </para>
    </sect2>

    <sect2 id="zend.xmlrpc.value.parameters">
        <title>Types and Conversions</title>
        <para>
            Some remote method calls require parameters. These are given to
            the <methodname>call()</methodname> method of <classname>Zend_XmlRpc_Client</classname>
            as an array in the second parameter. Each parameter may be
            given as either a native <acronym>PHP</acronym> type which will be automatically
            converted, or as an object representing a specific <acronym>XML-RPC</acronym> type
            (one of the <classname>Zend_XmlRpc_Value</classname> objects).
        </para>

        <sect3 id="zend.xmlrpc.value.parameters.php-native">
            <title>PHP Native Types as Parameters</title>
            <para>
                Parameters may be passed to <methodname>call()</methodname> as native <acronym>PHP</acronym>
                variables, meaning as a <type>String</type>,
                <code>integer</code>, <code>float</code>,
                <type>Boolean</type>, <type>Array</type>, or an
                <code>object</code>. In this case, each <acronym>PHP</acronym> native type will
                be auto-detected and converted into one of the <acronym>XML-RPC</acronym> types
                according to this table:
            </para>

            <table id="zend.xmlrpc.value.parameters.php-native.table-1">
                <title>PHP and XML-RPC Type Conversions</title>
                <tgroup cols="2">
                    <thead>
                        <row>
                            <entry><acronym>PHP</acronym> Native Type</entry>
                            <entry><acronym>XML-RPC</acronym> Type</entry>
                        </row>
                    </thead>
                    <tbody>
                        <row>
                            <entry>integer</entry>
                            <entry>int</entry>
                        </row>
                        <row>
                            <entry>double</entry>
                            <entry>double</entry>
                        </row>
                        <row>
                            <entry>boolean</entry>
                            <entry>boolean</entry>
                        </row>
                        <row>
                            <entry>string</entry>
                            <entry>string</entry>
                        </row>
                        <row>
                            <entry>array</entry>
                            <entry>array</entry>
                        </row>
                        <row>
                            <entry>associative array</entry>
                            <entry>struct</entry>
                        </row>
                        <row>
                            <entry>object</entry>
                            <entry>array</entry>
                        </row>
                    </tbody>
                </tgroup>
            </table>

            <note>
                <title>What type do empty arrays get cast to?</title>

                <para>
                    Passing an empty array to an <acronym>XML-RPC</acronym> method is problematic,
                    as it could represent either an array or a struct.
                    <classname>Zend_XmlRpc_Client</classname> detects such conditions and
                    makes a request to the server's
                    <code>system.methodSignature</code> method to determine the
                    appropriate <acronym>XML-RPC</acronym> type to cast to.
                </para>

                <para>
                    However, this in itself can lead to issues. First off,
                    servers that do not support
                    <code>system.methodSignature</code> will log failed
                    requests, and <classname>Zend_XmlRpc_Client</classname> will resort to
                    casting the value to an <acronym>XML-RPC</acronym> array type. Additionally,
                    this means that any call with array arguments will result in
                    an additional call to the remote server.
                </para>

                <para>
                    To disable the lookup entirely, you can call the
                    <methodname>setSkipSystemLookup()</methodname> method prior to making
                    your <acronym>XML-RPC</acronym> call:
                </para>

                <programlisting language="php"><![CDATA[
$client->setSkipSystemLookup(true);
$result = $client->call('foo.bar', array(array()));
]]></programlisting>
            </note>
        </sect3>

        <sect3 id="zend.xmlrpc.value.parameters.xmlrpc-value">
            <title>Zend_XmlRpc_Value Objects as Parameters</title>
            <para>
                Parameters may also be created as <classname>Zend_XmlRpc_Value</classname>
                instances to specify an exact <acronym>XML-RPC</acronym> type. The primary reasons
                for doing this are:

                <itemizedlist>
                    <listitem>
                        <para>
                            When you want to make sure the correct parameter
                            type is passed to the procedure (i.e. the
                            procedure requires an integer and you may get it
                            from a database as a string)
                        </para>
                    </listitem>
                    <listitem>
                        <para>
                            When the procedure requires <code>base64</code> or
                            <code>dateTime.iso8601</code> type (which doesn't exists as a
                            <acronym>PHP</acronym> native type)
                        </para>
                    </listitem>
                    <listitem>
                        <para>
                            When auto-conversion may fail (i.e. you want to
                            pass an empty <acronym>XML-RPC</acronym> struct as a parameter. Empty
                            structs are represented as empty arrays in <acronym>PHP</acronym>
                            but, if you give an empty array as a parameter it
                            will be auto-converted to an <acronym>XML-RPC</acronym> array since
                            it's not an associative array)
                        </para>
                    </listitem>
                </itemizedlist>
            </para>

            <para>
                There are two ways to create a <classname>Zend_XmlRpc_Value</classname>
                object: instantiate one of the <classname>Zend_XmlRpc_Value</classname>
                subclasses directly, or use the static factory method
                <methodname>Zend_XmlRpc_Value::getXmlRpcValue()</methodname>.
            </para>

            <table id="zend.xmlrpc.value.parameters.xmlrpc-value.table-1">
                <title>Zend_XmlRpc_Value Objects for XML-RPC Types</title>
                <tgroup cols="3">
                    <thead>
                        <row>
                            <entry><acronym>XML-RPC</acronym> Type</entry>
                            <entry><classname>Zend_XmlRpc_Value</classname> Constant</entry>
                            <entry><classname>Zend_XmlRpc_Value</classname> Object</entry>
                        </row>
                    </thead>
                    <tbody>
                        <row>
                            <entry>int</entry>
                            <entry>
                                <classname>Zend_XmlRpc_Value::XMLRPC_TYPE_INTEGER</classname>
                            </entry>
                            <entry><classname>Zend_XmlRpc_Value_Integer</classname></entry>
                        </row>
                        <row>
                            <entry>double</entry>
                            <entry>
                                <classname>Zend_XmlRpc_Value::XMLRPC_TYPE_DOUBLE</classname>
                            </entry>
                            <entry><classname>Zend_XmlRpc_Value_Double</classname></entry>
                        </row>
                        <row>
                            <entry>boolean</entry>
                            <entry>
                                <classname>Zend_XmlRpc_Value::XMLRPC_TYPE_BOOLEAN</classname>
                            </entry>
                            <entry><classname>Zend_XmlRpc_Value_Boolean</classname></entry>
                        </row>
                        <row>
                            <entry>string</entry>
                            <entry>
                                <classname>Zend_XmlRpc_Value::XMLRPC_TYPE_STRING</classname>
                            </entry>
                            <entry><classname>Zend_XmlRpc_Value_String</classname></entry>
                        </row>
                        <row>
                            <entry>base64</entry>
                            <entry>
                                <classname>Zend_XmlRpc_Value::XMLRPC_TYPE_BASE64</classname>
                            </entry>
                            <entry><classname>Zend_XmlRpc_Value_Base64</classname></entry>
                        </row>
                        <row>
                            <entry>dateTime.iso8601</entry>
                            <entry>
                                <classname>Zend_XmlRpc_Value::XMLRPC_TYPE_DATETIME</classname>
                            </entry>
                            <entry><classname>Zend_XmlRpc_Value_DateTime</classname></entry>
                        </row>
                        <row>
                            <entry>array</entry>
                            <entry>
                                <classname>Zend_XmlRpc_Value::XMLRPC_TYPE_ARRAY</classname>
                            </entry>
                            <entry><classname>Zend_XmlRpc_Value_Array</classname></entry>
                        </row>
                        <row>
                            <entry>struct</entry>
                            <entry>
                                <classname>Zend_XmlRpc_Value::XMLRPC_TYPE_STRUCT</classname>
                            </entry>
                            <entry><classname>Zend_XmlRpc_Value_Struct</classname></entry>
                        </row>
                    </tbody>
                </tgroup>
            </table>

            <para>
                <note>
                    <title>Automatic Conversion</title>
                    <para>
                        When building a new <classname>Zend_XmlRpc_Value</classname>
                        object, its value is set by a <acronym>PHP</acronym> type. The
                        <acronym>PHP</acronym> type will be converted to the specified type using
                        <acronym>PHP</acronym> casting. For example, if a string is given as a
                        value to the <classname>Zend_XmlRpc_Value_Integer</classname>
                        object, it will be converted using
                        <code>(int)$value</code>.
                    </para>
                </note>
            </para>
        </sect3>
    </sect2>

    <sect2 id="zend.xmlrpc.client.requests-and-responses">
        <title>Server Proxy Object</title>
        <para>
            Another way to call remote methods with the <acronym>XML-RPC</acronym> client is to
            use the server proxy. This is a <acronym>PHP</acronym> object that proxies a remote
            <acronym>XML-RPC</acronym> namespace, making it work as close to a native
            <acronym>PHP</acronym> object as possible.
        </para>

        <para>
            To instantiate a server proxy, call the <methodname>getProxy()</methodname>
            instance method of <classname>Zend_XmlRpc_Client</classname>. This will
            return an instance of <classname>Zend_XmlRpc_Client_ServerProxy</classname>.
            Any method call on the server proxy object will be forwarded to
            the remote, and parameters may be passed like any other <acronym>PHP</acronym>
            method.
        </para>

        <example id="zend.xmlrpc.client.requests-and-responses.example-1">
            <title>Proxy the Default Namespace</title>
            <programlisting language="php"><![CDATA[
$client = new Zend_XmlRpc_Client('http://framework.zend.com/xmlrpc');

$server = $client->getProxy();           // Proxy the default namespace

$hello = $server->test->sayHello(1, 2);  // test.Hello(1, 2) returns "hello"
]]></programlisting>
        </example>

        <para>
            The <methodname>getProxy()</methodname> method receives an optional argument
            specifying which namespace of the remote server to proxy. If it
            does not receive a namespace, the default namespace will be
            proxied. In the next example, the <code>test</code> namespace
            will be proxied:
        </para>

        <example id="zend.xmlrpc.client.requests-and-responses.example-2">
            <title>Proxy Any Namespace</title>
            <programlisting language="php"><![CDATA[
$client = new Zend_XmlRpc_Client('http://framework.zend.com/xmlrpc');

$test  = $client->getProxy('test');     // Proxy the "test" namespace

$hello = $test->sayHello(1, 2);         // test.Hello(1,2) returns "hello"
]]></programlisting>
        </example>

        <para>
            If the remote server supports nested namespaces of any depth,
            these can also be used through the server proxy. For example, if
            the server in the example above had a method
            <code>test.foo.bar()</code>, it could be called as
            <code>$test->foo->bar()</code>.
        </para>
    </sect2>

    <sect2 id="zend.xmlrpc.client.error-handling">
        <title>Error Handling</title>
        <para>
            Two kinds of errors can occur during an <acronym>XML-RPC</acronym> method call:
            <acronym>HTTP</acronym> errors and <acronym>XML-RPC</acronym> faults. The
            <classname>Zend_XmlRpc_Client</classname> recognizes each and provides the ability
            to detect and trap them independently.
        </para>

        <sect3 id="zend.xmlrpc.client.error-handling.http">
            <title>HTTP Errors</title>

            <para>
                If any <acronym>HTTP</acronym> error occurs, such as the remote
                <acronym>HTTP</acronym> server returns a <code>404 Not Found</code>, a
                <classname>Zend_XmlRpc_Client_HttpException</classname> will be thrown.
            </para>

            <example id="zend.xmlrpc.client.error-handling.http.example-1">
                <title>Handling HTTP Errors</title>

                <programlisting language="php"><![CDATA[
$client = new Zend_XmlRpc_Client('http://foo/404');

try {

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

} catch (Zend_XmlRpc_Client_HttpException $e) {

    // $e->getCode() returns 404
    // $e->getMessage() returns "Not Found"

}
]]></programlisting>
            </example>

            <para>
                Regardless of how the <acronym>XML-RPC</acronym> client is used, the
                <classname>Zend_XmlRpc_Client_HttpException</classname> will be thrown
                whenever an <acronym>HTTP</acronym> error occurs.
            </para>
        </sect3>

        <sect3 id="zend.xmlrpc.client.error-handling.faults">
            <title>XML-RPC Faults</title>

            <para>
                An <acronym>XML-RPC</acronym> fault is analogous to a <acronym>PHP</acronym>
                exception. It is a special type returned from an <acronym>XML-RPC</acronym> method
                call that has both an error code and an error message. <acronym>XML-RPC</acronym>
                faults are handled differently depending on the context of how the
                <classname>Zend_XmlRpc_Client</classname> is used.
            </para>

            <para>
                When the <methodname>call()</methodname> method or the server
                proxy object is used, an <acronym>XML-RPC</acronym> fault will result in a
                <classname>Zend_XmlRpc_Client_FaultException</classname> being thrown.
                The code and message of the exception will map directly to
                their respective values in the original <acronym>XML-RPC</acronym> fault
                response.
            </para>

            <example id="zend.xmlrpc.client.error-handling.faults.example-1">
                <title>Handling XML-RPC Faults</title>

                <programlisting language="php"><![CDATA[
$client = new Zend_XmlRpc_Client('http://framework.zend.com/xmlrpc');

try {

    $client->call('badMethod');

} catch (Zend_XmlRpc_Client_FaultException $e) {

    // $e->getCode() returns 1
    // $e->getMessage() returns "Unknown method"

}
]]></programlisting>
            </example>

            <para>
                When the <methodname>call()</methodname> method is used to make the
                request, the <classname>Zend_XmlRpc_Client_FaultException</classname> will be
                thrown on fault. A <classname>Zend_XmlRpc_Response</classname> object
                containing the fault will also be available by calling
                <methodname>getLastResponse()</methodname>.
            </para>

            <para>
                When the <methodname>doRequest()</methodname> method is used to make the
                request, it will not throw the exception. Instead, it will
                return a <classname>Zend_XmlRpc_Response</classname> object returned
                will containing the fault. This can be checked with
                <methodname>isFault()</methodname> instance method of
                <classname>Zend_XmlRpc_Response</classname>.
            </para>
        </sect3>

    </sect2>

    <sect2 id="zend.xmlrpc.client.introspection">
        <title>Server Introspection</title>
        <para>
            Some <acronym>XML-RPC</acronym> servers support the de facto introspection methods
            under the <acronym>XML-RPC</acronym> <code>system.</code> namespace.
            <classname>Zend_XmlRpc_Client</classname> provides special support for servers with
            these capabilities.
        </para>

        <para>
            A <classname>Zend_XmlRpc_Client_ServerIntrospection</classname> instance may be
            retrieved by calling the <methodname>getIntrospector()</methodname> method of
            <classname>Zend_XmlRpcClient</classname>. It can then be used to perform introspection
            operations on the server.
        </para>
    </sect2>

    <sect2 id="zend.xmlrpc.client.request-to-response">
        <title>From Request to Response</title>
        <para>
            Under the hood, the <methodname>call()</methodname> instance method of
            <classname>Zend_XmlRpc_Client</classname> builds a request object
            (<classname>Zend_XmlRpc_Request</classname>) and sends it to another method,
            <methodname>doRequest()</methodname>, that returns a response object
            (<classname>Zend_XmlRpc_Response</classname>).
        </para>

        <para>
            The <methodname>doRequest()</methodname> method is also available for use directly:
        </para>

        <example id="zend.xmlrpc.client.request-to-response.example-1">
            <title>Processing Request to Response</title>

            <programlisting language="php"><![CDATA[
$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() returns instanceof Zend_XmlRpc_Request
// $server->getLastResponse() returns instanceof Zend_XmlRpc_Response
]]></programlisting>
        </example>

        <para>
            Whenever an <acronym>XML-RPC</acronym> method call is made by the client through any
            means, either the <methodname>call()</methodname> method,
            <methodname>doRequest()</methodname> method, or server proxy, the last request
            object and its resultant response object will always be available
            through the methods <methodname>getLastRequest()</methodname> and
            <methodname>getLastResponse()</methodname> respectively.
        </para>
    </sect2>

    <sect2 id="zend.xmlrpc.client.http-client">
        <title>HTTP Client and Testing</title>

        <para>
            In all of the prior examples, an <acronym>HTTP</acronym> client was never specified.
            When this is the case, a new instance of
            <classname>Zend_Http_Client</classname> will be created with its default
            options and used by <classname>Zend_XmlRpc_Client</classname> automatically.
        </para>

        <para>
            The <acronym>HTTP</acronym> client can be retrieved at any time with the
            <methodname>getHttpClient()</methodname> method. For most cases, the default
            <acronym>HTTP</acronym> client will be sufficient. However, the
            <methodname>setHttpClient()</methodname> method allows for a different <acronym>HTTP</acronym>
            client instance to be injected.
        </para>

        <para>
            The <methodname>setHttpClient()</methodname> is particularly useful for unit testing. When combined
            with the <classname>Zend_Http_Client_Adapter_Test</classname>, remote services can be
            mocked out for testing. See the unit tests for <classname>Zend_XmlRpc_Client</classname>
            for examples of how to do this.
        </para>
    </sect2>

</sect1>
<!--
vim:se ts=4 sw=4 et:
-->
