Professional Documents
Culture Documents
By icarus
This article copyright Melonfire 2000-2002. All rights reserved.
Table of Contents
The Need For Speed . The Food Chain . . Return Of The Jedi . Digging Deeper . . In And Out . . . Different Strokes . . Bits And Bytes . . Of Form And Function No News Is Good News Cache Cow . . . Endgame . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 2 3 5 7 9 10 12 14 16 20
Dont worry if it didnt make too much sense - all will be explained shortly. For the moment, just feast your eyes on the output: Do, or do not. There is no try. -- Yoda, Star Wars Now refresh the page - you should see something like this, indicating that the second occurrence of the page has been retrieved from the cache. Do, or do not. There is no try. -- Yoda, Star Wars [cached] Lets take a closer look at how this was accomplished, on the next page.
Digging Deeper
When implementing a cache for your Web page, the first step is, obviously, to include the Cache_Lite class <?php // include the package require_once("Lite.php"); ?> You can either provide an absolute path to this file, or do what most lazy programmers do - include the path to your PEAR installation in PHPs "include_path" variable, so that you can access any of the PEAR classes without needing to type in long, convoluted file paths. The Cache_Lite object can support multiple caches simultaneously, so long as every cache created has a unique identifier. In this case, Ive used the identifier "starwars" to uniquely distinguish the cache Ill be using. <?php // set an ID for this cache $id = "starwars"; ?> Next, an object of the Cache_Lite class needs to be initialized, and assigned to a PHP variable. <?php // create a Cache_Lite object $objCache = new Cache_Lite($options); ?> This variable, $objCache, now serves as the control point for cache manipulation. The constructor of the Cache_Lite class can be provided with an associative array containing configuration parameters; these parameters allow you to customize the behaviour of the cache. In the example above, this array contains two parameters, "cacheDir", which specifies the directory used by the cache, and "lifeTime", which specifies the period for which data should be cached, in seconds. <?php // set some variables $options = array( "cacheDir" => "cache/", "lifeTime" => 50 );
?> Note that the directory specified must already exist, or else the cache will simply not work. Once an instance of the Cache_Lite object has been created, the business logic to use it becomes fairly simple. The first step is to check if the required data already exists in the cache. If it doesnt, it should be generated from the original data source, and a copy saved to the cache for future use. If it does, you can do something useful with it - write it to a file, pipe it to an external program or - as Ive done here - simply output it to the screen for all to admire. <?php // test if there exists a valid cache if ($quote = $objCache->get($id)) { // if so, display it echo $quote; // add a message indicating this is cached output echo " [cached]"; } else { // no cached data // implies this data has not been requested in last cache lifetime // so obtain it and display it $quote = "Do, or do not. There is no try. -- Yoda, Star Wars"; echo $quote; // also save it in the cache for future use $objCache->save($quote, $id); } ?> Most of this logic is accomplished via the get() and save() methods of the Cache_Lite object. The get() method checks to see if the data exists in the cache and returns it if so, while the save() method saves data to the cache. The save() method accepts the data to be saved, together with a unique identifier, as input arguments; the get() method uses this identifier to find and retrieve the cached data. The steps above make up a fairly standard process for using the Cache_Lite class, and youll see them being repeated over and over again in subsequent examples as well.
In And Out
The example you just saw cached the output of PHP statements. Its also possible to cache static HTML content, through creative use of PHPs output buffering functions. Consider the following example, which revises the code on the previous page to cache HTML markup instead of PHP command output: <?php // include the package require_once("Lite.php"); // set some variables $options = array( "cacheDir" => "cache/", "lifeTime" => 5 ); // create a Cache_Lite object $objCache = new Cache_Lite($options); // test if there exists a valid cache if ($page = $objCache->get("starwars")) { // if so, display it echo $page; // add a message indicating this is cached output echo " [cached]"; } else { // no cache // so display the HTML output // and save it to a buffer ob_start(); ?> <html> <head></head> <body> Do, or do not. There is no try. <br> -- Yoda, <i>Star Wars</i> </body> </html> <?php // page generation complete // retrieve the page from the buffer $page = ob_get_contents(); // and save it in the cache for future use $objCache->save($page, "starwars"); // also display the buffer and then flush it
ob_end_flush(); } ?> In this case, PHPs output buffering functions have been used to capture all the output generated subsequent to the call to ob_start(), and store this output in a buffer. Once the entire output has been captured, the ob_get_contents() function is used to retrieve the buffer contents to a variable. This variable (which now contains the entire HTML page) is then stored in the cache for future use. Finally, the ob_end_flush() function is used to end output buffering and send the contents of the buffer to the browser.
Different Strokes
An alternative method to accomplish the task on the previous page lies with the Cache_Lite_Output() class, a subclass of the Cache_Lite class. This subclass (which internally uses output buffering) inherits all of the attributes of the parent class, and adds two methods to the parent class method collection: a start() method, which begins caching data, and an end() method, which marks the end of data caching. Consider the following variant of the example on the previosu page, which illustrates how this may be used: <?php // include the package require_once("Output.php"); // set some variables $options = array( "cacheDir" => "cache/", "lifeTime" => 5 ); // create a Cache_Lite_Output object $objCache = new Cache_Lite_Output($options); // test if there exists a valid cache // if so, display it, else regenerate the page if (!$objCache->start("starwars")) { ?> <html> <head></head> <body> Do or do not. There is no try. <br> -- Yoda, <i>Star Wars</i> </body> </html> <?php // end caching $objCache->end(); } ?> Over here, if the call to start() - which is provided with a cache identifier - returns true, it implies that the requested data has already been cached, and the Cache_Lite_Output object takes care of printing the contents of the cache. If, however, the call to start() returns false, it implies that the data is not present in the cache, and so all subsequent output generated by the script will be cached, until the objects end() method is invoked.
// second data block // test if there is a valid cache for this block if ($data = $objCache->get("featured_character")) { // if so, display it echo $data; } else { // formulate and execute query $query = "SELECT char FROM characters ORDER BY RAND() LIMIT 1"; $result = mysql_query($query) or die("Error in query: " . mysql_error()); // get record $row = mysql_fetch_object($result); // display it echo $row->char; // and also cache it $objCache->save($row->char, "featured_character"); } // close connection mysql_close($connection); ?> In this case, the code for the header and footer blocks is cached; however, the data in between the two is not. Note the use of two different cache identifiers in this example, one for each block.
As you might imagine, this class has the potential to do much more than simply confuse users in search of the time. Flip the page to see what I mean.
// iterate over return value and print elements echo "<ul>"; foreach($data as $h) { echo "<li>$h"; } echo "</ul>"; ?> Heres what the output looks like:
Cache Cow
Now that you know the theory, lets wrap this tutorial up with a real-world example of how useful the Cache_Lite class can be in improving performance on your Web site. As you may (or may not) know, Amazon.com recently opened up their product catalog, allowing developers to create Amazon-backed online stores with their new AWS service. This service allows develoeprs to interact with Amazon.coms catalog and transaction system using SOAP, and it represents an important milestone in the progress of XML-based Web services. The only problem? Querying the AWS system, retrieving XML-encoded data and formatting it for use on a Web page is a complex process, one which can affect the performance of your store substantially. In order to compensate for this, clever developers can consider caching some of the AWS data on their local systems, reducing the need to query Amazon.com for every single request and thereby improving performance on their site. Heres the script, <?php // include Cache_Lite_Output class require_once("Output.php"); // set an ID for this cache $id = "MyStore"; // configure the cache $options = array( "cacheDir" => "cache/", "lifeTime" => 1800 ); // instantiate the cache $objCache = new Cache_Lite_Output($options); // does data exist in cache? // if so, print it // else regenerate it if (!$objCache->start($id)) { // include SOAP class include("nusoap.php"); // create a instance of the SOAP client object $soapclient = new soapclient("http://soap.amazon.com/schemas2/AmazonWebServices.wsdl", true); // uncomment the next line to see debug messages // $soapclient->debug_flag = 1; // create a proxy so that WSDL methods can be accessed directly
$proxy = $soapclient->getProxy(); // set up an array containing input parameters to be // passed to the remote procedure $params = array( browse_node => 1000, page => 1, mode => books, tag => melonfire-20, type => lite, devtag => YOUR_TOKEN_HERE, sort => +salesrank ); // invoke the method $result = $proxy->BrowseNodeSearchRequest($params); // check for errors if ($result[faultstring]) { echo $result[faultstring]; } else { // no errors? $total = $result[TotalResults]; $items = $result[Details]; // format and display the results ?> <html> <head> <basefont face="Verdana"> </head> <body bgcolor="white"> <p> <p> <table width="100%" cellspacing="0" cellpadding="5"> <tr> <td bgcolor="Navy"><font color="white" size="-1"><b>Bestsellers</b></font> </td> <td bgcolor="Navy" align="right"><font color="white" size="-1"><b> <? echo date("d M Y",mktime());?></b> </font></td> </tr> </table> <p> Browse the catalog below: <p> <table width="100%" border="0" cellspacing="5" cellpadding="0"> <?php // parse the $items[] array and extract the necessary information // (image, price, title, catalog, item URL) foreach ($items as $i) { ?>
<tr> <td align="center" valign="top" rowspan="4"> <a href="<? echo $i[Url]; ?>"><img border="0" src=<? echo $i[ImageUrlSmall]; ?>></a></td> <td><font size="-1"><b><? echo $i[ProductName]; ?></b> / <? echo $i[Catalog]; ?> / <? echo $i[Manufacturer]; ?> </font></td> </tr> <tr> <td align="left" valign="top"> <font size="-1">List Price: <? echo $i[ListPrice]; ?> /Amazon.com Price: <? echo $i[OurPrice]; ?> </font></td> </tr> <tr> <td align="left" valign="top" ><font size="1"> Release Date: <? echo $i[ReleaseDate]; ?></font> </td> </tr> <tr> <td align="left" valign="top"><font size="-1"><a href="<? echo $i[Url]; ?>"> Read more about this title on Amazon.com</a></font></td> </tr> <tr> <td colspan=2> </td> </tr> <? } ?> </table> <font size="2"> Disclaimer: All product data on this page belongs to Amazon.com. No guarantees are made as to accuracy of prices and information. YMMV! </font> </body> </html> <?php } $objCache->end(); } ?>> and heres what the output looks like:
Im assuming here that you already know how to use AWS (in case you dont, flip the page for some links to tutorials that will teach you the basics, and remember that youll need a free AWS developer token for the script above to work) and instead will focus on the caching aspects of the system. Since this Web page contains intermingled HTML and PHP code, its convenient to use the Cache_Lite_Output class here. The first task, obviously, is to initialize and configure the cache; Ive set the cache to refresh itself at 30-minute intervals, since this seems like a reasonable period of time. Next, Ive used the start() method to see if any data already exists in the cache, and display it if so. If no data exists, the NuSOAP PHP class is include()-d in the script, a SOAP client is instantiated, and a request is made to Amazon.com to obtain a list of bestsellers (node ID 100 in the AWS system). The response is then parsed and formatted into a Web page suitable for display; it is also simultaneously saved to the cache so that future requests for the same page can be served instantly, without having to query the AWS system each time. The end result: faster responses to user clicks, and an overall enhancement in user perception of your sites performance.
Endgame
And thats about all we have time for. In this article, I introduced you to the PEAR Cache_Lite class, a PHP class designed specifically to provide a robust caching mechanism for Web pages. I showed you how to configure the location and the lifetime of the cache, and demonstrated how to use the class to cache both static HTML content and dynamic PHP output. I also gave you a quick tour of the Cache_Lite class two variants, the Cache_Lite_Output and Cache_Lite_Function classes, illustrating how they could be used to cache blocks of output and function return values, respectively. Finally, I wrapped things up with a real-world example, showing you how a cache can make a substantial difference when dealing with complex, XML-based applications like Amazon Web Services. In case youd like to learn more about the topics discussed in this article, you should consider visiting the following links: Documentation for the PEAR Cache_Lite class, at http://pear.php.net/package-info.php?pacid=99 phpCache, a lightweight alternative to Cache_Lite, at http://0x00.org/php/phpCache/, and a tutorial on how to use it, at http://www.sitepoint.com/article/685 A comprehensive resource on the topic of Web caching, at http://www.web-caching.com/ An article discussing the benefits of caching your Web content, at http://www.ariadne.ac.uk/issue4/caching/ Using Amazon Web Services With PHP And SOAP, at http://www.melonfire.com/community/columns/trog/article.php?id=162 Until next time...stay healthy! Note: Examples are illustrative only, and are not meant for a production environment. Melonfire provides no warranties or support for the source code described in this article. YMMV!