PHP Web Services (2013)

Chapter 7. RPC and SOAP Services

In this chapter we’ll be looking at two closely-related types of services: Remote Procedure Call (RPC) services, and SOAP. These two feel fairly similar, as they both involve calling functions and passing parameters, but their implementations are in stark contrast as the RPC is a very loose way of describing a service, whereas SOAP is very tightly specified.

RPC

RPC services quite literally call procedures (i.e., functions) remotely. These types of API will typically have a single endpoint, so all requests are made to the same URL. Each request will include the name of the function to call, and may include some parameters to pass to it. Working with RPC services should feel familiar to us as developers because we know how to call functions—we simply do so over HTTP.

To start out, consider Example 6-2 when a call was made to Flickr. The URL we made for that example was:

http://api.flickr.com/services/rest/?method=flickr.photos.search&tags=

kitten&format=xmlrpc

Within the URL, the name of the function can be seen in the “method” parameter (flickr.photos.search), the particular tags to search for are found in tags=, and the format parameter asks for the response in XML-RPC format.

There is a distinct difference between using an RPC-style service, with function names and parameters included in the data supplied, and having a service that is true XML-RPC, which is a very defined format. The option you choose depends entirely on the situation you and your application find yourselves in, but whichever it is, be sure to label it correctly.

Building an RPC service layer for an application can be achieved very simply by wrapping a class and exposing it over HTTP. Example 7-1 shows a very basic class that offers some toy functionality to use in the following examples.

Example 7-1. Example of the library class

<?php

class Library

{

    public function getDwarves() {

        $dwarves = array("Bashful", "Doc", "Dopey", "Grumpy", "Happy",

            "Sneezy", "Sleepy");

        return $dwarves;

    }

    public function greetUser($name) {

        return array("message" => "Hello, " . $name);

    }

}

To make this available via an RPC-style service, a simple wrapper can be written for it, which looks at the incoming parameters and calls the relevant function. You could use something along these lines:

<?php

include("library.php");

$lib = new Library();

if(isset($_GET['action'])) {

    switch($_GET['action']) {

    case "getDwarves":

        $data = $lib->getDwarves();

        break;

    case "greetUser":

        $data = $lib->greetUser(

            filter_input(INPUT_GET, 'name', FILTER_SANITIZE_STRING)

        );

        break;

    default:

        http_response_code(400);

        $data = array("error" => "bad request");

    }

    header("Content-Type: application/json");

    echo json_encode($data);

}

This example does a very simple switch-case on the incoming “action” parameter and passes in any variables as required (with validation, of course). We fetch the return data from the underlying library, then send the appropriate content negotiation headers and the data, formatted as JSON. If the request isn’t understood, then a 400 status code is returned along with some error information.

The previous example shows a very simple RPC-style service using JSON, and illustrates how easy it is to wrap an existing class of functionality and expose it over HTTP. Sometimes it’s appropriate to use HTTP within an application to allow different components to be scaled independently; for example, moving comments to a separate storage area to be accessed by the original application rather than HTTP. In those scenarios, this approach of wrapping existing, hardened code can be very useful indeed, and is quick to implement.

Exactly as the difference between XML over an RPC service and XML-RPC is important to remember, the same applies here. The example shows JSON being returned by an RPC service, but JSON-RPC is something much more tightly specified. The *-RPC services can be a better choice when working with people or technologies that understand those and are happy implementing them. If the requirements are for something rather lighter and more approachable, then a simple custom format will work perfectly well. Standards are always good, especially for externally-available systems, but don’t feel that they are your only choice.

SOAP

SOAP was once an acronym for Simple Object Access Protocol; however, this has been dropped and now it is just “SOAP.” SOAP is an RPC-style service that communicates over a very tightly-specified format of XML. Since SOAP is well-specified when it follows WSDL conventions, little work is needed to implement it in an application, or to integrate against it; as of PHP 5.0, PHP has a really excellent set of SOAP libraries for both client and server.

You will see quite a few providers of SOAP implementations, and some open source tools such as SugarCRM and Magento also offer SOAP integration points. When looking at a new SOAP service, a tool called soapUI allows for browsing a service when a Web Service Description Language (WSDL) file is supplied. In fact, soapUI is excellent and can do about a hundred other things, including complicated functional testing, but for now we will look at its SOAP functionality.

As an example, I took the WSDL file from RadioReference and added it into soapUI, simply creating a new project, naming the project, and giving the URL to the WSDL file for this service. By default, this will create a request for each of the available methods, and generate an easy interface in which they can be executed. To run one, pick it from the list on the left, and then click the green Play button above the sample request. I used getCountryList as an example, as you can see in Figure 7-1.

soapUI showing a request to getCountryList

Figure 7-1. soapUI showing a request to getCountryList

The left half of the main pane shows the request that was sent, and the right half shows the response that was received. This gives a quick overview of how things look when using this API from our PHP code.

WSDL

This is a good moment to talk about the WSDL files that always seem to be mentioned whenever SOAP comes up. When it was first mentioned in this chapter, the acronym was defined as “Web Service Description Language,” and this is a pretty good description of what is found in a WSDL file. It describes the location of a particular service, the data types that are used in it, and the methods, parameters, and return values that are available. The WSDL format is rather unfriendly XML, so it is best generated and parsed by machines rather than humans. If you do find yourself in the situation of needing to read one, it usually makes more sense to begin at the end of the document and then read upwards.

WDSL files are commonly used with SOAP, but they can be used with other types of web services. SOAP can also be used without a WSDL file, known in PHP as “non-WSDL mode.” This chapter includes examples of SOAP with and without WSDLs, and an example of generating a WSDL file.

PHP SOAP Client

Returning to the countries list, we can acquire it from PHP quite easily using the SOAP extension. Take a look at this example, which does exactly that:

<?php

$client = new SoapClient('http://api.radioreference.com/soap2/?wsdl&v=latest');

$countries = $client->getCountryList();

var_dump($countries);

Simply using var_dump() doesn’t create a very pretty output, but it does illustrate what these two lines of PHP have produced. The beginning of the output looks like this:

array(236) {

  [0]=>

  object(stdClass)#2 (3) {

    ["coid"]=>

    int(5)

    ["countryName"]=>

    string(11) "Afghanistan"

    ["countryCode"]=>

    string(2) "AF"

  }

  [1]=>

  object(stdClass)#3 (3) {

    ["coid"]=>

    int(8)

    ["countryName"]=>

    string(7) "Albania"

    ["countryCode"]=>

    string(2) "AL"

  }

  [2]=>

  object(stdClass)#4 (3) {

    ["coid"]=>

    int(60)

    ["countryName"]=>

    string(7) "Algeria"

    ["countryCode"]=>

    string(2) "DZ"

  }

Our two lines of PHP connected to a remote service and fetched us an array of objects containing the country information as requested. This shows the joy of SOAP, which is that very few lines of code are needed to exchange data between systems. The SoapClient class in PHP makes consuming data with a WSDL file trivial.

PHP SOAP Server

What about when we want to publish our own services? Well, PHP has a SoapServer that is almost as easy to use. Using the example library code from Example 7-1, we can make it available as a SOAP service in non-WSDL mode:

<?php

require('library.php');

$options = array("uri" => "http://localhost");

$server = new SoapServer(null, $options);

$server->setClass('Library');

$server->handle();

Since a WSDL is not used in the previous example, the Uniform Resource Identifier (URI) for the service must be provided. The example then creates the SoapServer and tells it which class holds the functionality it should expose. When the call to handle() is added, everything “just works.” The PHP to call the code looks much like the previous example, but without a WSDL file, it is necessary to tell the SoapClient where to find the service by setting the location parameter and passing the URI:

<?php

$options = array("location" => "http://localhost/book/soap-server.php",

    "uri" => "http://localhost");

try {

    $client = new SoapClient(null, $options);

    $dwarves = $client->getDwarves();

    var_dump($dwarves);

} catch (SoapFault $e) {

    var_dump($e);

}

Again, just doing a var_dump() shows the results that are returned very clearly, but it isn’t particularly pretty! The list of Dwarf names arrives in an array format:

array(7) { [0]=> string(7) "Bashful" [1]=> string(3) "Doc" [2]=> string(5) "Dopey" [3]=> string(6) "Grumpy" [4]=> string(5) "Happy" [5]=> string(6) "Sneezy" [6]=> string(6) "Sleepy" }

At this point, a working SOAP service exists, but not the WSDL file that is commonly used with it. The WSDL file holds the description of the service functionality, which means a file is created to describe our service, and should be recreated if any of the functions available change or if anything is added. Many technology stacks, such as Java and .NET, offer built-in functionality that makes it very easy to work with services that use WSDL files.

Generating a WSDL File from PHP

There are various solutions for generating a WSDL file from your library class code; some IDEs such as Eclipse have a button for it, and some frameworks also have this functionality. The examples here use a tool that will work regardless of the IDE or framework you use, because it’s written in PHP: the php2wsdl tool.

To get set up, the files are extracted and placed in a php2wsdl/ directory. Then a WSDLCreator object is instantiated and the files are added, along with information about which endpoint to use for which class, and a WSDL file is generated. Here’s the code:

<?php

require("php2wsdl/WSDLCreator.php");

$wsdlgen = new WSDLCreator("LibraryWSDL", "http://localhost/book/wsdl");

$wsdlgen->addFile("library.php");

$wsdlgen->addURLToClass("Library", "http://localhost/book/soap-server.php");

$wsdlgen->createWSDL();

$wsdlgen->saveWSDL("wsdl");

This writes a file called wsdl to the local directory, and it contains the following:

<!--WSDL file generated by PHP WSDLCreator (http://www.protung.ro)-->

<definitions name="LibraryWSDL" targetNamespace="urn:LibraryWSDL" xmlns:typens="urn:LibraryWSDL" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns="http://schemas.xmlsoap.org/wsdl/">

   <message name="getDwarves"/>

   <message name="getDwarvesResponse"/>

   <message name="greetUser">

      <part name="name" type="xsd:anyType"/>

   </message>

   <message name="greetUserResponse"/>

   <portType name="LibraryPortType">

      <operation name="getDwarves">

         <input message="typens:getDwarves"/>

         <output message="typens:getDwarvesResponse"/>

      </operation>

      <operation name="greetUser">

         <input message="typens:greetUser"/>

         <output message="typens:greetUserResponse"/>

      </operation>

   </portType>

   <binding name="LibraryBinding" type="typens:LibraryPortType">

      <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>

      <operation name="getDwarves">

         <soap:operation soapAction="urn:LibraryAction"/>

         <input>

            <soap:body namespace="urn:LibraryWSDL" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>

         </input>

         <output>

            <soap:body namespace="urn:LibraryWSDL" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>

         </output>

      </operation>

      <operation name="greetUser">

         <soap:operation soapAction="urn:LibraryAction"/>

         <input>

            <soap:body namespace="urn:LibraryWSDL" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>

         </input>

         <output>

            <soap:body namespace="urn:LibraryWSDL" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>

         </output>

      </operation>

   </binding>

   <service name="LibraryWSDLService">

      <port name="LibraryPort" binding="typens:LibraryBinding">

         <soap:address location="http://localhost/book/soap-server.php"/>

      </port>

   </service>

</definitions>

The WSDL as it stands isn’t terribly descriptive, as it can’t guess what data types could be used or whether the methods should have arguments or return values. This is because PHP is dynamically typed, data types are not declared when defining variables or passing them into functions, and data types of return values are not declared either. Some other languages do declare data types and WSDL files usually contain detailed type information.

As an aside, look out for WSDL files with data types that PHP doesn’t support—if the client or server is not in PHP, there can be a mismatch of formats in some cases. This is the main reason why so many WSDL files have fairly loose types, with strings rather than anything more specific. In fact, I have also seen an entire web service with a WSDL file that described a single method and accepted a custom XML format within it, for exactly this reason—not fun!

In order to make WSDL files more accurate, phpDocumentor comments can be added to our source code. Where the data types for parameters and return values are specified in documentation, the WSDL file will change to reflect the additional information.

PHP Client and Server with WSDL

Now there is a WSDL file to use with the Library example class, and the client and server code can be altered to take advantage of this. First, here’s the server, with only the constructor needing to change:

<?php

require('library.php');

$server = new SoapServer("wsdl"); // wsdl file name

$server->setClass('Library');

$server->handle();

With the WSDL file in use, there’s no need to give any other information. Just give the filename (this can be remote if appropriate) and all the location and other settings are picked up from there. The client can do exactly the same:

<?php

try {

    $client = new SoapClient("http://localhost/book/wsdl");

    $dwarves = $client->getDwarves();

    var_dump($dwarves);

} catch (SoapFault $e) {

    var_dump($e);

}

At this point, you’re able to either build or consume RPC-style services in general, and XML-RPC, JSON-RPC, and SOAP in particular, with the use of handy tools such as soapUI.