<!-- EN-Revision: 13825 -->
<sect1 id="zend.xmlrpc.client">
    <title>Zend_XmlRpc_Client</title>

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

        <para>Zend Framework possède la capacité de consommer des services distants XML-RPC, via la classe
        <classname>Zend_XmlRpc_Client</classname>. Ses caractéristiques principales sont la conversion automatique des types entre
        PHP et XML-RPC, un objet proxy de serveur, et des possibilités d'introspection du serveur.</para>
    </sect2>

    <sect2 id="zend.xmlrpc.client.method-calls">
        <title>Appels de méthodes</title>

        <para>Le constructeur de <classname>Zend_XmlRpc_Client</classname> reçoit en premier paramètre l'URL du serveur XML-RPC
        distant. L'instance retournée pourra alors être utilisée pour appeler n'importe quelle méthode distante.</para>

        <para>Pour appeler une méthode distante, utilisez la méthode <code>call()</code> de votre instance. Le code
        suivant montre un exemple avec le serveur XML-RPC du site de Zend Framework. Vous pouvez l'utiliser pour tester
        ou explorer les possibilités des composants <classname>Zend_XmlRpc</classname>.</para>

        <example id="zend.xmlrpc.client.method-calls.example-1">
            <title>XML-RPC appel de méthode</title>

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

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

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

        <para>Le type de la valeur XML-RPC retournée sera automatiquement casté en un type compatible PHP. Dans
        l'exemple ci-dessus, une <code>string</code> PHP est retournée et immédiatement utilisable.</para>

        <para>Le premier paramètre de <code>call()</code> est le nom de la méthode distante à appeler. Si celle-ci
        demande des paramètres, ceux-ci doivent alors être passés via le deuxième paramètre de <code>call()</code>, sous
        forme de tableau PHP (<code>array</code>) :</para>

        <example id="zend.xmlrpc.client.method-calls.example-2">
            <title>XML-RPC appel de méthode avec des paramètres</title>

            <programlisting role="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 est un type PHP natif
]]></programlisting>
        </example>

        <para>Le tableau de paramètres peut contenir des types PHP natifs, des objets <classname>Zend_XmlRpc_Value</classname>, ou
        bien les deux à la fois.</para>

        <para>La méthode <code>call()</code> convertira automatiquement la réponse XML-RPC et retournera un type PHP
        natif valide. Un objet <classname>Zend_XmlRpc_Response</classname> pour la valeur de retour sera de même disponible, via
        un appel à <code>getLastResponse()</code>.</para>
    </sect2>

    <sect2 id="zend.xmlrpc.value.parameters">
        <title>Types et conversions</title>

        <para>Certaines méthodes distantes requièrent des paramètres. Ceux-ci sont donnés sous forme de tableau PHP à
        <code>call()</code>. Chaque paramètre est supposé être un type PHP natif qui sera alors lui-même converti, ou
        alors un objet représentant un type XML-RPC (un objet parmi les <classname>Zend_XmlRpc_Value</classname>).</para>

        <sect3 id="zend.xmlrpc.value.parameters.php-native">
            <title>Types PHP natifs comme paramètres</title>

            <para>Les paramètres passés à <code>call()</code> peuvent être d'un type PHP natif, à savoir
            <code>string</code>, <code>integer</code>, <code>float</code>, <code>boolean</code>, <code>array</code>, ou
            <code>object</code>. Dans ce cas, chacun des types sera converti de manière automatique en son type
            compatible XML-RPC, suivant la table suivante :</para>

            <table id="zend.xmlrpc.value.parameters.php-native.table-1">
                <title>PHP et XML-RPC, conversions de types</title>

                <tgroup cols="2">
                    <thead>
                        <row>
                            <entry>Type PHP natif</entry>

                            <entry>XML-RPC 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>Comment est casté un tableau vide ?</title>

                <para>Fournir un tableau vide à une méthode XML-RPC est problématique, car il peut être représenté sous
                la forme soit d'un tableau, soit d'une structure ("struct"). <classname>Zend_XmlRpc_Client</classname> détecte ce
                genre de conditions et fait une requête vers la méthode <code>system.methodSignature</code> du serveur
                pour déterminer le type XML-RPC approprié vers le quel casté.</para>

                <para>Cependant, ceci peut mener malgré tout à des soucis. Premièrement, les serveurs qui ne supportent
                <code>system.methodSignature</code> vont retourner une requête de type échec, et
                <classname>Zend_XmlRpc_Client</classname> résultera en un cast de la valeur de type tableau XML-RPC ("array"). De
                plus, ceci sous-entend que tout appel avec des arguments de type tableau entraîneront un appel
                additionnel au serveur distant.</para>

                <para>Pour désactiver entièrement la recherche, vous pouvez appeler la méthode
                <code>setSkipSystemLookup()</code> avant de réaliser votre appel XML-RPC :</para>

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

        <sect3 id="zend.xmlrpc.value.parameters.xmlrpc-value">
            <title>Objets <classname>Zend_XmlRpc_Value</classname> en tant que paramètres</title>

            <para>Les paramètres peuvent aussi être des objets <classname>Zend_XmlRpc_Value</classname> qui spécifient alors
            exactement un type XML-RPC. Les raisons principales d'utiliser un tel procédé sont : <itemizedlist>
                    <listitem>
                        <para>Lorsque vous voulez être certain du type de paramètre (la méthode attend un entier et vous
                        le récupérez sous forme de chaîne de caractères depuis une base de données).</para>
                    </listitem>

                    <listitem>
                        <para>Lorsque la méthode attend un type <code>base64</code> ou <code>dateTime.iso8601</code>
                        (ceux-ci n'existant pas nativement dans le langage PHP).</para>
                    </listitem>

                    <listitem>
                        <para>Lorsque la conversion de types (cast) peut échouer (vous voulez passer une valeur XML-RPC
                        vide comme paramètre. Mais les valeurs vides en PHP sont représentés sous forme de tableaux
                        vides, or si vous passez un tableau vide à votre méthode <code>call</code>, il va être converti
                        en un tableau XML-RPC, comme ce n'est pas un tableau associatif).</para>
                    </listitem>
                </itemizedlist></para>

            <para>Deux manières existent pour créer des objets <classname>Zend_XmlRpc_Value</classname> : instanciez une
            sous-classe <classname>Zend_XmlRpc_Value</classname> directement, ou utilisez une fabrique ("factory method") telle
            que <classname>Zend_XmlRpc_Value::getXmlRpcValue()</classname>.</para>

            <table id="zend.xmlrpc.value.parameters.xmlrpc-value.table-1">
                <title>Objets <classname>Zend_XmlRpc_Value</classname> comme types XML-RPC</title>

                <tgroup cols="3">
                    <thead>
                        <row>
                            <entry>XML-RPC Type</entry>

                            <entry><classname>Zend_XmlRpc_Value</classname> Constante</entry>

                            <entry><classname>Zend_XmlRpc_Value</classname> Objet</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>Conversion automatique</title>

                    <para>Lorsque vous créez un objet <classname>Zend_XmlRpc_Value</classname>, sa valeur est déterminée par un
                    type PHP. Celui-ci va être converti vers le type désiré en utilisant le cast PHP. Par exemple si une
                    chaîne de caractères est donnée comme valeur à un objet <classname>Zend_XmlRpc_Value_Integer</classname>, elle
                    sera alors convertie suivant la règle <code>(int)$value</code>.</para>
                </note></para>
        </sect3>
    </sect2>

    <sect2 id="zend.xmlrpc.client.requests-and-responses">
        <title>Objet proxy du serveur</title>

        <para>Un autre moyen d'appeler des méthodes avec un client XML-RPC est d'utiliser le proxy du serveur. C'est un
        objet PHP qui proxie un espace de nom XML-RPC, en fonctionnant autant que possible comme les objets PHP.</para>

        <para>Pour instancier un proxy serveur, appelez <code>getProxy()</code> de <classname>Zend_XmlRpc_Client</classname>. Elle
        retourne un objet <classname>Zend_XmlRpc_Client_ServerProxy</classname>. Tout appel de méthode sur l'objet proxy sera
        proxié vers le serveur XML-RPC, et les paramètres seront utilisés comme pour une méthode PHP banale.</para>

        <example id="zend.xmlrpc.client.requests-and-responses.example-1">
            <title>Proxy espace de nom par défaut</title>

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

$server = $client->getProxy();
// Proxy l'espace de nom par défaut

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

        <para>La méthode <code>getProxy()</code> reçoit un argument optionnel désignant l'espace de nom à utiliser par
        le proxy. Par défaut, il s'agit de l'espace général, voici un exemple utilisant un espace de nom
        <code>test</code> :</para>

        <example id="zend.xmlrpc.client.requests-and-responses.example-2">
            <title>Proxy un espace de nom</title>

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

$test  = $client->getProxy('test');
// Proxy l'espace de nommage "test"

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

        <para>Si le serveur distant supporte les espaces de noms imbriqués, alors le proxy les supportera. Par exemple,
        si le serveur dans l'exemple ci-dessus acceptait les espaces de noms imbriqués, alors sa méthode
        <code>test.foo.bar()</code> aurait pu être appelée via <code>$test-&gt;foo-&gt;bar()</code>.</para>
    </sect2>

    <sect2 id="zend.xmlrpc.client.error-handling">
        <title>Gestion des erreurs</title>

        <para>Deux types d'erreurs peuvent être distingués : erreurs HTTP, ou erreurs XML-RPC. L'objet
        <classname>Zend_XmlRpc_Client</classname> reconnaît ces erreurs et fournit les moyens de les repérer et de les
        gérer.</para>

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

            <para>Si une erreur HTTP survient, par exemple le serveur renvoie un <code>404 Not Found</code>, alors une
            <classname>Zend_XmlRpc_Client_HttpException</classname> sera levée.</para>

            <example id="zend.xmlrpc.client.error-handling.http.example-1">
                <title>Gérer les erreurs HTTP</title>

                <programlisting role="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() retourne 404
    // $e->getMessage() retourne "Not Found"

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

            <para>Quelque soit l'utilisation du client XML-RPC, une <classname>Zend_XmlRpc_Client_HttpException</classname> sera
            systématiquement levée lorsqu'une erreur HTTP de quelque type que ce soit est rencontrée.</para>
        </sect3>

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

            <para>Une erreur XML-RPC peut être assimilée à une exception en PHP. C'est un type spécial retourné par une
            des méthodes du client XML-RPC, et ce type contient un message, et un code d'erreur. Les erreurs XML-RPC
            seront gérées différemment en fonction du contexte d'utilisation de l'objet
            <classname>Zend_XmlRpc_Client</classname>.</para>

            <para>Lors de l'utilisation de la méthode <code>call()</code>, ou de l'objet proxy serveur, une erreur
            XML-RPC aura pour effet de lancer une <classname>Zend_XmlRpc_Client_FaultException</classname>. Le code et le message
            de l'exception seront rendus dans leurs valeurs respectives de la réponse XML-RPC.</para>

            <example id="zend.xmlrpc.client.error-handling.faults.example-1">
                <title>Gérer les erreurs XML-RPC</title>

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

try {

    $client->call('badMethod');

} catch (Zend_XmlRpc_Client_FaultException $e) {

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

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

            <para>En utilisant <code>call()</code>, une exception <classname>Zend_XmlRpc_Client_FaultException</classname> sera
            donc lancée si une erreur survient. Un objet <classname>Zend_XmlRpc_Response</classname> contenant l'erreur sera de
            même disponible via la méthode <code>getLastResponse()</code>.</para>

            <para>Lors de l'utilisation de la méthode <code>doRequest()</code>, aucune exception ne sera levée si une
            erreur XML-RPC survient. Simplement, l'objet <classname>Zend_XmlRpc_Response</classname> retourné contiendra l'erreur.
            Vérifiez-en l'état avec <code>isFault()</code>.</para>
        </sect3>
    </sect2>

    <sect2 id="zend.xmlrpc.client.introspection">
        <title>Introspection du serveur</title>

        <para>Certains serveurs XML-RPC supportent l'introspection de leurs méthodes au travers de l'espace de noms
        <code>system.</code> <classname>Zend_XmlRpc_Client</classname> fournit un support d'un tel procédé.</para>

        <para>Une instance de <classname>Zend_XmlRpc_Client_ServerIntrospection</classname> sera retournée si vous appelez la
        méthode <code>getIntrospector()</code> sur l'objet <classname>Zend_XmlRpcClient</classname>.</para>
    </sect2>

    <sect2 id="zend.xmlrpc.client.request-to-response">
        <title>De la requête à la réponse</title>

        <para>Dans les faits, la méthode <code>call()</code> de <classname>Zend_XmlRpc_Client</classname> fabrique un objet
        <classname>Zend_XmlRpc_Request</classname> et l'envoie à une méthode <code>doRequest()</code>, qui retourne un objet de
        réponse <classname>Zend_XmlRpc_Response</classname>.</para>

        <para>La méthode <code>doRequest()</code> est disponible directement si besoin :</para>

        <example id="zend.xmlrpc.client.request-to-response.example-1">
            <title>Effectuer une requête et récupérer une réponse manuellement</title>

            <programlisting role="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() retoure instanceof Zend_XmlRpc_Request
// $server->getLastResponse() retourne instanceof Zend_XmlRpc_Response
]]></programlisting>
        </example>

        <para>Lorsqu'une méthode XML-RPC est appelée, quel qu'en soit le moyen, (<code>call()</code>,
        <code>doRequest()</code> ou proxy serveur), le dernier objet de requête, et son homologue de réponse, seront
        toujours disponibles, au travers des appels à <code>getLastRequest()</code> et
        <code>getLastResponse()</code>.</para>
    </sect2>

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

        <para>Dans tous les exemples utilisés sur cette page, nous ne parlons jamais du client HTTP. Lorsque c'est
        nécessaire, une instance de <classname>Zend_Http_Client</classname> sera créée par défaut et injectée dans
        <classname>Zend_XmlRpc_Client</classname> de manière automatique.</para>

        <para>L'objet client HTTP peut être récupéré à tout moment grâce à la méthode <code>getHttpClient()</code>.
        <code>setHttpClient()</code> permet d'injecter un objet <classname>Zend_Http_Client</classname>.</para>

        <para><code>setHttpClient()</code> est particulièrement utilisée pour les tests unitaires. Lorsque combinée avec
        <classname>Zend_Http_Client_Adapter_Test</classname>, les services Web peuvent être déguisés (émulés) pour les tests.
        Voyez les tests unitaires de <classname>Zend_XmlRpc_Client</classname> pour des exemples concrets.</para>
    </sect2>
</sect1>