对于这个项目,我们还将使用事件驱动的方法来编写代码,正如第29章和第30章所介绍的。在这个例子中,我们不会绘制系统流程图,因为系统中只出现非常少的几个界面,而且这些页面之间的链接是非常简单的。
如图33-1所示的是用户访问Tahuayo时看到的主页面。
图 33-1 Tahuayo的第一个页面显示了该站点的所有主要特性:目录浏览、搜索和购物车可以看到,该站点的主要特性就是选中目录的显示以及显示这些目录中的物品。在默认情况下,第一页上显示当前销售情况最好的产品目录。如果一个用户点击其他目录,将看到该目录下相似的显示。
在我们进一步开始项目之前,需要掌握一些简单的术语:Amazon将目录当作是浏览节点。在贯穿整个代码和正式的文档中,我们都将发现这种表达方式。
文档提供了所有流行的浏览节点列表。此外,如果希望看到特定的节点,可以浏览Amazon站点并且从URL中读入它们,你可以在http://www.browsernodes.com/获得Browse Nodes资源。
奇怪的是,某些重要的目录,例如销售最佳的图书,无法通过浏览节点进行访问。
在这个页面的下方,还有许多图书和链接,但是无法从上图中看到(上图只是屏幕的截图)。我们在每一页上显示10本图书,同时显示链接到30多本相关图书。这种每页显示10本图书的设置是由Amazon确定的。而每页显示30本图书的设置是我们自己的选择。
从这个页面,用户可以点击并查看每一本图书的详细信息。这个页面如图33-2所示。
图 33-2 详细信息页面显示了关于特定图书的详细信息,同时还包括类似产品和概述虽然无法在一个截图中显示整个页面,但是我们已经尽量显示了更多的内容,但是并不是全部的,因为在这个页面上显示的信息是一个heavy查询得来的。我们选择过滤了图书以外的产品,以及不适合图书目录的其他产品列表。
如果点击图书的封面,可以看到一个扩大后的图书封面。
我们可能已经注意到了位于上图右上方的搜索文本框。这个搜索操作可以对站点的关键字进行搜索,同样,它也可以通过Web服务接口搜索Amazon的目录。一个搜索操作的输出结果示例如图33-3所示。
图 33-3 搜索batman的结果输出虽然我们只是列出了少量的目录,但是客户可以通过这个搜索工具找到任何想要的图书,并且浏览特定的图书。
每一本图书都有一个“添加到购物车(Add to Cart)”的链接。点击购物车汇总中的这个链接或“详细信息(Details)”链接,将显示购物车中的详细内容,如图33-4所示。
图 33-4 在购物车页面,可以删除物品,清空购物车或付账最后,当用户点击其中任何一个“付账(Checkout)”链接,都会将购物车中的详细信息发送给Amazon并且转到Amazon站点。这样,客户就将看到如图33-5所示的页面。
图 33-5 在将客户选购物品保存在Amazon购物车之前,系统将确认该交易,并显示Tahuayo购物车中的所有物品通过构建我们自己的前台应用程序和使用Amazon作为后台,可以理解本项目的含义。
由于这个项目仍然使用了事件驱动的方法,所以这个应用程序中的核心程序逻辑都是在一个文件中实现的——index.php。在该应用程序中,所用到的文件概述如表33-1所示。
我们还需要前面所介绍的nusoap.php文件,因为在以上文件中,这个文件是必需的。NuSOAP文件可以在附带的文件中找到,具体目录位于chapter33中,但是也可以从http://dietrich.ganx4.com/nusoap/index.php中找到该文件的最新版本(如果发布了新版本)。
下面,我们开始了解核心应用程序index.php文件。
33.3.1 核心应用程序
程序清单33-3所示的就是核心应用程序index.php文件。
程序清单33-3 index.php——核心应用程序文件
<?php
//we are only using one session variable/'cart/'to store the cart contents
session_start;
require_once(/'constants.php/');
require_once(/'Product.php/');
require_once(/'AmazonResultSet.php/');
require_once(/'utilityfunctions.php/');
require_once(/'bookdisplayfunctions.php/');
require_once(/'cartfunctions.php/');
require_once(/'categoryfunctions.php/');
//These are the variables we are expecting from outside.
//They will be validated and converted to globals
$external=array(/'action/',/'ASIN/',/'mode/',/'browseNode/',/'page/',/'search/');
//the variables may come via Get or Post
//convert all our expected external variables to short global names
foreach($external as$e){
if(@$_REQUEST[$e]){
$$e=$_REQUEST[$e];
}else{
$$e=/'/';
}
$$e=trim($$e);
}
//default values for global variables
if($mode==/'/'){
$mode=/'Books/';//No other modes have been tested
}
if($browseNode==/'/'){
$browseNode=53;//53 is bestselling non-fiction books
}
if($page==/'/'){
$page=1;//First Page-there are 10 items per page
}
//validate/strip input
if(!eregi(/'^[A-Z0-9]+$/',$ASIN)){
//ASINS must be alpha-numeric
$ASIN=/'/';
}
if(!eregi(/'^[a-z]+$/',$mode)){
//mode must be alphabetic
$mode=/'Books/';
}
$page=intval($page);//pages and browseNodes must be integers
$browseNode=intval($browseNode);
//it may cause some confusion,but we are stripping characters out from
//$search it seems only fair to modify it now so it will be displayed
//in the heading
$search=safeString($search);
if(!isset($_SESSION[/'cart/'])){
session_register(/'cart/');
$_SESSION[/'cart/']=array;
}
//tasks that need to be done before the top bar is shown
if($action==/'addtocart/'){
addToCart($_SESSION[/'cart/'],$ASIN,$mode);
}
if($action==/'deletefromcart/'){
deleteFromCart($_SESSION[/'cart/'],$ASIN);
}
if($action==/'emptycart/'){
$_SESSION[/'cart/']=array;
}
//show top bar
require_once(/'topbar.php/');
//main event loop.Reacts to user action on the calling page
switch($action){
case/'detail/':
showCategories($mode);
showDetail($ASIN,$mode);
break;
case/'addtocart/':
case/'deletefromcart/':
case/'emptycart/':
case/'showcart/':
echo/"<hr/><h1>Your Shopping Cart</h1>/";
showCart($_SESSION[/'cart/'],$mode);
break;
case/'image/':
showCategories($mode);
echo/"<h1>Large Product Image</h1>/";
showImage($ASIN,$mode);
break;
case/'search/':
showCategories($mode);
echo/"<h1>Search Results For/".$search./"</h1>/";
showSearch($search,$page,$mode);
break;
case/'browsenode/':
default:
showCategories($mode);
$category=getCategoryName($browseNode);
if(!$category||($category==/'Best Selling Books/')){
echo/"<h1>Current Best Sellers</h1>/";
}else{
echo/"<h1>Current Best Sellers in/".$category./"</h1>/";
}
showBrowseNode($browseNode,$page,$mode);
break;
}
require(/'bottom.php/');
下面,我们用自己的方法来了解这个文件。首先,我们创建了一个会话。就像前面所介绍的,将客户的购物车保存为一个会话变量。
接着,我们包括并引入了几个文件。这些文件都是下面将要介绍的函数,但是在介绍它们之前,我们必须先介绍第一个被包括进来的文件。constants.php文件定义了一些重要的常量,这些常量将在整个应用程序中使用。程序清单33-4给出了constants.php的所有代码。
程序清单33-4 constants.php——声明重要的全局常量和变量
<?php
//this application can connect via REST(XML over HTTP)or SOAP
//define one version of METHOD to choose.
//define(/'METHOD/',/'SOAP/');
define(/'METHOD/',/'REST/');
//make sure to create a cache directory an make it writable
define(/'CACHE/',/'cache/');//path to cached files
define(/'ASSOCIATEID/',/'XXXXXXXXXXXXXX/');//put your associate id here
define(/'DEVTAG/',/'XXXXXXXXXXXXXX/');//put your developer tag here
//give an error if software is run with the dummy devtag
if(DEVTAG==/'XXXXXXXXXXXXXX/'){
die(/"You need to sign up for an Amazon.com developer tag at
<a href=/"https://aws.amazon.com//">Amazon</a>
when you install this software.You should probably sign up
for an associate ID at the same time.Edit the file constants.php./");
}
//(partial)list of Amazon browseNodes.
$categoryList=array(5=>/'Computers&Internet/',3510=>/'Web Development/',
295223=>/'PHP/',17=>/'Literature and Fiction/',
3=>/'Business&Investing/',53=>/'Non Fiction/',
23=>/'Romance/',75=>/'Science/',21=>/'Reference/',
6=>/'Food&Wine/',27=>/'Travel/',
16272=>/'Science Fiction/'
);
这个应用程序可以使用REST或SOAP进行开发。修改METHOD常量值,可以选择所使用的开发方法。
CACHE常量定义了保存我们从Amazon站点上下载的数据的路径。可以修改该常量值,使其指向系统上任何希望的地方。
ASSOCIATEID常量定义了会员ID。如果是在事务中发送会员ID,将得到现金奖励。修改该常量,使其保存会员ID。
DEVTAG常量定义了Amazon在注册时为我们分配的开发人员令牌。必须将其修改为我们自己的开发人员令牌,否则该应用程序将无法正常运行。可以在如下URL注册一个开发人员令牌:http://aws.amazon.com。
现在,让我们回头看看index.php文件。它包含一些初始设置,以及主要的事件循环。首先,我们将从通过GET或POST方法传递进来的$_REQUEST超级全局变量中获取所需的变量。接着,将为这些标准的全局变量设置默认值,这些全局变量可以确定以后的页面显示,如下所示:
//default values for global variables
if($mode==/'/'){
$mode=/'Books/';//No other modes have been tested
}
if($browseNode==/'/'){
$browseNode=53;//53 is bestselling non-fiction books
}
if($page==/'/'){
$page=1;//First Page-there are 10 items per page
在以上代码中,我们将mode变量设置为/"books/"。Amazon支持许多模式(产品的类型),但是对于这个应用程序,我们只考虑图书。修改本章代码使其适用于其他产品目录不会太困难,只要重新设置$mode变量就可以了。此外,可能还需要查看Amazon文档,确认对于非图书类产品是否还具有其他属性,同时从用户界面上去除只与图书相关的文字。
browseNode变量用来指定要显示的图书种类。如果用户点击了/"Selected Categories/"链接,就可以自动设置图书种类。如果还没有设置(例如,当用户第一次来到该站点)我们仍然需要对其进行设置,设置为53。Amazon的浏览节点都是整数,用整数来标识一个种类。53表示非科幻图书类别,该类别也是一个非常不错的节点,就像那些出现在初始页面的类别,虽然有些最好的常见目录(例如,最佳销售)无法通过浏览节点进行访问。
page变量用来告诉Amazon我们希望在给定的种类中要显示的结果子集。page 1包含了1~10的结果,page 2包含了11~20的结果等。Amazon可以设置一个页面上的结果数,我们不用对其进行控制。当然,也可以在一页上显示Amazon站点上两页或更多页上的数据,但是对Amazon和我们的站点来说,10是都可以接受的,而且这样可以使我们的站点与Amazon保持一致。
接下来,我们将整理接收到的输入数据,可以通过搜索文本框或者是GET和POST参数:
//validate/strip input
if(!eregi(/'^[A-Z0-9]+$/',$ASIN)){
//ASINS must be alpha-numeric
$ASIN=/'/';
}
if(!eregi(/'^[a-z]+$/',$mode)){
//mode must be alphabetic
$mode=/'Books/';
}
$page=intval($page);//pages and browseNodes must be integers
$browseNode=intval($browseNode);
//it may cause some confusion,but we are stripping characters out from
//$search it seems only fair to modify it now so it will be displayed
//in the heading
以上代码没有什么新内容。safeString函数源自utilityfunctions.php函数库。它只是通过一个正则表达式的替换操作,从输入字符串中删除任何非字母字符。由于我们在前面已经介绍它,这里不再进行描述。
在应用程序中,我们对用户输入数据进行校验的主要原因在于,我们将使用客户的输入在缓存中创建文件名称。如果允许客户在输入中使用“..”或“/”,可能会遇到非常严重的问题。
接下来,如果客户还没有购物车的话,将为其设置一个购物车:
if(!isset($_SESSION[/'cart/'])){
session_register(/'cart/');
$_SESSION[/'cart/']=array;
在页面最上方的信息栏中显示信息之前(参阅图33-1),还需要完成一些操作。购物车将出现在每一个页面最上方的信息栏中。因此,在显示购物车内容之前,保持购物车变量为最新的是非常重要的:
//tasks that need to be done before the top bar is shown
if($action==/'addtocart/'){
addToCart($_SESSION[/'cart/'],$ASIN,$mode);
}
if($action==/'deletefromcart/'){
deleteFromCart($_SESSION[/'cart/'],$ASIN);
}
if($action==/'emptycart/'){
$_SESSION[/'cart/']=array;
在这里,我们将在显示购物车之前添加或删除一些物品。讨论购物车和付账时,我们还将介绍这些函数。如果想现在就了解这些函数,可以在cartfunctions.php文件中找到它们。现在,我们先将它们放到一边,因为首先必须理解Amazon的接口。
下一步,我们将包含并引入topbar.php文件。这个文件只包含了HTML和样式单,以及一个对ShowSmallCart函数的调用(源自cartfunctions.php)。这个函数将显示购物车的总结信息,我们将在每个页面右上方看到它们。介绍购物车函数时,我们还将介绍这些函数。
最后,我们介绍事件处理的主循环。表33-2给出了可能出现的事件总结。
可以看到,表33-2中的前4个事件都与获取和显示信息相关。而后4个事件都与管理购物车相关。
从Amazon获取数据的事件具有相似的工作方式。我们将以browsenode(种类)事件为例介绍如何获取关于图书的数据。