Broadcast on Broadcast off
The Documentation for Project Zero has moved. Please update your bookmarks to: http://www.projectzero.org/documentation/
Table of
Contents...
Hide
 

REST programming in PHP

Project Zero simplifies the task of implementing RESTful resources. The following sections of this article provide information about handling REST resources in PHP:

Review of RESTful design

RESTful designs often make use of resources. A resource is a logical representation of an entity in as system. The concept of resource handlers in Project Zero provides a simple model for manipulating an individual resource or a set of resources. The paradigm allows you to add, remove, update and delete resources. You can also get a list of members in the logical resource collection. For example:

Method HTTP URI Description
GET /resources/people list members
POST /resources/people create member
GET /resources/people/1 retrieve member
PUT /resources/people/1 update member
DELETE /resources/people/1 delete member

Simple convention for resources

Project Zero supports the notion of resource handlers with in the <appRoot>/app/resources directory. Each script under the resources directory represents a resource handler. By convention, the name of the resource handler is the name of the resource followed by the language specific extension (.php):

Project Zero supports a common URL convention for interacting with individual resources and collection of resources, based upon the following pattern:

/resources/<resourceName>[/<resourceId>[/<pathInfo>]]

HTTP methods are mapped into collection and member events according to the following:

Resource GET PUT POST DELETE
Collection list putCollection create deleteCollection
Member retrieve update postMember delete

Invoking operations on a resource in PHP

PHP allows a resource handler to scripted as a regular script or through a set of functions defined in a class.
  • Simple Script dispach
    Using this approach the script is executed for a resource request and it is up to the developer to code for the different methods or events. Since the script is executed for both collection and member resources, distinguishing a GET method for a collection (List) from a GET method for an item (Retrieve) can be done using the value of /event/_name key from the global context.


    Values of Id and pathInfo are added to the request parameters and event zone respectively as shown in the following table. Also, the script people.php is executed for each of the URI in the table.

    Method URI ...with GC data
    GET /resources/people zget('/event/_name') = 'list'
    POST /resources/people zget('/event/_name') = 'create'
    PUT /resources/people zget('/event/_name') = 'putCollection'
    DELETE /resources/people zget('/event/_name') = 'deleteCollection'
    GET /resources/people/1/acc zget('/event/_name') = 'retrieve'
    zget('/request/params/peopleId') = 1
    zget('/event/pathInfo') = /acc
    PUT /resources/people/1 zget('/event/_name') = 'update'
    zget('/request/params/peopleId') = 1
    POST /resources/people/1 zget('/event/_name') = 'postMember'
    zget('/request/params/peopleId') = 1
    DELETE /resources/people/1 zget('/event/_name') = 'delete'
    zget('/request/params/peopleId') = 1

    The code sample below shows how this can be combined with the /request/method key from the global context.

    <appRoot>/app/resources/people.php

    <?php
    
    switch (zget('/request/method')) {
            case 'GET':
            if (zget('/event/_name') == 'list') {
                    // Perform a list specific operation on the collection
            } else {
                    // /event/_name = "retrieve"
                    // Retrieve the resource
            }
            break;
            case 'POST':
            // do POST specific handling
            break;
            case 'DELETE':
            // do DELETE specific handling
            break;
            case 'PUT':
            // do PUT specific handling
            break;
    }
    
    ?>

  • Method dispatch
    The script developer can delegate the method-specific handling to Project Zero by defining a class with the with the resource name and specifying the operations on the resource by defining the required functions in the class based on the table below.

    Values of Id and pathInfo are added to the request parameters and event zone respectively as shown in the following table.

    Method URI ...invokes handler in
    app/resources/people/
    ...with event data
    GET /resources/people people.php
    People:onList()
     
    POST /resources/people people.php
    People::onCreate()
     
    PUT /resources/people people.php
    People::onPutCollection()
     
    DELETE /resources/people people.php
    People::onDeleteCollection()
     
    GET /resources/people/1/acc people.php
    People::onRetrieve()
    zget('/request/params/peopleId') = 1
    zget('/event/pathInfo') = /acc
    PUT /resources/people/1 people.php
    People::onUpdate()
    zget('/request/params/peopleId') = 1
    POST /resources/people/1 people.php
    People::onPostMember()
    zget('/request/params/peopleId') = 1
    DELETE /resources/people/1 people.php
    People::onDelete()
    zget('/request/params/peopleId') = 1

<appRoot>/app/resources/people.php

<?php

// Initialization common to all operations
// Get configured (as 'theDB' in zero.config) DataManager for data access
$dataManager = data_manager('theDB');

class Employees {
        function onList() {
                global $dataManager;
                // Retrieve employee records via Query Zero
                $employeeRecords = dataExec($dataManager, "SELECT * FROM employees");
                // Use the zero global context to render the data as JSON
                zput('/request/view', 'JSON');
                zput('/request/json/output', $employeeRecords);
                render_view();
        }
        
        function onCreate() {
                global $dataManager;
                // Convert the raw JSON stream in to a PHP array
                $er = json_decode($HTTP_RAW_POST_DATA);
                
                $result = dataExec($dataManager, "INSERT INTO employees (username, , location, phonenumber) ". 
                "VALUES (?, ?, ?, ?, ?)", array($er['username'], $er['location'], $er['phonenumber']));
                
                // Set a Location header with URI to the new record
                $locationUri = get('/request/path') . "/" . $er["username"];
                zput('/request/headers/out/Location', $locationUri);
                zput('/request/status', 204);
        }
        
        function onRetrieve() {
                // Get configured DataManager for data access
                global $dataManager;
                
                // Zero puts 'itemId' when accessing restful resources 
                $username = zget("/request/params/employeesId");
                
                // Retrieve employee record via Query Zero
                $employeeRecord = dataQueryFirst($dataManager, "SELECT * FROM employees WHERE username=?", array($username));
                
                if(isset($employeeRecord)) {
                        // JSON encode employee record
                        zput('/request/headers/out/Content-Type', 'text/json');  			
                        echo json_encode($employeeRecord);
                } else {
                        // Error handling; return a custom error page
                        zput("/request/status", 404);
                        echo "username ". $username . " not found.";
                }
        }
        
        function onUpdate() {
                // Get configured DataManager for data access
                global $dataManager;
                
                // By convention, Zero places posted text/json into the GC as a JSON object
                $er = json_decode($HTTP_RAW_POST_DATA);
                
                $result = dataExec($dataManager, "UPDATE employees location=?, phonenumber=? WHERE username=?",
                array($er['firstname'], $er['phonenumber'], $er['username']));
                zput("/request/status", 204);
        }
        
        function onDelete() {
                // Get configured DataManager for data access
                global $dataManager;
                $username = zget("/request/params/employeesId");
                
                // Delete employee record via Query Zero
                $result = dataExec($dataManager, "DELETE FROM employees WHERE username=?", array($username));
                
                zput("/request/status", 204);
        }
        
}
?>
ALERT! NOTE: The script is always invoked prior to the method dispatch. This means that any code outside the class definition (including that in any includes) will be executed for all operations. It is recommended that only common code applicable to all operations be placed outside the class definition.

ALERT! NOTE: As Project Zero does not buffer output, any PHP code that generates output would effectively commit that response and headers to the client. Subsequent header or status manipulation would be ignored.

Nested resources

The Core developer's guide for Nested resources describes how alternate URL can be mapped to a resource handler and how the various path segments are made available to the script developer. The basic mechanism described using examples in groovy in that article can also be applied to PHP scripts.

-- madhu - 19 Jul 2007

r9 - 08 Feb 2008 - 11:30:10 - owenb
Syndicate this site RSS ATOM
Copyright 2007 © IBM Corporation | Privacy | Terms of Use | About this site