13.1. JPSpan

JPSpan is an open source Ajax framework for PHP (available for download at www.sourceforge.net/projects/jpspan) that creates JavaScript wrappers for PHP objects and methods. It accomplishes this task by using reflection to inspect the composition of an object. Reflection is a common feature of object-oriented languages, allowing developers to determine information about the makeup of an object at runtime. By using reflection, JPSpan is able to create appropriate JavaScript wrappers for each method of the object. These JavaScript functions handle the cross-browser creation of XHR objects as well as the instantiation of objects on the server and data type conversion of arguments and results.

What's more, since JPSpan is simply a series of PHP pages and JavaScript files, there is no real installation to speak of. Simply download the files from the previously mentioned web site and copy the JPSpan folder to the PHP web server.

13.1.1. Using JPSpan

Most of the coding for a JPSpan Ajax solution takes place on the server using a PostOffice object. The PostOffice object is responsible for two things: generating the JavaScript for the client and handling Ajax requests on the server. Generally speaking, the same PHP file handles both activities, depending on the query string of the request. The standard way of doing this using JPSpan is to have the query string equal to "client" when the JavaScript wrappers should be generated (for example, myserver.php?client) and to omit the word "client" when server requests are being handled. The latter is actually handled by the JavaScript wrappers directly, so developers never have to worry about the correct format for handling requests.

The basic format for a JPSpan server page is as follows:

<?php
    //include the necessary files
    require_once '../JPSpan/JPSpan.php';
    require_once JPSPAN . 'Server/PostOffice.php';

    //create the PostOffice object
    $server = & new JPSpan_Server_PostOffice();

    //add a handler for your class
    $server->addHandler(new MyCustomObject());

    //check the query string
    if (isset($_SERVER['QUERY_STRING'])
          && strcasecmp($_SERVER['QUERY_STRING'], 'client') == 0){

        //turn off JavaScript compression
        define('JPSPAN_INCLUDE_COMPRESS', false);

        //output the JavaScript wrappers
        $server->displayClient();
    } else {

        //include the error handler
        require_once JPSPAN . 'ErrorHandler.php';

        //handle incoming requests
        $server->serve();
    }
?>

The first two lines of code include the main JPSpan library (which defines a global constant called JPSPAN containing the directory in which the framework was installed) and the PostOffice class that is used for generating the client-side code for the solution. After that, an instance of JPSpan_Server_PostOffice is created and stored in $server; this is the object that contains all of the Ajax logic.

Next, the PostOffice is assigned an object to handle using the addHandler() method. This method accepts a single argument, which is an object (not a class) that should be available to the client and whose functionality should be handled by the server. Any number of objects can be handled by a single PostOffice.

After the objects have been added to the PostOffice, it's time to determine whether the request should return the JavaScript wrappers or handle a request. The if statement checks to see if the query string is exactly equal to "client", indicating that the JavaScript wrappers should be emitted to the client. This is done using the displayClient() method, which outputs the JavaScript for all of the handled objects (defining JSPAN_INCLUDE_COMPRESS as false turns off server-side code compression that's intended to remove white space due to performance issues). If the query string is not "client", then the server begins listening for requests. The first step is to include ErrorHandler.php, which handles any errors caused by the request. Then, the serve() method is called to handle the request.

To include the JavaScript in a page, include the file in a <script/> tag, such as:

<script type="text/javascript" src="/myserver.php?client"></script>

This single line includes all of the generated JavaScript for each object handled by the PostOffice. For each of the handled objects, a JavaScript constructor is defined. This constructor is the name of the PHP class in all lowercase (due to a quirk in PHP's implementation of reflection) and accepts a single object containing callback functions for each PHP method. For example, consider the following PHP class:

class MyCustomObject {
    function MyCustomObject() {
        if (!isset($_SESSION['message'])) {
            $_SESSION['message'] = 'Hello world!';
        }
    }

    function getMessage() {
        return $_SESSION['message'];
    }

    function setMessage($message) {
        $_SESSION['message'] = $message;
        return true;
    }
}

In this class there is a constructor and two methods. The constructor simply stores a message in the session so that the other methods have data to work with. The getMessage() method returns the value stored in the session, while the setMessage() method changes that value to something else. It's important to note that this value must be stored in the session because this object doesn't persist from request to request. Each request sent back to the server creates a new instance of MyCustomObject, thus dis- allowing access to property values stored in a previous instance.

The generated JavaScript contains a constructor named mycustomobject (all lowercase due to a quirk in the PHP implementation of reflection). When instantiated, this object contains methods called getmessage() and setmessage() (again, both in all lowercase) that are used to invoke the methods of the same name on the server.

When creating an instance of mycustomobject, you must pass in a single argument. This argument is an object containing methods called getmessage() and setmessage() as well. The difference is that these methods are actually callback functions that receive the result of the server request as their only argument. For instance:

var oHandlers = {
    getmessage : function (oResult) {
        alert("Got: " + oResult);
    },

    setmessage : function (oResult) {
        if (oResult) {
            alert("Message set.");
        } else {
            alert("An error occurred while setting the message.");
        }
    }
};

var oObject = new mycustomobject(oHandlers);

oObject.setmessage("Welcome");

//other logic here

oObject.getmessage();  //outputs "Got: Welcome"

In this example, oHandlers is an object containing callback functions for getmessage() and setmessage(). Whenever either one of these methods is called, the result is automatically passed into the corresponding callback function. This object is passed into the mycustomobject constructor to create a new object. After that, each of the two methods is called. The oResult value for the setmessage() callback should be a Boolean value of true; if it's not, that means that something went wrong on the server. For the getmessage() callback, oResult is just the string value that was stored, so it is displayed in an alert.

As you can see, there is no XHR programming required to make JPSpan code work. Using reflection, all of the necessary hooks are accounted for, making it possible to focus on the server-side portion of the code without worrying about the client-server communication.

13.1.1.1. Type Translation

Anytime data is being sent from the server to the client or vice versa, data must be provided in a way that both can understand. If this isn't possible, then the data must be translated. JPSpan includes three PHP files responsible for this data translation: Serializer.php, Types.php, and Unserializer.php.

Serializer.php contains the mappings for translation from PHP to JavaScript in an array called _JPSPAN_SERIALIZER_MAP. This array contains an element for each PHP type that can be represented in JavaScript:

$GLOBALS['_JPSPAN_SERIALIZER_MAP'] = array(
    'string'=>array(
        'class'=>'JPSpan_SerializedString',
        'file'=>NULL
        ),

'integer'=>array(
        'class'=>'JPSpan_SerializedInteger',
        'file'=>NULL
        ),
    'boolean'=>array(
        'class'=>'JPSpan_SerializedBoolean',
        'file'=>NULL
        ),
    'double'=>array(
        'class'=>'JPSpan_SerializedFloat',
        'file'=>NULL
        ),
    'null'=>array(
        'class'=>'JPSpan_SerializedNull',
        'file'=>NULL
        ),
    'array'=>array(
        'class'=>'JPSpan_SerializedArray',
        'file'=>NULL
        ),
    'object'=>array(
        'class'=>'JPSpan_SerializedObject',
        'file'=>NULL
        ),
    'jpspan_error'=>array(
        'class'=>'JPSpan_SerializedError',
        'file'=>NULL
        ),
);

Each item in the array is an associative array containing two properties: class, which indicates the serializer class to use for translating this type of data, and file, which indicates the file in which the specified class exists. In this example, file is NULL for all items because these are default mappings contained within the same file. If you were to override the default mapping, you would need to provide the location for the custom class.

The files Unserializer.php and Types.php are used for conversion from JavaScript to PHP. Types.php contains definitions for two base types: JPSpan_Object and JPSpan_Error; all JavaScript values are converted into one of these two types. The JPSpan_Object class is simply an empty class that serves as a base for more specific implementations; JPSpan_Error has the properties code, name, and message.

The Unserializer.php file uses two classes for deserialization, both of which are found in the unserializer folder: JPSpan_Unserializer_XML in XML.php and JPSpan_Unserializer_PHP in PHP.php. By default, JPSpan uses XML to contain all of the information about a remote method call. For example, the data being sent to the server for a call to setMessage() with a string of "Welcome" as the only argument looks like this:

<r><a><e k="0"><s>Welcome</s></e></a></r>

Note that there is no mention of the actual method name being called. The <r/> element is simply the root of the XML document while the <a/> element represents an array of values (the arguments). Inside of the array is any number of elements, each represented by an <e/> element containing a property called k, which indicates the key value for associative arrays or the index for sequential arrays (in this case it's an index). Within each <e/> element is an element representing the type of value being passed. In the case of setMessage(), the argument is a string, so the <s/> element is used to surround the actual string value. There are also other elements used for other types of values, such as <b/> for Boolean values and <i/> for integer values.

This information is useful if you need to augment JPSpan for custom data types. Otherwise, you never need to touch these files. The default JPSpan settings should be good enough for most needs.

13.1.1.2. Error Handling

Errors can occur with any application, especially when communicating between client and server. JPSpan has a means of trapping nonfatal errors that occur when processing the request and having them appear as standard client-side exceptions. The ErrorHandler.php file is a generic handler that propagates nonfatal errors to the client. It can handle general PHP errors as well as those caused specifically by JPSpan components. There are two basic configuration settings for error handling.

  • The first setting is JPSPAN_ERROR_MESSAGES, which is a Boolean value that determines how much information to send to the client when a generic PHP error occurs. When defined as TRUE, detailed error messages are returned only for JPSpan-specific exceptions; PHP errors simply return an error that says, "Server unable to respond." If defined as FALSE, verbose error information is returned for all types of errors. This setting is defined such as:

    if (!defined('JPSPAN_ERROR_MESSAGES')){
        define ('JPSPAN_ERROR_MESSAGES', TRUE);
    }

  • The second setting is JPSPAN_ERROR_DEBUG, which allows even more information to be sent to the client. When this setting is set to TRUE, two additional pieces of information are returned with error messages: the filename in which the error occurred and the line number that caused the error. Since this information is sensitive, it is set to FALSE by default. You can change the setting by doing the following:

    if (!defined('JPSPAN_ERROR_DEBUG') )
    {
        define ('JPSPAN_ERROR_DEBUG', TRUE);
    }

    This setting should be set to TRUE only while debugging; production code should have this setting as FALSE.

Should you decide to modify either of these settings, this code should occur before inclusion of ErrorHandler.php in the server page.

13.1.2. JPSpan Example

In order to truly understand how to use JPSpan, it's helpful to look at a familiar example. Here, you will be rebuilding the example from Chapter 2, retrieving data about a customer when given a customer ID. The same database table, Customers, will be used, as will the same sample data.

13.1.2.1. The CustomerInfo Class

The most important part of this example is the CustomerInfo class, which contains the logic used to communicate with the database. For simplicity, this class has only one method: getCustomerInfo(). This method accepts a single argument, the customer ID, and returns a string of information about the customer:

<?php

class CustomerInfo {

    function getCustomerInfo($sId) {

        if (is_numeric($sId)) {

            //variable to hold customer info
            $sInfo = "";

            //database information
            $sDBServer = "your.databaser.server";
            $sDBName = "your_db_name";
            $sDBUsername = "your_db_username";
            $sDBPassword = "your_db_password";

            //create the SQL query string
            $sQuery = "Select * from Customers where CustomerId=".$sId;

            //make the database connection
            $oLink = mysql_connect($sDBServer,$sDBUsername,$sDBPassword);
            @mysql_select_db($sDBName) or $sInfo = "Unable to open database";

            if ($sInfo == "") {
                if($oResult = mysql_query($sQuery)
                     and mysql_num_rows($oResult) > 0) {
                   $aValues = mysql_fetch_array($oResult,MYSQL_ASSOC);
                   $sInfo = $aValues['Name']."<br />".$aValues['Address']."<br />".
                         $aValues['City']."<br />".$aValues['State']."<br />".
                         $aValues['Zip']."<br /><br />Phone: ".
                         $aValues['Phone']."<br />".
                         "<a href="mailto:".$aValues['Email']."">".
                         $aValues['Email']."</a>";
                   mysql_free_result($oResult);
                } else {
                   $sInfo = "Customer with ID $sId doesn't exist.";
                }
            }
            mysql_close($oLink);
        } else {
            $sInfo = "Invalid customer ID.";
        }

        return $sInfo;

    }

}

?>

The getCustomerInfo() method is fairly straightforward. It does a database lookup based on the customer ID that is passed in as an argument. The logic is the same as that in the examples in Chapter 2.

This class is stored in a file named CustomerInfo.php so that it can be included in the JPSpan server page.

13.1.2.2. Creating the Server Page

The server page is very similar to the sample page shown earlier in this section. In fact, there are only two changes to the overall code:

<?php
    //include the necessary files
    require_once '../JPSpan/JPSpan.php';
    require_once JPSPAN . 'Server/PostOffice.php';


    //include the CustomerInfo class
    require_once 'CustomerInfo.php';

    //create the PostOffice object
    $server = & new JPSpan_Server_PostOffice();

    //add a handler for your class
    $server->addHandler(new CustomerInfo());

    //check the query string
    if (isset($_SERVER['QUERY_STRING'])
          && strcasecmp($_SERVER['QUERY_STRING'], 'client') == 0){

        //turn off JavaScript compression
        define('JPSPAN_INCLUDE_COMPRESS', false);

        //output the JavaScript wrappers
        $server->displayClient();
    } else {

        //include the error handler
        require_once JPSPAN . 'ErrorHandler.php';

        //handle incoming requests
        $server->serve();
    }

?>

The highlighted lines are necessary to include the CustomerInfo class and add handling for a Customer object, respectively. This file is saved as CustomerInfoServer.php.

13.1.2.3. Creating the Client Page

Now that the CustomerInfo class is incorporated into the server page, it's time to construct the client page. Once again, this code is very similar to that of Chapter 2:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>JPSpan Example</title>

    <script type="text/javascript"src="CustomerInfoServer.php?client"></script>
    <script type="text/javascript"src="JPSpanExample.js"></script>
</head>
<body>
    <p>Enter customer ID number to retrieve information:</p>
    <p>Customer ID: <input type="text" id="txtCustomerId" value="" /></p>
    <p><input type="button" value="Get Customer Info"
              onclick="requestCustomerInfo()" /></p>
    <div id="divCustomerInfo"></div>
</body>
</html>

The first highlighted line includes the JavaScript necessary to call the JPSpan server. Note that this example assumes CustomerInfoServer.php to be in the same directory as the client page. The second line includes the JavaScript file necessary to use the JPSpan wrappers. This file contains the following code:

var oHandlers = {
    getcustomerinfo : function (sInfo) {
        displayCustomerInfo(sInfo);
    }
};

var oInfo = new customerinfo(oHandlers);

function requestCustomerInfo() {
    var sId = document.getElementById("txtCustomerId").value;

    oInfo.getcustomerinfo(sId);
}

function displayCustomerInfo(sText) {
    var divCustomerInfo = document.getElementById("divCustomerInfo");
    divCustomerInfo.innerHTML = sText;
}

The differences in this code versus the examples in Chapter 2 are the use of the JPSpan methods. First, an object called oHandlers is created to contain the callback function for getcustomerinfo(). Within this callback function, the displayCustomerInfo() function is called (which is unchanged from Chapter 2). The information returned from the server is in HTML format, so it can be passed directly. Next, an instance of customerinfo is created and stored in oInfo so that it can be used to retrieve the data.

As you can see from the small amount of JavaScript code necessary to make this example work, JPSpan eliminates much of the client-side development. In addition, it allows work on the server to focus purely on the logic necessary to perform the task in question rather than worrying about data type formatting and how the client should be communicating with the server.

13.1.3. Summary of JPSpan

JPSpan is easy to use and integrates well with PHP. It can cope with classes that use the built-in types and more complex ones if they themselves are built from these. If JPSpan has one main failing, it is that the online documentation is somewhat sparse. Although PHP is covered extensively by numerous sites, if you need to customize JPSpan itself, you will need to dig into the source files and examples included in the basic installation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset