chitika Ad

Saturday, 1 March 2014

Zend Framework 101: Zend_Soap

Zend Framework 101: Zend_Soap

Summary

In this article I introduced you to the Zend_Soap component of the Zend Framework. I showed you how to create a basic web service with Zend_Soap_Server and then how to consume it with Zend_Soap_Client.
Part of this process included generating a WSDL file to describe all of the functions in the service. We achieved this by using Zend_Soap_AutoDiscover and PhpDoc.
Further reading:

Other Options

Zend Framework 101: Zend_Soap

Zend Framework 101: Zend_Soap

Further Improvements

As stated previously, this is a somewhat simplified implementation of web services. There are several things you can add to make this a more advanced implementation. These may include:
  • Complex data types, such as arrays and objects.
  • Type mapping (automatically mapping SOAP data types to local PHP classes on both the server and client). This allows easier manipulation of data passed to and returned from web service calls.
  • Authentication. There are two levels of authentication: one to protect who can access the web service directly, and the second is to let users authenticate via a web service so they receive an elevated level of access.
  • Error handling. We can't always assume data passed to web service calls is valid. The server needs the ability to send errors, and clients need to be able to detect errors and handle them accordingly.
  • Caching. Since every call to a web service can potentially be quite expensive (in terms of time and processing power), it can be extremely beneficial to implement a caching mechanism that allows you re-use the response from web service calls without having to communicate with the SOAP server.
  • WSDL file caching. In this article I haven't covered anything to do with caching of WSDL files. By default, the server and client will cache these files, but you can control this behaviour when instantiating Zend_Soap_Server and Zend_Soap_Client.
Hopefully in an upcoming article I will cover each of these aspects.

Zend Framework 101: Zend_Soap

Zend Framework 101: Zend_Soap

Consuming the Web Service

Now that we've created our web service (at the location http://example.com/webservice.php), we can create a client script to consume the web service.
For this, we are going to use Zend_Soap_Client class. You could use PHP's built-in SoapClient class (or even use a programming language other than PHP), but since this is a series on the Zend Framework we'll use the Zend_Soap_Client class.
Consuming the service is simply a matter of instantiating Zend_Soap_Client with the URL of the WSDL file (http://example.com/webservice.php?wsdl) as the only argument, then calling the functions from the PHP class we created earlier as if they were local function calls.
Note: There are several options that can be customised when instantiating Zend_Soap_Client, but to keep things simple I won't cover them here. You can read more athttp://framework.zend.com/manual/en/zend.soap.client.html.
Listing 5 shows how we make use of the web service calls. The PHP script can live on a completely different web server and URL to your server. After all, that is the point of web services!
Listing 5 Instantiating the Zend_Soap_Client class and making calls to the web service (client.php)
<?php
    require_once('Zend/Soap/Client.php');
 
    $url = 'http://exsmple.com/webservice.php?wsdl';
 
    $client = new Zend_Soap_Client($url);
 
    echo sprintf('Server Timestamp: %s', $client->getDate());
    echo "<br />\n";
    echo $client->getAgeString('Quentin', 28);
?>
In this code you will see we call $client->getDate() and $client->getAgeString(). When these functions are called, internally Zend_Soap_Client is communicating with the SOAP server specified in $url.
While this is a somewhat simplified implementation, it works, and it should demonstrate how to implement both the server and client aspects of a SOAP web service!

Zend Framework 101: Zend_Soap

Zend Framework 101: Zend_Soap

Handle SOAP requests

Next we must handle any requests to the web service. This involves filling in the "else" block from the previous listing.
To achieve this, we use the Zend_Soap_Server class. Just like Zend_Soap_AutoDiscover, it needs to the know the name of the class that holds the web service methods.
Additionally, when we instantiate Zend_Soap_Server we need to pass to it the URL of the web service. This is so the server knows which methods to handle, as well its data types (Zend_Soap_Server doesn't do any auto-discovery of the class you pass to it with setClass()).
You can either hard-code the URL of the WSDL file, or you can auto-generate its location as in Listing 4.
Note: This URL auto-generator is somewhat simple. You may need to account for different port names or for using HTTPS.
Listing 4 shows how to instantiate the Zend_Soap_Server class, set the WSDL, set the PHP class and then handle the request.
Listing 4 Handle SOAP requests (webservice.php)
<?php
    require_once('MyWebService.php');
 
    if ($_SERVER['QUERY_STRING'] == 'wsdl') {
        require_once('Zend/Soap/AutoDiscover.php');
 
        $auto = new Zend_Soap_AutoDiscover();
        $auto->setClass('MyWebService');
        $auto->handle();
    }
    else {
        require_once('Zend/Soap/Server.php');
 
        $wsdl = sprintf(
            'http://%s%s?wsdl',
            $_SERVER['HTTP_HOST'],
            $_SERVER['SCRIPT_NAME']
        );
 
        $soapServer = new Zend_Soap_Server($wsdl);
        $soapServer->setClass('MyWebService');
        $soapServer->handle();
    }
?>
That's all there is to it. The next step is to actually consume the web service, as covered in the next section.

Zend Framework 101: Zend_Soap

Zend Framework 101: Zend_Soap

Handle WSDL request

If the URL of your web service is http://example.com/webservice.php, then clients will expect to be able to retrieve the WSDL of your web service at http://example.com/webservice.php?wsdl. As such, we must handle requests to this URL.
When somebody hits the URL we will invoke the WSDL generator that comes with Zend_Soap in order to generate the WSDL file, then send it back to the client. The class we use to generate the WSDL isZend_Soap_AutoDiscover. All that is required is that we pass the name of the class we created in the previous section (MyWebService) to the setClass() method of our Zend_Soap_AutoDiscover instance.
Listing 2 shows the code we use to achieve this. We will improve on this code shortly.
Listing 2 Auto-generating the WSDL file (webservice.php)
<?php
    require_once('MyWebService.php');
    require_once('Zend/Soap/AutoDiscover.php');
 
    $auto = new Zend_Soap_AutoDiscover();
    $auto->setClass('MyWebService');
    $auto->handle();
?>
Note: We need to still include our class in the script so that Zend_Soap_AutoDiscover knows about it.
Figure 1 shows how the WSDL file might look in your browser
Figure 1 Representation of WSDL in Firefox
Figure 1: Representation of WSDL in Firefox
If you were to now visit the webservice.php file in your browser you will see the generated WSDL file (which is simply an XML file that web service clients know how to interpret).
As noted previously, we only want to send the WSDL when the client includes a query string of “wsdl”. To check for this we use the $_SERVER['QUERY_STRING'] variable.
Listing 3 Ensuring the WSDL is only served when requested (webservice.php)
<?php
    require_once('MyWebService.php');
 
    if ($_SERVER['QUERY_STRING'] == 'wsdl') {
        require_once('Zend/Soap/AutoDiscover.php');
 
        $auto = new Zend_Soap_AutoDiscover();
        $auto->setClass('MyWebService');
        $auto->handle();
    }
    else {
        // handle SOAP requests here
    }
?>
Now when you request the webservice.php file nothing will be shown in your browser, but if you request it as webservice.php?wsdl then the WSDL file will once again be displayed.
Another point of interest is that if you add a new method to the MyWebService class, it will automatically be added to the WSDL file, thereby making it available to all clients.

Zend Framework 101: Zend_Soap

Zend Framework 101: Zend_Soap

Create Web Service Methods

Our first step in creating a web service is to create a single PHP class that contains the methods you want people to be able to execute using SOAP. Each public method that belongs to this class is a method that can be executed your web service.
Tip: If you want public methods in your class that aren't auto-discovered (and thereby included in your WSDL), prefix the function name with a double-underscore. For instance, public function __myHiddenPublicFunction() { … }.
Each method in this class must be documented using the PhpDoc syntax, for the purposes of dynamically generating the WSDL file. At minimum this requires specifying input parameters (using @param) and the return data type (using @return). The reason for doing this is for generation of the WSDL file.
Let's being by creating a simple class with two methods. The first method (getDate()) will return the current date and time of the server. This method has no arguments and returns a string.
The second method (getAgeString()) accepts a name and an age and returns a nicely formatted string using these values. This method has a string argument and an integer argument and returns a string.
Listing 1 The PHP class with our web service methods (MyWebService.php)
<?php
    /**
     * Web service methods
     */
    class MyWebService
    {
        /**
         * Get the server date and time
         *
         * @return  string
         */
        public function getDate()
        {
            return date('c');
        }
 
 
        /**
         * Get a nicely formatted string of a person's age
         *
         * @param   string  $name   The name of a person
         * @param   int     $age    The age of a person
         * @return  string
         */
        public function getAgeString($name, $age)
        {
            return sprintf('%s is %d years old', $name, $age);
        }
    }
?>

Zend Framework 101: Zend_Soap

Zend Framework 101: Zend_Soap

How SOAP Works

The way SOAP typically works is that the SOAP client (which may be implemented in any language, such as PHP, ASP, Perl, Python or otherwise) connects to the web service endpoint and requests its WSDL (Web Services Description Language) file.
The WSDL file tells the client about the different function calls that are available, as well as a description of any complex types that are used in addition to the basic data types (such as string, integer or Boolean).
When we create our web service, we must not only handle calls to the various methods that we offer, but we must be able to send a WSDL file to clients. Thankfully, Zend_Soap can automate the generation of WSDL files, as I'll show you later in this article.
In the next three sections I will cover the key steps involved in creating a web service usingZend_Soap_Server. For the purposes of this example the web service will be located athttp://example.com/webservice.php.

Zend Framework 101: Zend_Soap

Zend Framework 101: Zend_Soap

This article is part of the series “Zend Framework 101”. The Zend Framework 101 series covers many components that make up the Zend Framework in easy to understand articles.. Read more about Zend Framework 101...

Introduction

In this article I will introduce you to the Zend_Soap component of the Zend Framework. This component allows you to easily create and consume SOAP web services. We will create a simple web service with several methods using Zend_Soap_Server, and consume the service with Zend_Soap_Client.
This article requires Zend Framework, downloadable from http://framework.zend.com. At time of writing, the current version of Zend Framework is 1.9.0.

Zend Framework 101: Zend_Session

Zend Framework 101: Zend_Session

Other Zend_Session Functionality

In addition to what's been covered so far in this article, there are some other advanced features that can be used with Zend_Session. This section shows introduces you to these features and provides a link for more information about the feature.

Storing Sessions in a Database

By default, PHP stores session data on the filesystem of the server. You can change this so session data is stored in a database. The database must be a connection made using the Zend_Db component. You can then make use of theZend_Session_SaveHandler_DbTable class with the Zend_Session::setSaveHandler() method.
Tip: If you don't use Zend_Db for accessing your database (or you want to save session data using some other method), you can create your own save handler by implementing the Zend_Session_SaveHandler_Interfaceinterface.
Storing session data in your database is useful in a load-balanced environment, where subsequent user requests may be handled by different front-end servers (all sharing the same database server).
For more information on saving session data to the database, refer tohttp://framework.zend.com/manual/en/zend.session.savehandler.dbtable.html.

Session Value and Namespace Expiration

You can make values in a session namespace (or an entire namespace) automatically expire after a set period of time. The time can be measured either in seconds or “hops” (that is, you can make a value only be set for the specified number of subsequent page requests).

Zend Framework 101: Zend_Session

Zend Framework 101: Zend_Session

Implementing "Remember Me" Functionality

A common feature of web sites where users can log in is the ability to be automatically logged-in when the user next visits the web site.
By default, PHP sessions use session cookies, which are cookies that the web browser stores in memory while the browser is running, and destroyed when the browser is exited.
To implement "remember me" functionality, you must change the lifetime of the session cookie so it is not destroyed when the user closes their browser. Zend_Session makes this easy to do.
Additionally, your PHP installation will also periodically delete session cookies after the time specified in the PHP configuration. If this occurs to soon, it doesn't matter whether the user's browser has remember their session ID – the session data will no longer be saved on the server!

Extending the Session Cookie Lifetime

To make the browser session cookie stay alive when users close their browser, use theZend_Session::rememberMe() static method. This method takes an optional single argument, which is the number of seconds before the cookie will expire. If no value is specified then a default of two weeks is used.
Listing 7 demonstrates how to use this function. You should call rememberMe() before performing any other session-related activity. Typically this would appear in your global bootstrapping script.
Listing 7 Changing session behaviour to remember the cookie for 7 days (listing-7.php)
<?php
    require_once('Zend/Session.php');
 
    $seconds = 60 * 60 * 24 * 7; // 7 days
    Zend_Session::rememberMe($seconds);
 
    // now the session cookie won't be deleted
    // when the browser is closed
?>
The forgetMe() function is the opposite of rememberMe() - it changes the session cookie so it will be removed when the browser is closed.

Increasing the PHP Session Garbage Collection Time

Every time a new session is created, a new file is written to the server to hold the session data. Over time this can result in a large number of files. To combat this, PHP has a garbage collection system that automatically deletes untouched session files (files that haven't been updated) after a specified amount of time. By default this time is 24 minutes (1440 seconds).
If the garbage collection time is too low then extending the length of the session cookie will have no effect, since the session data will no longer be stored on the server. To deal with this, you must ensure the PHPsession.gc_maxlifetime setting is at least the same value as the value passed to rememberMe().
In the previous listing, we used a value of 7 days (604800 seconds). In your PHP configuration (either inphp.ini file, your Apache httpd.conf file, or in a .htaccess file), use the setting in Listing 8.
Listing 8 Changing the garbage collection time to 7 days (.htaccess)
php_value session.gc_maxlifetime 604800
Caution: Ensure your sessions are written to a different filesystem location than for other web sites, since another site will likely use the lower garbage collection time, thereby resulting in your sessions being cleaned-up anyway.
You must be aware though that increasing the garbage collection time will result in a much larger number of session files stored at any one time. You should monitor your server after making this change to ensure not too much space is used.

An Example of Implementing "Remember Me"

Now that you know how to implement "remember me" functionality, let's look at a solid example. Many sites that offer this functionality ask the user if they want to be remembered. This example will offer that also.
Listing 9 shows the form you might use for logging in. This form consists of inputs for username and password, as well as the option to remember the user and a submit button.
Listing 9 A log in form with the option to remember the user (listing-9.php)
<form method="post" action="login.php">
    <div>
        Username:
        <input type="text" name="username" />
 
        Password:
        <input type="password" name="password" />
        <label>
            <input type="checkbox" name="remember" value="1" />
            Remember Me
        </label>
 
        <input type="submit" value="Log In" />
    <div>
</form>
Next we process this form, as shown in Listing 10. We'll skip the details here for authenticating a user by using a fictional log_in_user() function. If the user successfully logs in, we then check whether or not they want to be remembered. If they do, we call rememberMe(), otherwise we call forgetMe().
Note: Technically you probably don't need to call forgetMe() since the default PHP setting is to expire the cookie on browser close anyway.
This code is based on the code in Listing 3.
Listing 10 Processing the user log in and whether or not they want to be remembered (login.php)
<?php
    $username = $_POST['username'];
    $password = $_POST['password'];
 
    if (log_in_user($username, $password)) {
        require_once('Zend/Session.php');
 
        $remember = isset($_POST['remember']) && $_POST['remember'];
        $seconds  = 60 * 60 * 24 * 7; // 7 days
 
        if ($remember) {
            Zend_Session::RememberMe($seconds);
        }
        else {
            Zend_Session::ForgetMe();
        }
 
        $session = new Zend_Session_Namespace('identity');
        $session->username = $username;
 
        header('Location: members.php');
        exit;
    }
    else {
        // some error message for invalid login
    }
?>