From 13be9b602171e3930ff897182319cfc372a73829 Mon Sep 17 00:00:00 2001 From: Gary Hockin Date: Sun, 17 Jan 2016 15:46:09 -0700 Subject: [PATCH] Added base documentation --- doc/book/zend.xmlrpc.client.md | 281 ++++++++++++++++++ doc/book/zend.xmlrpc.intro.md | 54 ++++ doc/book/zend.xmlrpc.server.md | 517 +++++++++++++++++++++++++++++++++ doc/bookdown.json | 9 + 4 files changed, 861 insertions(+) create mode 100644 doc/book/zend.xmlrpc.client.md create mode 100644 doc/book/zend.xmlrpc.intro.md create mode 100644 doc/book/zend.xmlrpc.server.md create mode 100644 doc/bookdown.json diff --git a/doc/book/zend.xmlrpc.client.md b/doc/book/zend.xmlrpc.client.md new file mode 100644 index 0000000..304c80f --- /dev/null +++ b/doc/book/zend.xmlrpc.client.md @@ -0,0 +1,281 @@ +# Zend\\XmlRpc\\Client + +## Introduction + +Zend Framework provides support for consuming remote *XML-RPC* services as a client in the +`Zend\XmlRpc\Client` package. Its major features include automatic type conversion between *PHP* and +*XML-RPC*, a server proxy object, and access to server introspection capabilities. + +## Method Calls + +The constructor of `Zend\XmlRpc\Client` receives the *URL* of the remote *XML-RPC* server endpoint +as its first parameter. The new instance returned may be used to call any number of remote methods +at that endpoint. + +To call a remote method with the *XML-RPC* client, instantiate it and use the `call()` instance +method. The code sample below uses a demonstration *XML-RPC* server on the Zend Framework website. +You can use it for testing or exploring the `Zend\XmlRpc` components. + +**XML-RPC Method Call** + +```php +$client = new Zend\XmlRpc\Client('http://framework.zend.com/xmlrpc'); + +echo $client->call('test.sayHello'); + +// hello +``` + +The *XML-RPC* value returned from the remote method call will be automatically unmarshaled and cast +to the equivalent *PHP* native type. In the example above, a *PHP* `String` is returned and is +immediately ready to be used. + +The first parameter of the `call()` 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 `call()` with an `Array` of values to pass to the remote method: + +**XML-RPC Method Call with Parameters** + +```php +$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 +``` + +If the remote method doesn't require parameters, this optional parameter may either be left out or +an empty `array()` passed to it. The array of parameters for the remote method can contain native +*PHP* types, `Zend\XmlRpc\Value` objects, or a mix of each. + +The `call()` method will automatically convert the *XML-RPC* response and return its equivalent +*PHP* native type. A `Zend\XmlRpc\Response` object for the return value will also be available by +calling the `getLastResponse()` method after the call. + +## Types and Conversions + +Some remote method calls require parameters. These are given to the `call()` method of +`Zend\XmlRpc\Client` as an array in the second parameter. Each parameter may be given as either a +native *PHP* type which will be automatically converted, or as an object representing a specific +*XML-RPC* type (one of the `Zend\XmlRpc\Value` objects). + +### PHP Native Types as Parameters + +Parameters may be passed to `call()` as native *PHP* variables, meaning as a `String`, `Integer`, +`Float`, `Boolean`, `Array`, or an `Object`. In this case, each *PHP* native type will be +auto-detected and converted into one of the *XML-RPC* types according to this table: + +> ## Note +#### What type do empty arrays get cast to? +Passing an empty array to an *XML-RPC* method is problematic, as it could represent either an array +or a struct. `Zend\XmlRpc\Client` detects such conditions and makes a request to the server's +`system.methodSignature` method to determine the appropriate *XML-RPC* type to cast to. +However, this in itself can lead to issues. First off, servers that do not support +`system.methodSignature` will log failed requests, and `Zend\XmlRpc\Client` will resort to casting +the value to an *XML-RPC* array type. Additionally, this means that any call with array arguments +will result in an additional call to the remote server. +To disable the lookup entirely, you can call the `setSkipSystemLookup()` method prior to making your +*XML-RPC* call: +```php +$client-setSkipSystemLookup(true); +$result = $client-call('foo.bar', array(array())); +``` + +### Zend\\XmlRpc\\Value Objects as Parameters + +Parameters may also be created as `Zend\XmlRpc\Value` instances to specify an exact *XML-RPC* type. +The primary reasons for doing this are: + +> - 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) +- When the procedure requires `base64` or `dateTime.iso8601` type (which doesn't exists as a *PHP* +native type) +- When auto-conversion may fail (i.e. you want to pass an empty *XML-RPC* struct as a parameter. +Empty structs are represented as empty arrays in *PHP* but, if you give an empty array as a +parameter it will be auto-converted to an *XML-RPC* array since it's not an associative array) + +There are two ways to create a `Zend\XmlRpc\Value` object: instantiate one of the +`Zend\XmlRpc\Value` subclasses directly, or use the static factory method +`Zend\XmlRpc\AbstractValue::getXmlRpcValue()`. + +> ## Note +#### Automatic Conversion +When building a new `Zend\XmlRpc\Value` object, its value is set by a *PHP* type. The *PHP* type +will be converted to the specified type using *PHP* casting. For example, if a string is given as a +value to the `Zend\XmlRpc\Value\Integer` object, it will be converted using `(int) $value`. + +## Server Proxy Object + +Another way to call remote methods with the *XML-RPC* client is to use the server proxy. This is a +*PHP* object that proxies a remote *XML-RPC* namespace, making it work as close to a native *PHP* +object as possible. + +To instantiate a server proxy, call the `getProxy()` instance method of `Zend\XmlRpc\Client`. This +will return an instance of `Zend\XmlRpc\Client\ServerProxy`. Any method call on the server proxy +object will be forwarded to the remote, and parameters may be passed like any other *PHP* method. + +**Proxy the Default Namespace** + +```php +$client = new Zend\XmlRpc\Client('http://framework.zend.com/xmlrpc'); + +$service = $client->getProxy(); // Proxy the default namespace + +$hello = $service->test->sayHello(1, 2); // test.Hello(1, 2) returns "hello" +``` + +The `getProxy()` 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 'test' namespace will be proxied: + +**Proxy Any Namespace** + +```php +$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" +``` + +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 `test.foo.bar()`, it +could be called as `$test->foo->bar()`. + +## Error Handling + +Two kinds of errors can occur during an *XML-RPC* method call: *HTTP* errors and *XML-RPC* faults. +The `Zend\XmlRpc\Client` recognizes each and provides the ability to detect and trap them +independently. + +### HTTP Errors + +If any *HTTP* error occurs, such as the remote *HTTP* server returns a **404 Not Found**, a +`Zend\XmlRpc\Client\Exception\HttpException` will be thrown. + +**Handling HTTP Errors** + +```php +$client = new Zend\XmlRpc\Client('http://foo/404'); + +try { + + $client->call('bar', array($arg1, $arg2)); + +} catch (Zend\XmlRpc\Client\Exception\HttpException $e) { + + // $e->getCode() returns 404 + // $e->getMessage() returns "Not Found" + +} +``` + +Regardless of how the *XML-RPC* client is used, the `Zend\XmlRpc\Client\Exception\HttpException` +will be thrown whenever an *HTTP* error occurs. + +### XML-RPC Faults + +An *XML-RPC* fault is analogous to a *PHP* exception. It is a special type returned from an +*XML-RPC* method call that has both an error code and an error message. *XML-RPC* faults are handled +differently depending on the context of how the `Zend\XmlRpc\Client` is used. + +When the `call()` method or the server proxy object is used, an *XML-RPC* fault will result in a +`Zend\XmlRpc\Client\Exception\FaultException` being thrown. The code and message of the exception +will map directly to their respective values in the original *XML-RPC* fault response. + +**Handling XML-RPC Faults** + +```php +$client = new Zend\XmlRpc\Client('http://framework.zend.com/xmlrpc'); + +try { + + $client->call('badMethod'); + +} catch (Zend\XmlRpc\Client\Exception\FaultException $e) { + + // $e->getCode() returns 1 + // $e->getMessage() returns "Unknown method" + +} +``` + +When the `call()` method is used to make the request, the +`Zend\XmlRpc\Client\Exception\FaultException` will be thrown on fault. A `Zend\XmlRpc\Response` +object containing the fault will also be available by calling `getLastResponse()`. + +When the `doRequest()` method is used to make the request, it will not throw the exception. Instead, +it will return a `Zend\XmlRpc\Response` object returned will containing the fault. This can be +checked with `isFault()` instance method of `Zend\XmlRpc\Response`. + +## Server Introspection + +Some *XML-RPC* servers support the de facto introspection methods under the *XML-RPC* **system.** +namespace. `Zend\XmlRpc\Client` provides special support for servers with these capabilities. + +A `Zend\XmlRpc\Client\ServerIntrospection` instance may be retrieved by calling the +`getIntrospector()` method of `Zend\XmlRpc\Client`. It can then be used to perform introspection +operations on the server. + +```php +$client = new Zend\XmlRpc\Client('http://example.com/xmlrpcserver.php'); +$introspector = $client->getIntrospector(); +foreach ($introspector->listMethods() as $method) { + echo "Method: " . $method . "\n"; +} +``` + +The following methods are available for introspection: + +- `getSignatureForEachMethod`: Returns the signature for each method on the server +- `getSignatureForEachMethodByMulticall($methods=null)`: Attempt to get the method signatures in one +request via system.multicall(). Optionally pass an array of method names. +- `getSignatureForEachMethodByLooping($methods=null)`: Get the method signatures for every method by +successively calling system.methodSignature. Optionally pass an array of method names +- `getMethodSignature($method)`: Get the method's signature for $method +- `listMethods`: List all methods on the server + +## From Request to Response + +Under the hood, the `call()` instance method of `Zend\XmlRpc\Client` builds a request object +(`Zend\XmlRpc\Request`) and sends it to another method, `doRequest()`, that returns a response +object (`Zend\XmlRpc\Response`). + +The `doRequest()` method is also available for use directly: + +**Processing Request to Response** + +```php +$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); + +// $client->getLastRequest() returns instanceof Zend\XmlRpc\Request +// $client->getLastResponse() returns instanceof Zend\XmlRpc\Response +``` + +Whenever an *XML-RPC* method call is made by the client through any means, either the `call()` +method, `doRequest()` method, or server proxy, the last request object and its resultant response +object will always be available through the methods `getLastRequest()` and `getLastResponse()` +respectively. + +## HTTP Client and Testing + +In all of the prior examples, an *HTTP* client was never specified. When this is the case, a new +instance of `Zend\Http\Client` will be created with its default options and used by +`Zend\XmlRpc\Client` automatically. + +The *HTTP* client can be retrieved at any time with the `getHttpClient()` method. For most cases, +the default *HTTP* client will be sufficient. However, the `setHttpClient()` method allows for a +different *HTTP* client instance to be injected. + +The `setHttpClient()` is particularly useful for unit testing. When combined with the +`Zend\Http\Client\Adapter\Test`, remote services can be mocked out for testing. See the unit tests +for `Zend\XmlRpc\Client` for examples of how to do this. diff --git a/doc/book/zend.xmlrpc.intro.md b/doc/book/zend.xmlrpc.intro.md new file mode 100644 index 0000000..4b9df93 --- /dev/null +++ b/doc/book/zend.xmlrpc.intro.md @@ -0,0 +1,54 @@ +# Introduction to Zend\\XmlRpc + +From its [home page](http://www.xmlrpc.com/), *XML-RPC* is described as a "...remote procedure +calling using *HTTP* as the transport and *XML* as the encoding. *XML-RPC* is designed to be as +simple as possible, while allowing complex data structures to be transmitted, processed and +returned." + +Zend Framework provides support for both consuming remote *XML-RPC* services and building new +*XML-RPC* servers. + +## Quick Start + +To show how easy is to create *XML-RPC* services with `Zend\XmlRpc\Server`, take a look at the +following example: + +```php +class Greeter +{ + + /** + * Say hello to someone. + * + * @param string $name Who to greet + * @return string + */ + public function sayHello($name='Stranger') + { + return sprintf("Hello %s!", $name); + } +} + +$server = new Zend\XmlRpc\Server; +// Our Greeter class will be called +// greeter from the client +$server->setClass('Greeter', 'greeter'); +$server->handle(); +``` + +> ## Note +It is necessary to write function and method docblocks for the services which are to be exposed via +`Zend\XmlRpc\Server`, as it will be used to validate parameters provided to the methods, and also to +determine the method help text and method signatures. + +An example of a client consuming this *XML-RPC* service would be something like this: + +```php +$client = new Zend\XmlRpc\Client('http://example.com/xmlrpcserver.php'); + +echo $client->call('greeter.sayHello'); +// will output "Hello Stranger!" + +echo $client->call('greeter.sayHello', array('Dude')); +// will output "Hello Dude!" +``` diff --git a/doc/book/zend.xmlrpc.server.md b/doc/book/zend.xmlrpc.server.md new file mode 100644 index 0000000..623898b --- /dev/null +++ b/doc/book/zend.xmlrpc.server.md @@ -0,0 +1,517 @@ +# Zend\\XmlRpc\\Server + +## Introduction + +`Zend\XmlRpc\Server` is intended as a fully-featured *XML-RPC* server, following [the specifications +outlined at www.xmlrpc.com](http://www.xmlrpc.com/spec). Additionally, it implements the +`system.multicall()` method, allowing boxcarring of requests. + +## Basic Usage + +An example of the most basic use case: + +```php +$server = new Zend\XmlRpc\Server(); +$server->setClass('My\Service\Class'); +echo $server->handle(); +``` + +## Server Structure + +`Zend\XmlRpc\Server` is composed of a variety of components, ranging from the server itself to +request, response, and fault objects. + +To bootstrap `Zend\XmlRpc\Server`, the developer must attach one or more classes or functions to the +server, via the `setClass()` and `addFunction()` methods. + +Once done, you may either pass a `Zend\XmlRpc\Request` object to `Zend\XmlRpc\Server::handle()`, or +it will instantiate a `Zend\XmlRpc\Request\Http` object if none is provided -- thus grabbing the +request from `php://input`. + +`Zend\XmlRpc\Server::handle()` then attempts to dispatch to the appropriate handler based on the +method requested. It then returns either a `Zend\XmlRpc\Response`-based object or a +`Zend\XmlRpc\Server\Fault` +object. These objects both have `__toString()` methods that create valid *XML-RPC* *XML* responses, +allowing them to be directly echoed. + +## Anatomy of a webservice + +### General considerations + +For maximum performance it is recommended to use a simple bootstrap file for the server component. +Using `Zend\XmlRpc\Server` inside a \[Zend\\Mvc\\Controller\](zend.mvc.controllers) is strongly +discouraged to avoid the overhead. + +Services change over time and while webservices are generally less change intense as code-native +*APIs*, it is recommended to version your service. Do so to lay grounds to provide compatibility for +clients using older versions of your service and manage your service lifecycle including deprecation +timeframes. To do so just include a version number into your *URI*. It is also recommended to +include the remote protocol name in the *URI* to allow easy integration of upcoming remoting +technologies. `http://myservice.ws/1.0/XMLRPC/`. + +### What to expose? + +Most of the time it is not sensible to expose business objects directly. Business objects are +usually small and under heavy change, because change is cheap in this layer of your application. +Once deployed and adopted, web services are hard to change. Another concern is *I/O* and latency: +the best webservice calls are those not happening. Therefore service calls need to be more +coarse-grained than usual business logic is. Often an additional layer in front of your business +objects makes sense. This layer is sometimes referred to as [Remote +Facade](http://martinfowler.com/eaaCatalog/remoteFacade.html). Such a service layer adds a coarse +grained interface on top of your business logic and groups verbose operations into smaller ones. + +## Conventions + +`Zend\XmlRpc\Server` allows the developer to attach functions and class method calls as dispatchable +*XML-RPC* methods. Via `Zend\Server\Reflection`, it does introspection on all attached methods, +using the function and method docblocks to determine the method help text and method signatures. + +*XML-RPC* types do not necessarily map one-to-one to *PHP* types. However, the code will do its best +to guess the appropriate type based on the values listed in @param and @return lines. Some *XML-RPC* +types have no immediate *PHP* equivalent, however, and should be hinted using the *XML-RPC* type in +the PHPDoc. These include: + +- **dateTime.iso8601**, a string formatted as '`YYYYMMDDTHH:mm:ss`' +- **base64**, base64 encoded data +- **struct**, any associative array + +An example of how to hint follows: + +```php +/** +* This is a sample function +* +* @param base64 $val1 Base64-encoded data +* @param dateTime.iso8601 $val2 An ISO date +* @param struct $val3 An associative array +* @return struct +*/ +function myFunc($val1, $val2, $val3) +{ +} +``` + +PhpDocumentor does no validation of the types specified for params or return values, so this will +have no impact on your *API* documentation. Providing the hinting is necessary, however, when the +server is validating the parameters provided to the method call. + +It is perfectly valid to specify multiple types for both params and return values; the *XML-RPC* +specification even suggests that system.methodSignature should return an array of all possible +method signatures (i.e., all possible combinations of param and return values). You may do so just +as you normally would with PhpDocumentor, using the '|' operator: + +```php +/** +* This is a sample function +* +* @param string|base64 $val1 String or base64-encoded data +* @param string|dateTime.iso8601 $val2 String or an ISO date +* @param array|struct $val3 Normal indexed array or an associative array +* @return boolean|struct +*/ +function myFunc($val1, $val2, $val3) +{ +} +``` + +> ## Note +Allowing multiple signatures can lead to confusion for developers using the services; to keep things +simple, a *XML-RPC* service method should only have a single signature. + +## Utilizing Namespaces + +*XML-RPC* has a concept of namespacing; basically, it allows grouping *XML-RPC* methods by +dot-delimited namespaces. This helps prevent naming collisions between methods served by different +classes. As an example, the *XML-RPC* server is expected to server several methods in the 'system' +namespace: + +- system.listMethods +- system.methodHelp +- system.methodSignature + +Internally, these map to the methods of the same name in `Zend\XmlRpc\Server`. + +If you want to add namespaces to the methods you serve, simply provide a namespace to the +appropriate method when attaching a function or class: + +```php +// All public methods in My_Service_Class will be accessible as +// myservice.METHODNAME +$server->setClass('My\Service\Class', 'myservice'); + +// Function 'somefunc' will be accessible as funcs.somefunc +$server->addFunction('somefunc', 'funcs'); +``` + +## Custom Request Objects + +Most of the time, you'll simply use the default request type included with `Zend\XmlRpc\Server`, +`Zend\XmlRpc\Request\Http`. However, there may be times when you need *XML-RPC* to be available via +the *CLI*, a *GUI*, or other environment, or want to log incoming requests. To do so, you may create +a custom request object that extends `Zend\XmlRpc\Request`. The most important thing to remember is +to ensure that the `getMethod()` and `getParams()` methods are implemented so that the *XML-RPC* +server can retrieve that information in order to dispatch the request. + +## Custom Responses + +Similar to request objects, `Zend\XmlRpc\Server` can return custom response objects; by default, a +`Zend\XmlRpc\Response\Http` object is returned, which sends an appropriate Content-Type *HTTP* +header for use with *XML-RPC*. Possible uses of a custom object would be to log responses, or to +send responses back to `STDOUT`. + +To use a custom response class, use `Zend\XmlRpc\Server::setResponseClass()` prior to calling +`handle()`. + +## Handling Exceptions via Faults + +`Zend\XmlRpc\Server` catches Exceptions generated by a dispatched method, and generates an *XML-RPC* +fault response when such an exception is caught. By default, however, the exception messages and +codes are not used in a fault response. This is an intentional decision to protect your code; many +exceptions expose more information about the code or environment than a developer would necessarily +intend (a prime example includes database abstraction or access layer exceptions). + +Exception classes can be whitelisted to be used as fault responses, however. To do so, simply +utilize `Zend\XmlRpc\Server\Fault::attachFaultException()` to pass an exception class to whitelist: + +```php +Zend\XmlRpc\Server\Fault::attachFaultException('My\Project\Exception'); +``` + +If you utilize an exception class that your other project exceptions inherit, you can then whitelist +a whole family of exceptions at a time. `Zend\XmlRpc\Server\Exception`s are always whitelisted, to +allow reporting specific internal errors (undefined methods, etc.). + +Any exception not specifically whitelisted will generate a fault response with a code of '404' and a +message of 'Unknown error'. + +## Caching Server Definitions Between Requests + +Attaching many classes to an *XML-RPC* server instance can utilize a lot of resources; each class +must introspect using the Reflection *API* (via `Zend\Server\Reflection`), which in turn generates a +list of all possible method signatures to provide to the server class. + +To reduce this performance hit somewhat, `Zend\XmlRpc\Server\Cache` can be used to cache the server +definition between requests. When combined with `__autoload()`, this can greatly increase +performance. + +An sample usage follows: + +```php +use Zend\XmlRpc\Server as XmlRpcServer; + +// Register the "My\Services" namespace +$loader = new Zend\Loader\StandardAutoloader(); +$loader->registerNamespace('My\Services', 'path to My/Services'); +$loader->register(); + +$cacheFile = dirname(__FILE__) . '/xmlrpc.cache'; +$server = new XmlRpcServer(); + +if (!XmlRpcServer\Cache::get($cacheFile, $server)) { + + $server->setClass('My\Services\Glue', 'glue'); // glue. namespace + $server->setClass('My\Services\Paste', 'paste'); // paste. namespace + $server->setClass('My\Services\Tape', 'tape'); // tape. namespace + + XmlRpcServer\Cache::save($cacheFile, $server); +} + +echo $server->handle(); +``` + +The above example attempts to retrieve a server definition from `xmlrpc.cache` in the same directory +as the script. If unsuccessful, it loads the service classes it needs, attaches them to the server +instance, and then attempts to create a new cache file with the server definition. + +## Usage Examples + +Below are several usage examples, showing the full spectrum of options available to developers. +Usage examples will each build on the previous example provided. + +**Basic Usage** + +The example below attaches a function as a dispatchable *XML-RPC* method and handles incoming calls. + +```php +/** + * Return the MD5 sum of a value + * + * @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(); +``` + +**Attaching a class** + +The example below illustrates attaching a class' public methods as dispatchable *XML-RPC* methods. + +```php +require_once 'Services/Comb.php'; + +$server = new Zend\XmlRpc\Server(); +$server->setClass('Services\Comb'); +echo $server->handle(); +``` + +**Attaching a class with arguments** + +The following example illustrates how to attach a class' public methods and passing arguments to its +methods. This can be used to specify certain defaults when registering service classes. + +```php +namespace Services; + +class PricingService +{ + /** + * Calculate current price of product with $productId + * + * @param ProductRepository $productRepository + * @param PurchaseRepository $purchaseRepository + * @param integer $productId + */ + public function calculate(ProductRepository $productRepository, + PurchaseRepository $purchaseRepository, + $productId) + { + ... + } +} + +$server = new Zend\XmlRpc\Server(); +$server->setClass('Services\PricingService', + 'pricing', + new ProductRepository(), + new PurchaseRepository()); +``` + +The arguments passed at `setClass()` at server construction time are injected into the method call +`pricing.calculate()` on remote invokation. In the example above, only the argument `$purchaseId` is +expected from the client. + +**Passing arguments only to constructor** + +`Zend\XmlRpc\Server` allows to restrict argument passing to constructors only. This can be used for +constructor dependency injection. To limit injection to constructors, call +`sendArgumentsToAllMethods` and pass `FALSE` as an argument. This disables the default behavior of +all arguments being injected into the remote method. In the example below the instance of +`ProductRepository` and `PurchaseRepository` is only injected into the constructor of +`Services_PricingService2`. + +```php +class Services\PricingService2 +{ + /** + * @param ProductRepository $productRepository + * @param PurchaseRepository $purchaseRepository + */ + public function __construct(ProductRepository $productRepository, + PurchaseRepository $purchaseRepository) + { + ... + } + + /** + * Calculate current price of product with $productId + * + * @param integer $productId + * @return double + */ + public function calculate($productId) + { + ... + } +} + +$server = new Zend\XmlRpc\Server(); +$server->sendArgumentsToAllMethods(false); +$server->setClass('Services\PricingService2', + 'pricing', + new ProductRepository(), + new PurchaseRepository()); +``` + +**Attaching a class instance** + +`setClass()` allows to register a previously instantiated class at the server. Just pass an instance +instead of the class name. Obviously passing arguments to the constructor is not possible with +pre-instantiated classes. + +**Attaching several classes using namespaces** + +The example below illustrates attaching several classes, each with their own namespace. + +```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'); // methods called as comb.* +$server->setClass('Services\Brush', 'brush'); // methods called as brush.* +$server->setClass('Services\Pick', 'pick'); // methods called as pick.* +echo $server->handle(); +``` + +**Specifying exceptions to use as valid fault responses** + +The example below allows any `Services\Exception`-derived class to report its code and message in +the fault response. + +```php +require_once 'Services/Exception.php'; +require_once 'Services/Comb.php'; +require_once 'Services/Brush.php'; +require_once 'Services/Pick.php'; + +// Allow Services_Exceptions to report as fault responses +Zend\XmlRpc\Server\Fault::attachFaultException('Services\Exception'); + +$server = new Zend\XmlRpc\Server(); +$server->setClass('Services\Comb', 'comb'); // methods called as comb.* +$server->setClass('Services\Brush', 'brush'); // methods called as brush.* +$server->setClass('Services\Pick', 'pick'); // methods called as pick.* +echo $server->handle(); +``` + +**Utilizing custom request and response objects** + +Some use cases require to utilize a custom request object. For example, *XML/RPC* is not bound to +*HTTP* as a transfer protocol. It is possible to use other transfer protocols like *SSH* or telnet +to send the request and response data over the wire. Another use case is authentication and +authorization. In case of a different transfer protocol, one need to change the implementation to +read request data. + +The example below instantiates a custom request class and passes it to the server to handle. + +```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'; + +// Allow Services_Exceptions to report as fault responses +Zend\XmlRpc\Server\Fault::attachFaultException('Services\Exception'); + +$server = new Zend\XmlRpc\Server(); +$server->setClass('Services\Comb', 'comb'); // methods called as comb.* +$server->setClass('Services\Brush', 'brush'); // methods called as brush.* +$server->setClass('Services\Pick', 'pick'); // methods called as pick.* + +// Create a request object +$request = new Services\Request(); + +echo $server->handle($request); +``` + +**Specifying a custom response class** + +The example below illustrates specifying a custom response class for the returned response. + +```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'; + +// Allow Services_Exceptions to report as fault responses +Zend\XmlRpc\Server\Fault::attachFaultException('Services\Exception'); + +$server = new Zend\XmlRpc\Server(); +$server->setClass('Services\Comb', 'comb'); // methods called as comb.* +$server->setClass('Services\Brush', 'brush'); // methods called as brush.* +$server->setClass('Services\Pick', 'pick'); // methods called as pick.* + +// Create a request object +$request = new Services\Request(); + +// Utilize a custom response +$server->setResponseClass('Services\Response'); + +echo $server->handle($request); +``` + +## Performance optimization + +**Cache server definitions between requests** + +The example below illustrates caching server definitions between requests. + +```php +use Zend\XmlRpc\Server as XmlRpcServer; + +// Register the "Services" namespace +$loader = new Zend\Loader\StandardAutoloader(); +$loader->registerNamespace('Services', 'path to Services'); +$loader->register(); + +// Specify a cache file +$cacheFile = dirname(__FILE__) . '/xmlrpc.cache'; + +// Allow Services\Exceptions to report as fault responses +XmlRpcServer\Fault::attachFaultException('Services\Exception'); + +$server = new XmlRpcServer(); + +// Attempt to retrieve server definition from cache +if (!XmlRpcServer\Cache::get($cacheFile, $server)) { + $server->setClass('Services\Comb', 'comb'); // methods called as comb.* + $server->setClass('Services\Brush', 'brush'); // methods called as brush.* + $server->setClass('Services\Pick', 'pick'); // methods called as pick.* + + // Save cache + XmlRpcServer\Cache::save($cacheFile, $server); +} + +// Create a request object +$request = new Services\Request(); + +// Utilize a custom response +$server->setResponseClass('Services\Response'); + +echo $server->handle($request); +``` + +> ## Note +The server cache file should be located outside the document root. + +**Optimizing XML generation** + +`Zend\XmlRpc\Server` uses `DOMDocument` of *PHP* extension **ext/dom** to generate it's *XML* +output. While **ext/dom** is available on a lot of hosts it is not exactly the fastest. Benchmarks +have shown, that `XmlWriter` from **ext/xmlwriter** performs better. + +If **ext/xmlwriter** is available on your host, you can select a the `XmlWriter`-based generator to +leverage the performance differences. + +```php +use Zend\XmlRpc; + +XmlRpc\AbstractValue::setGenerator(new XmlRpc\Generator\XmlWriter()); + +$server = new XmlRpc\Server(); +... +``` + +> ## Note +#### Benchmark your application +Performance is determined by a lot of parameters and benchmarks only apply for the specific test +case. Differences come from *PHP* version, installed extensions, webserver and operating system just +to name a few. Please make sure to benchmark your application on your own and decide which generator +to use based on **your** numbers. + +> ## Note +#### Benchmark your client +This optimization makes sense for the client side too. Just select the alternate *XML* generator +before doing any work with `Zend\XmlRpc\Client`. diff --git a/doc/bookdown.json b/doc/bookdown.json new file mode 100644 index 0000000..d587693 --- /dev/null +++ b/doc/bookdown.json @@ -0,0 +1,9 @@ +{ + "title": "Zend\\Xmlrpc", + "target": "html/", + "content": [ + "book/zend.xmlrpc.intro.md", + "book/zend.xmlrpc.client.md", + "book/zend.xmlrpc.server.md" + ] +} \ No newline at end of file