Automating REST Services with Reflection API

by Stan Vassilev (2008-02-25)
  Page: 1  2  [next]

In this article, we look at the ability of PHP code to inspect itself, using the Reflection API introduced with PHP 5. We will build a tool that lets us automatically convert any class to a fully functional REST service, without writing a single line of dedicated binding code.

PHP has been designed as a high-level dynamic scripting language for the web. Dynamic languages, unlike static languages, retain rich information about the source code's meaning and structure for the entire duration of its execution. Such information covers function and class identifiers, parameters, types, interfaces, relationships etc.

We can easily see this with PHP, since we are able refer to variables and functions just by having their string names at our disposal, or even evaluate entire code fragments from a string at runtime (such features, of course, should be used in moderation):

  1. $foo = 'bar';
  2. ${$foo} = 10; // sets $bar = 10
  3.  
  4. $foo = 'bar';
  5. $foo(); // calls function bar()

So What is Reflection?

Reflection is the ability of your program's code to tap into this rich information, in order to understand its own definitions and structure. This is used for a wide array of tasks, such as automating unit tests, building automatic documentation from source code, creating auto-generated reports and more.

The idea for reflection support in PHP has seen its beginning as early as PHP 4, by introducing a set of functions that describe the script runtime environment. Some examples include:

  1. // Returns the names of all user-defined and internal functions
  2.  
  3. // Returns the names of all loaded classes
  4.  
  5. // Returns the name of the object's class
  6. get_class($object);
  7.  
  8. // Returns the name of the object's superclass, if there is any
  9. get_parent_class($object);
  10.  
  11. // Returns the names of all methods of the object's class
  12. get_class_methods($className);

Those methods, while still useful, suffer from a number of limitations. For example, get_class_methods() cannot differentiate between static and instance methods, nor is there a way to learn more about a method's parameters, or in which file the class was defined.

This is why the introduction of the new Reflection API, starting with PHP5, is a major and very welcome addition to the language.

The Reflection API is a set of classes with an easy and consistent interface, allowing us to retrieve information about the declared functions, classes, down to details such as the PHPDoc comment that applies to a function, method or a class.

In this article, we'll use the Reflection API to build a reusable tool that will automatically expose the public methods of any class we choose, as a REST web service.

A REST Service in a Nutshell

A REST service can be quickly described by the following rules:

  • A given URL represents a specific resource (such as an object, or a collection of objects).
  • REST can use GET and POST variables to pass the method name and parameters.
  • We return the results as any preferred format, such as XML, JSON or even plain text.
  • To keep things simple for our example, we'll accept request variables via GET, and return a JSON object with field error (holding the error message, if the service encountered a problem) or result (if it was executed properly).

The Example Service

To test our work, we'll use the following class, which returns some information about the employees in a company:

  1. class Employees
  2. {
  3.   private $data = array(
  4.     array(
  5.       'fullName' => 'Billy Silverman',
  6.       'jobTitle' => 'developer',
  7.       'birthDate' => '1973-09-23'
  8.     ),
  9.     array(
  10.       'fullName' => 'Jessica Conner',
  11.       'jobTitle' => 'developer',
  12.       'birthDate' => '1984-02-01'
  13.     ),
  14.     array(
  15.       'fullName' => 'Dennis Jackson',
  16.       'jobTitle' => 'designer',
  17.       'birthDate' => '1969-11-23'
  18.     ),
  19.     array(
  20.       'fullName' => 'Sara Erickson',
  21.       'jobTitle' => 'designer',
  22.       'birthDate' => '1980-05-06'
  23.     ),
  24.   );
  25.  
  26.   /**
  27.    * Returns an array of employee names, optionally filtered
  28.    * by job title.
  29.    */
  30.   public function getAllNames($jobTitle = null)
  31.   {
  32.     $names = array();
  33.    
  34.     foreach ($this->data as $employee) {
  35.       if ($jobTitle === null || $jobTitle == $employee['jobTitle']) {
  36.         $names[] = $employee['fullName'];
  37.       }
  38.     }
  39.    
  40.     if (count($names)) {
  41.       return $names;
  42.     } else {
  43.       throw new Exception('There are no employees with that job title.');
  44.     }
  45.   }
  46.  
  47.   /**
  48.    * Returns all details for an employee with a given name.
  49.    */
  50.   public function getDetails($fullName)
  51.   {
  52.     foreach ($this->data as $employee) {
  53.       if ($fullName == $employee['fullName']) {
  54.         return $employee;
  55.       }
  56.     }
  57.    
  58.     throw new Exception('There is no employee with this name.');
  59.   }
  60. }

Our goals are:

  • Write a REST wrapper that can accept an instance of class Employees.
  • Allow us to query the class by passing the method name and the parameters, by name, in the url's query string, for example: service.php?method=getDetails&fullName=Jessica+Conner.
  • If the method parameter is optional (has a default value), it should be optional for the REST service as well, and assume the specified default value when not passed in GET.

Building the Wrapper

Let's call the class RestService. The constructor will accept an object, and pass it to method runAsService(), where we'll do the actual reflection work and invoke the method.

We will be doing all of our error handling via exceptions (Employees throws exceptions on bad input, as well), so the constructor should catch any exception and output the exception message as a JSON object, in an appropriate form for the service clients:

  1. class RestService
  2. {
  3.   public function __construct($object)
  4.   {
  5.     try {
  6.      
  7.       $result = $this->runAsService($object);
  8.       echo json_encode(array('result' => $result));
  9.      
  10.     } catch (Exception $e) {
  11.      
  12.       echo json_encode(array('error' => $e->getMessage()));
  13.      
  14.     }
  15.   }
  16. }
File under: art  homepage  php5  reflection  rest 
  Page: 1  2  [next]

Comments

There are no comments on this entry.

Visit the forum