Historically, every time there’s been a need for two systems to communicate, a new protocol has been created (for example, SMTP for sending mail, POP3 for receiving mail, and the numerous protocols that database clients and servers use). The idea of web services is to remove the need to create new protocols by providing a standardized mechanism for remote procedure calls, based on XML and HTTP.
Web services make it easy to integrate heterogeneous systems. Say you’re writing a web interface to a library system that already exists. It has a complex system of database tables, and lots of business logic embedded in the program code that manipulates those tables. And it’s written in C++. You could reimplement the business logic in PHP, writing a lot of code to manipulate tables in the correct way, or you could write a little code in C++ to expose the library operations (e.g., check out a book to a user, see when this book is due back, see what the overdue fines are for this user) as a web service. Now your PHP code simply has to handle the web frontend; it can use the library service to do all the heavy lifting.
A RESTful web service is a loose description of web APIs implemented using HTTP and the principles of REST. It refers to a collection of resources, along with basic operations a client can perform on those resources through the API.
For example, an API might describe a collection of authors and the books to which those authors have contributed. The data within each object type is arbitrary. In this case, a “resource” is each individual author, each individual book, and the collections of all authors, all books, and the books to which each author has contributed. Each resource must have a unique identifier so calls into the API know what resource is being retrieved or acted upon.
You might represent a simple set of classes to represent the book and author resources, as in Example 16-1.
Because HTTP was built with the REST architecture in mind, it provides a set of “verbs” that you use to interact with the API. We’ve already seen GET
and POST
verbs, which websites often use to represent “retrieve data” and “perform an action,” respectively. RESTful web services introduce two additional verbs, PUT
and DELETE
:
GET
POST
PUT
DELETE
For example, the Books
and Authors
API might consist of the following REST endpoints, based on the data contained within the object classes:
GET /api/authors
POST /api/authors
GET /api/authors/
id
id
from the collection and return it.PUT /api/authors/
id
id
, update that author’s information in the collection.DELETE /api/authors/
id
id
from the collection.GET /api/authors/
id
/books
id
has contributed.POST /api/authors/
id
/books
id
.GET /api/books/
id
id
from the collection and return it.The GET
, POST
, PUT
, and DELETE
verbs provided by RESTful web services can be thought of as roughly equivalent to the create, retrieve, update, and delete (CRUD) operations typical to a database, although they can correlate to collections, not just entities as is typical with CRUD implementations.
In each of the preceding API endpoints, the HTTP status code is used to provide the result of the request. HTTP provides a long list of standard status codes: for example, 201 Created
would be returned when you create a resource, and 501 Not Implemented
would be returned when you send a request to an endpoint that doesn’t exist.
While the full list of HTTP codes is beyond the scope of this chapter, some common ones include:
200 OK
201 Created
400 Bad Request
401 Unauthorized
403 Forbidden
, represents a valid request, but one that could not be completed due to a lack of permissions. Typically, this response indicates that authorization is required but has not yet been provided.403 Forbidden
401 Unauthorized
, this response indicates a valid request, but one that could not be completed due to a lack of permissions. Typically, this response indicates that authorization was available but that the user lacks permission to perform the requested action.404 Not Found
500 Internal Server Error
These codes are mere guidelines and typical responses; the exact responses provided by a RESTful API are detailed by the API itself.
Retrieving information for a resource involves a straightforward GET
request. Example 16-2 uses the curl extension to format an HTTP request, set parameters on it, send the request, and get the returned information.
To retrieve information about an author, this script first constructs a URL representing the endpoint for the resource. Then, it initializes a curl resource and provides the constructed URL to it. Finally, the curl object is executed, which sends the HTTP request, waits for the response, and returns it.
In this case, the response is JSON data, which is decoded and handed off to a Factory
method of Author
to construct an instance of the Author
class.
Updating an existing resource is a bit trickier than retrieving information about a resource. In this case, you need to use the PUT
verb. As PUT
was originally intended to handle file uploads, PUT
requests require that you stream data to the remote service from a file.
Rather than creating a file on disk and streaming from it, the script in Example 16-3 uses the 'memory'
stream provided by PHP, first filling it with the data to send, then rewinding it to the start of the data it just wrote, and finally pointing the curl object at the file.
$bookID = "ProgrammingPHP"; $url = "http://example.com/api/books/{$bookID}"; $data = json_encode(array( 'edition' => 4, )); $requestData = http_build_query($data, '', '&'); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); $fh = fopen("php://memory", 'rw'); fwrite($fh, $requestData); rewind($fh); curl_setopt($ch, CURLOPT_INFILE, $fh); curl_setopt($ch, CURLOPT_INFILESIZE, mb_strlen($requestData)); curl_setopt($ch, CURLOPT_PUT, true); $response = curl_exec($ch); $resultInfo = curl_getinfo($ch); curl_close($ch); fclose($fh);
To create a new resource, call the appropriate endpoint with the POST
verb. The data for the request is put into the typical key-value form for POST
requests.
In Example 16-4, the Author
API endpoint for creating a new author takes the information to create the new author as a JSON-formatted object under the key 'data'
.
<?php $newAuthor = new Author('pbmacintyre'); $newAuthor->name = "Peter Macintyre"; $url = "http://example.com/api/authors"; $data = array( 'data' => json_encode($newAuthor) ); $requestData = http_build_query($data, '', '&'); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POSTFIELDS, $requestData); curl_setopt($ch, CURLOPT_POST, true); $response = curl_exec($ch); $resultInfo = curl_getinfo($ch); curl_close($ch);
This script first constructs a new Author
instance and encodes its values as a JSON-formatted string. Then, it constructs the key-value data in the appropriate format, provides that data to the curl object, and sends the request.
Deleting a resource is similarly straightforward. Example 16-5 creates a request, sets the verb on that request to 'DELETE'
via the curl_setopt()
function, and sends it.
<?php $authorID = "ktatroe"; $bookID = "ProgrammingPHP"; $url = "http://example.com/api/authors/{$authorID}/books/{$bookID}"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); $result = curl_exec($ch); $resultInfo = curl_getinfo($ch); curl_close($ch);
While less popular nowadays than REST, XML-RPC and SOAP are two older, standard protocols used to create web services. XML-RPC is the older and simpler of the two, while SOAP is newer and more complex.
PHP provides access to both SOAP and XML-RPC through the xmlrpc extension, which is based on the xmlrpc-epi project (http://xmlrpc-epi.sourceforge.net). The xmlrpc extension is not compiled in by default, so you’ll need to add --with-xmlrpc
to your configure
line when you compile PHP.
Example 16-6 shows a very basic XML-RPC server that exposes only one function (which XML-RPC calls a “method”). That function, multiply()
, multiplies two numbers and returns the result. It’s not a very exciting example, but it shows the basic structure of an XML-RPC server.
<?php // expose this function via RPC as "multiply()" function times ($method, $args) { return $args[0] * $args[1]; } $request = $HTTP_RAW_POST_DATA; if (!$request) { $requestXml = $_POST['xml']; } $server = xmlrpc_server_create() or die("Couldn't create server"); xmlrpc_server_register_method($server, "multiply", "times"); $options = array( 'output_type' => 'xml', 'version' => 'auto', ); echo xmlrpc_server_call_method($server, $request, null, $options); xmlrpc_server_destroy($server);
The xmlrpc extension handles the dispatch for you. That is, it works out which method the client was trying to call, decodes the arguments, and calls the corresponding PHP function. It then returns an XML response that encodes any values returned by the function that can be decoded by an XML-RPC client.
Create a server with xmlrpc_server_create()
:
$server = xmlrpc_server_create();
Once you’ve created a server, expose functions through the XML-RPC dispatch mechanism using xmlrpc_server_register_method()
:
xmlrpc_server_register_method(server, method, function);
The method
parameter is the name the XML-RPC client knows. The function
parameter is the PHP function implementing that XML-RPC method. In the case of Example 16-6, the multiply()
XML-RPC client method is implemented by the times()
function in PHP. Often a server will call xmlrpc_server_register_method()
many times to expose many functions.
When you’ve registered all your methods, call xmlrpc_server_call_method()
to dispatch the incoming request to the appropriate function:
$response = xmlrpc_server_call_method(server, request, user_data [, options]);
The request
is the XML-RPC request, which is typically sent as HTTP POST
data. We fetch that through the $HTTP_RAW_POST_DATA
variable. It contains the name of the method to be called, and parameters to that method. The parameters are decoded into PHP data types, and the function (times()
, in this case) is called.
A function exposed as an XML-RPC method takes two or three parameters:
$retval = exposedFunction(method, args [, user_data]);
The method
parameter contains the name of the XML-RPC method (so you can have one PHP function exposed under many names). The arguments to the method are passed in the array args
, and the optional user_data
parameter is whatever the xmlrpc_server_call_method()
function’s user_data
parameter was.
The options
parameter to xmlrpc_server_call_method()
is an array mapping option names to their values. The options are:
output_type
"php"
or "xml"
(default).verbosity
"no_white_space"
, "newlines_only"
, and "pretty"
(default).escaping
"cdata"
, "non-ascii"
(default), "non-print"
(default), and "markup"
(default).versioning
"simple"
, "soap 1.1"
, "xmlrpc"
(default for clients), and "auto"
(default for servers, meaning “whatever format the request came in”).encoding
"iso-8859-1"
(the default).An XML-RPC client issues an HTTP request and parses the response. The xmlrpc extension that ships with PHP can work with the XML that encodes an XML-RPC request, but it doesn’t know how to issue HTTP requests. For that functionality, you must download the xmlrpc-epi distribution (http://xmlrpc-epi.sourceforge.net) and install the sample/utils/utils.php file. This file contains a function to perform the HTTP request.
Example 16-7 shows a client for the multiply
XML-RPC service.
<?php require_once("utils.php"); $options = array('output_type' => "xml", 'version' => "xmlrpc"); $result = xu_rpc_http_concise( array( 'method' => "multiply", 'args' => array(5, 6), 'host' => "192.168.0.1", 'uri' => "/~gnat/test/ch11/xmlrpc-server.php", 'options' => $options, ) ); echo "5 * 6 is {$result}";
We begin by loading the XML-RPC convenience utilities library. This gives us the xu_rpc_http_concise()
function, which constructs a POST
request for us:
$response = xu_rpc_http_concise(hash);
The hash
array contains the various attributes of the XML-RPC call as an associative array:
method
args
host
url
options
debug
0
)The value returned by xu_rpc_http_concise()
is the decoded return value from the called method.
There are several features of XML-RPC we haven’t covered. For example, XML-RPC’s data types do not always map precisely onto those of PHP, and there are ways to encode values as a particular data type rather than as the xmlrpc extension’s best guess. Also, there are features of the xmlrpc extension we haven’t covered, such as SOAP faults. See the xmlrpc extension’s documentation (http://www.php.net) for the full details.
For more information on XML-RPC, see Programming Web Services in XML-RPC (http://oreil.ly/Web_Services_XML-RPC) by Simon St.Laurent et al. (O’Reilly). See Programming Web Services with SOAP (http://oreil.ly/Web_Services_SOAP) by James Snell et al. (O’Reilly) for more information on SOAP.
Now that we’ve covered the majority of the syntax, features, and application of PHP, the next chapter explores what to do when things go wrong: how to debug problems that arise in your PHP applications and scripts.