在本系列第一部分,我向您介绍了 YQL,Yahoo! Query Language,该语言提供一个统一的、类似 SQL 的接口来检索来自多个 web 服务 APIs 的数据。我展示了如何从一个 PHP 应用程序访问 YQL 服务,提供了一些示例来说明 YQL 如何允许您利用 SQL 类型的结构来过滤、排列和链接查询结果。
在第二篇、也是本系列的最后一篇中,我深入探究了 YQL,并展示了如何从 PHP 应用程序内使用 YQL 来添加、编辑和删除第三方 web 服务上的数据。我还展示了如何使用 YQL 搜索和检索 HTML 页面上的数据以及 RSS 和 Atom 等结构化 XML 格式的数据。
正如在前一篇文章中所看到的,YQL 允许您使用 SELECT 语句提取来自第三方 web 服务的数据。但是,SELECT 并非 YQL 支持的惟一 SQL 语句;您还可以使用 INSERT、DELETE 和 UPDATE 查询、运用相同的 SQL 语法操作第三方服务上的数据。
要了解其运作方式,请查看 清单 1,其中使用一个 INSERT 查询在 Wordpress.com 上将一篇博客文章添加到用户的博客上。要运行这示例,您必须用有效的 Wordpress.com 帐户凭据替换 BLOGNAME、BLOGUSER 和 BLOGPASS 变量;您可以注册一个免费 Wordpress.com 博客来获取这些(参见 参考资料 获取链接)。
清单 1. 使用 YQL INSERT 查询添加数据
<?php
// set up Zend auto-loader
// load Zend REST client classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Rest_Client');
// execute YQL query
// add new post to Wordpress blog using POST
try {
$client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
$client->env('store://datatables.org/alltableswithkeys');
$client->q("INSERT INTO wordpress.post
(title, description, blogurl, username, password) VALUES
('Hello world', 'This is my first blog post with YQL...woohoo!',
'http://BLOGNAME.wordpress.com', 'BLOGUSER', 'BLOGPASS')");
$result = $client->post();
echo 'Entry posted with ID: ' . $result->results->postid;
} catch (Zend_Rest_Client_Exception $e) {
echo "Client error: " . $e->getResponse();
exit;
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
exit;
}
?>
|
如 清单 1 所示,使用 YQL 执行一个 INSERT 查询与在任何兼容 SQL 的 RDBMS 上执行 INSERT 查询一样。您必须指定要插入数据的表名,后面紧接一列键和值。YQL 使用表定义将查询数据转化成一个 XML 编码的数据包,之后通过 POST 将该数据包提交给 Wordpress.com web 服务。响应以一个标准 YQL 结果文档的形式返回给客户端,该文档包含新插入文章的 ID。
图 1 展示了 Wordpress.com 博客上新增的文章。
图 1. 使用 YQL 添加的博客文章
清单 2 提供 清单 1 的一个更交互式的版本,允许用户直接将内容输入到一个 web 表单,并将该内容转化为一篇 Wordpress.com 博客文章。
清单 2. 使用 YQL 交互式地添加博客文章
<html>
<head></head>
<body>
<h2>Post Blog Entry</h2>
<form method="post" action="<?php echo htmlentities($_SERVER['PHP_SELF']); ?>">
<div>
Title: <br/>
<input type="text" name="title" size="40" />
</div>
<div>
Body: <br/>
<textarea name="body" rows="5" cols="30"></textarea>
</div>
<div>
Blog URL: <br/>
http:// <input type="text" name="prefix" size="40" /> .wordpress.com
</div>
<div>
Username: <br/>
<input type="text" name="user" size="40" />
</div>
<div>
Password: <br/>
<input type="text" name="pass" size="40" />
</div>
<input type="submit" name="submit" value="Post" />
</form>
<?php
if (isset($_POST['submit'])) {
// validate input (omitted for brevity)
$title = $_POST['title'];
$body = $_POST['body'];
$blog = 'http://' . $_POST['prefix'] . '.wordpress.com';
$user = $_POST['user'];
$pass = $_POST['pass'];
// set up Zend auto-loader
// load Zend REST client classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Rest_Client');
// execute YQL query
try {
$client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
$client->env('store://datatables.org/alltableswithkeys');
$client->q(
"INSERT INTO wordpress.post (title, description, blogurl, username, password)
VALUES ('$title', '$body', '$blog', '$user', '$pass')");
$result = $client->post();
} catch (Zend_Rest_Client_Exception $e) {
echo "Client error: " . $e->getResponse();
exit;
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
exit;
}
// iterate over query result set
echo 'Entry posted with ID: ' . $result->results->postid;
}
?>
</body>
</html>
|
图 2 展示显示给用户的 web 表单以及一些输入。
图 2. 用于交互式地向 Wordpress.com 添加博客文章的一个 web 表单
图 3 显示提交表单之后 Wordpress.com 上的博客文章。
图 3. Wordpress.com 上新增的博客文章
与使用 INSERT 一样,您也可以使用 UPDATE 和 DELETE 语句。比如,清单 3 使用一个 UPDATE 查询来更新 清单 1 中新创建的文章。
清单 3. 使用 YQL UPDATE 查询修改数据
<?php
// set up Zend auto-loader
// load Zend REST client classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Rest_Client');
// execute YQL query
// update post on Wordpress blog
try {
$client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
$client->env('store://datatables.org/alltableswithkeys');
$client->q(
"UPDATE wordpress.post SET title='Updated blog post',
description='Look, ma, I updated my blog post via YQL', publish='true'
WHERE blogurl = 'http://BLOGNAME.wordpress.com' AND
username = 'BLOGUSER' AND password = 'BLOGPASS' AND postid = '5'");
$result = $client->post();
if ($result->methodResponse->params->param->value->boolean == 1) {
echo 'Post successfully updated';
} else {
throw new Exception ('Could not delete post');
}
} catch (Zend_Rest_Client_Exception $e) {
echo "Client error: " . $e->getResponse();
exit;
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
exit;
}
?>
|
与任何数据库 UPDATE 查询一样,您必须向 YQL 查询传递要更新的字段列表,以及其新值和将更新限制为一组记录的一个 WHERE 子句。在 清单 3 中,WHERE 子句包含用户凭据以及要更新的文章 ID。
图 4 展示 Wordpress.com 博客上已更新的文章。
图 4. 通过 YQL 修改的博客文章
清单 4 展示如何使用 YQL 删除一篇文章。
清单 4. 使用 YQL DELETE 查询删除数据
<?php
// set up Zend auto-loader
// load Zend REST client classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Rest_Client');
// execute YQL query
// delete post from Wordpress blog
try {
$client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
$client->env('store://datatables.org/alltableswithkeys');
$client->setConfig(array('timeout' => 30));
$client->q(
"DELETE FROM wordpress.post WHERE blogurl = 'http://BLOGNAME.wordpress.com'
AND username = 'BLOGUSER' AND password = 'BLOGPASS' AND postid = '5'");
$result = $client->post();
if ($result->methodResponse->params->param->value->boolean == 1) {
echo 'Post successfully deleted';
} else {
throw new Exception ('Could not delete post');
}
} catch (Zend_Rest_Client_Exception $e) {
echo "Client error: " . $e->getResponse();
exit;
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
exit;
}
?>
|
注意,在撰写本文时,Wordpress.com 的 YQL 社区表不包含 UPDATE 和 DELETE 功能支持,不过一个补丁正在开发中。如果运行前几个清单有问题,您可以引用作者的这些补丁版本的表,修改 YQL 查询字符串,以通过一个 USE 子句引用自定义表。示例如下:
USE 'http://github.com/vikram0/yql-tables/raw/master/wordpress/wordpress.post.xml' AS wordpress.post; DELETE FROM wordpress.post WHERE blogurl = 'http://BLOGNAME.wordpress.com' AND username = 'BLOGUSER' AND password = 'BLOGPASS' AND postid = '5' |
在本例中,您可能还需要调整客户端超时,如下所示:
$client->setConfig(array('timeout' => 30));
|
在掌握了所有这些背景信息之后,让我们来看一下如何使用 YQL 数据修改功能来构建一个示例应用程序。在本例中您的目标 web 服务是 Payvment(参见 参考资料 获取链接),即一个 “云中购物车” 实现。顾名思义,Payvment 允许您使用一个 REST API 创建、编辑和修改购物车,这样就可以很容易地将购物车功能整合到一个 web 应用程序中。Payvment 还使一个社区开放数据表可供 YQL 使用,支持您使用 YQL(而非访问 Payvment REST API)来执行购物车操作。
考虑到这一点,让我们组合一个示例应用程序,使用 YQL 和 PHP 来构建一个云中的购物车。该应用程序定义一个基本产品目录,含有每个 SKU 的价格和说明,允许用户使用 YQL INSERT 查询添加这些商品到其 Payvment 车。用户还可以使用 UPDATE 或 DELETE 查询修改数量或从购物车中删除商品。您可以使用一个 SELECT 查询随时检索购物车的当前内容(包含小计)。
清单 5 列出了完整的代码。
清单 5. 使用 Payvment YQL 社区表构建的购物车
<?php
// set API keys and constants
define (PAYVMENT_API_KEY, 'YOUR-API-KEY');
define (PAYVMENT_ID, 'YOUR-ID');
// define simple product catalog
$catalog = array(
'167' => array('title' => 'Flying hammer', 'price' => 35.99),
'543' => array('title' => 'Lightsaber', 'price' => 76.99),
'129' => array('title' => 'Sonic screwdriver', 'price' => 10.99)
);
// set up Zend auto-loader
// load Zend REST client classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Rest_Client');
// start session
session_start();
try {
// check if a cart already exists, if not, get a new cart ID
if (!isset($_SESSION['cart'])) {
$client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
$query = "USE 'http://github.com/vikram0/yql-tables/raw/master/shoppingcart/
shoppingcart.YourProducts.xml' AS shoppingcart;
SELECT * FROM shoppingcart WHERE apiKey='" . PAYVMENT_API_KEY . "'";
$client->q($query);
$result = $client->get();
if ($result->results->cartfeed->status->StatusCode != 200) {
throw new Exception ('ERROR: Cannot get shopping cart');
}
$_SESSION['cart'] = (string) $result->results->cartfeed->results->CartID;
}
// check for add/edit/delete operations
switch(strtolower($_REQUEST['op'])) {
// add = INSERT
case 'add':
$id = $_REQUEST['id'];
$client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
$query =
"USE 'http://github.com/vikram0/yql-tables/raw/master/shoppingcart/
shoppingcart.YourProducts.xml' AS shoppingcart;
INSERT INTO shoppingcart (apiKey, cartID, payvmentID, itemID, itemName,
itemPrice) VALUES ('" . PAYVMENT_API_KEY . "', '" .
$_SESSION['cart'] . "', '" . PAYVMENT_ID . "', '$id', '" .
$catalog[$id]['title'] . "', '" . $catalog[$id]['price'] . "')";
$client->q($query);
$result = $client->post();
if ($result->results->cartfeed->status->StatusCode != 200) {
throw new Exception ('ERROR: Cannot add item to shopping cart');
}
break;
// remove = DELETE
case 'remove':
$id = $_REQUEST['id'];
$client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
$query =
"USE 'http://github.com/vikram0/yql-tables/raw/master/shoppingcart/
shoppingcart.YourProducts.xml' AS shoppingcart;
DELETE FROM shoppingcart WHERE apiKey = '" . PAYVMENT_API_KEY . "'
AND cartID = '" . $_SESSION['cart'] . "' AND itemID = '" . $id . "'
AND retailerID = '" . PAYVMENT_ID . "'";
$client->q($query);
$result = $client->post();
if ($result->results->cartfeed->status->StatusCode != 200) {
throw new Exception ('ERROR: Cannot remove item from shopping cart');
}
break;
// update = UPDATE
case 'update':
$id = $_REQUEST['id'];
$qty = $_REQUEST['qty'];
$client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
$query =
"USE 'http://github.com/vikram0/yql-tables/raw/master/shoppingcart/
shoppingcart.YourProducts.xml' AS shoppingcart;
UPDATE shoppingcart SET itemQty = $qty WHERE apiKey = '" . PAYVMENT_API_KEY . "'
AND cartID = '" . $_SESSION['cart'] . "' AND itemID = '" . $id . "'
AND retailerID = '" . PAYVMENT_ID . "'";
$client->q($query);
$result = $client->post();
if ($result->results->cartfeed->status->StatusCode != 200) {
throw new Exception ('ERROR: Cannot update item in shopping cart');
}
break;
}
// get current cart contents (summary and individual items)
$client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
$query =
"USE 'http://github.com/vikram0/yql-tables/raw/master/shoppingcart/
shoppingcart.YourProducts.xml' AS shoppingcart;
SELECT * FROM shoppingcart WHERE apiKey='" . PAYVMENT_API_KEY . "'
AND cartID='" . $_SESSION['cart'] . "'";
$client->q($query);
$result = $client->get();
if ($result->results->cartfeed->status->StatusCode == 200) {
$cart_summary = $result->results->cartfeed->results;
$cart_items = $result->results->cartfeed->items->storeitems;
} else {
throw new Exception ('ERROR: Cannot get shopping cart contents');
}
} catch (Exception $e) {
die($e->getMessage());
}
?>
<html>
<head>
<style type="text/css">
table {
border-width: 1px;
border-spacing: 2px;
border-style: outset;
border-color: red;
border-collapse: collapse;
background-color: white;
}
table th {
border-width: 1px;
padding: 4px;
border-style: inset;
border-color: red;
background-color: white;
-moz-border-radius: 0px 0px 0px 0px;
font-weight: bolder;
}
table td {
border-width: 1px;
padding: 4px;
border-style: inset;
border-color: red;
background-color: white;
-moz-border-radius: 0px 0px 0px 0px;
}
</style>
</head>
<body>
<h2>Current Shopping Cart</h2>
<table>
<tr>
<th>Item</th>
<th>Price</th>
<th>Quantity</th>
<th>Subtotal</th>
<th></th>
<th></th>
</tr>
<?php foreach ($cart_items->item as $i): // display current cart ?>
<form method="post">
<input type="hidden" name="id" value="<?php echo $i->ItemID; ?>" />
<tr>
<td><?php echo $i->ItemName; ?></td>
<td><?php echo $i->ItemPrice; ?></td>
<td><input type="text" name="qty" size="3"
value="<?php echo $i->ItemQty; ?>" /></td>
<td><?php echo $i->ItemQtyPrice; ?></td>
<td><input type="submit" name="op" value="Update" /></td>
<td><input type="submit" name="op" value="Remove" /></td>
</tr>
</form>
<?php endforeach; ?>
<tr>
<td colspan="3">Total</td>
<td><?php echo $cart_summary->GrandTotal; ?></td>
<td colspan="2"></td>
</tr>
</table>
<h2>Product Catalog</h2>
<table>
<tr>
<th>Title</th>
<th>Price</th>
<th></th>
</tr>
<?php foreach ($catalog as $id => $item): // display product catalog ?>
<form method="post">
<input type="hidden" name="id" value="<?php echo $id; ?>" />
<tr>
<td><?php echo $item['title']; ?></td>
<td><?php echo $item['price']; ?></td>
<td><input type="submit" name="op" value="Add" /></td>
</tr>
</form>
<?php endforeach; ?>
</table>
</body>
</html>
|
这段代码乍看起来让人生畏,但是它实际上比较简单。下面是对 清单 5 中关键部分的细分:
- 脚本的前几行定义用户的 Payvment API 键和惟一用户标识符。这些令牌可从 Payvment 站点(参见 参考资料 获取链接)免费获取(注册后)。在这几行代码之后,脚本将一个简单产品目录定义为一个数组,以数组键为 SKUs,而对应的值存放产品信息。该产品目录被格式化为一个 HTML 表,带有可供用户添加商品到其购物车的控件。
- 接下来,脚本初始化一个会话,检查会话是否已经包含一个购物车 ID。这个 ID 旨在跨 Payvment API 惟一标识用户的购物车,且它必须包含在大部分 API 调用中。如果会话中有购物车 ID,脚本继续下一步;如果没有,它制定并执行一个返回新购物车 ID 的虚拟 SELECT 查询,然后将该 ID 保存到会话。
- 之后脚本检查看当前请求是否包含一个添加、编辑或删除操作,由 $_REQUEST['op'] 变量表示。根据所请求的操作,Zend_Rest_Client 使用 YQL 对 Payvment 数据执行一个 INSERT、UPDATE 或 DELETE 查询。注意,每个查询都包含 AIP 键、用户 ID,有时还包含被修改或删除的商品的 ID。
- 最后,脚本执行一个 SELECT 查询来检索购物车的当前内容。然后该信息被格式化为一个可显示的 HTML 表,连同用于修改数量或删除商品的适当控件。注意,Payvment API 根据添加商品到购物车时提供的信息自动计算商品小计和购物车总量。
注意,在撰写本文时,Payvment 服务的 YQL 社区表使用一个过期的 REST API 端点,不过一个补丁正在开发中。上一个清单利用作者的补丁版本的表,在 Github(参见 参考资料)中有提供。
图 5 展示运行中的应用程序。
图 5. 基于云的购物车
通过 YQL 使用 INSERT、DELETE 和 UPDATE 查询时有一个要点需要注意。支持这些操作的大部分 web 服务都需要一个用户名、密码和/或 AIP 键来执行数据修改;通常您可以从每个服务的网站免费获取这些凭据。在这些情况下,添加或修改数据的程序类似于前面的清单,其中凭据构成 YQL 查询的一部分,通过 Zend_Rest_Client 被传递给 YQL 查询。
对于需要 OAuth 身份验证的服务,最好使用一个支持 OAuth 的工具包,比如 Yahoo! Open Social SDK,而非 Zend_Rest_Client,因为它为您处理 OAuth 身份验证的所有细节。您可以在 参考资料 中找到对这个 SDK 的链接,以及到其他文档的链接。
在本文的前半部分,您了解了 YQL 如何过滤和排列搜索结果,使用 WHERE 子句支持和实用功能来对结果集进行排序、计数,并删除重复结果。不过这些只是一部分;YQL 还能够通过本地 和远程 分页限值来限制结果集中的记录数。
一个远程 限值控制 YQL 从第三方 Web 服务检索的记录的数量;一个本地 限值控制 YQL 返回给调用应用程序的记录的数量。
如果没有指定限值,YQL 通常会返回 10 条记录。要修改这个数目,在圆括号中向查询传递一个限值,比如,下面的示例将远程限值设置为 15 条记录:
SELECT * FROM music.artist.popular (15) |
要返回所有记录,将限值设定为 0,如下所示:
SELECT * FROM music.artist.popular (0) |
本地限值通过 LIMIT 和 OFFSET 子句执行,其运作方式与 SQL 语句很像:LIMIT 子句定义要返回的记录数,而 OFFSET 子句指定起始偏移值。下面的示例返回结果集中的第 5 到 9 条记录:
SELECT * FROM music.artist.popular (15) LIMIT 5 OFFSET 5 |
拥有这些控制之后,使用像 Zend_Paginator 这样的分页小部件就可以相当容易地为 YQL 结果实现简单的分页系统。比如,清单 6 展示一种为 YQL 结果分页的方法:
清单 6. 使用 Zend_Paginator 为 YQL 结果分页
<?php
// set up Zend auto-loader
// load Zend REST client classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Rest_Client');
Zend_Loader::loadClass('Zend_Paginator');
Zend_Loader::loadClass('Zend_Paginator_Adapter_Array');
try {
// execute YQL query
$client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
$client->q("SELECT name FROM music.artist.popular (0)");
$result = $client->get();
$data = array();
foreach ($result->results->Artist as $artist) {
$data[] = (string)$artist['name'];
}
// initialize pager with data set
$pager = new Zend_Paginator(new Zend_Paginator_Adapter_Array($data));
// set page number from request
$currentPage = isset($_GET['p']) ? (int) htmlentities($_GET['p']) : 1;
$pager->setCurrentPageNumber($currentPage);
// set number of items per page from request
$itemsPerPage = isset($_GET['c']) ? (int) htmlentities($_GET['c']) : 10;
$pager->setItemCountPerPage($itemsPerPage);
// get pages
$pages = $pager->getPages();
// create page links
$pageLinks = array();
$separator = ' | ';
for ($x=1; $x<=$pages->pageCount; $x++) {
if ($x == $pages->current) {
$pageLinks[] = $x;
} else {
$q = http_build_query(array('p' => $x, 'c' => $itemsPerPage));
$pageLinks[] = "<a href=\"?$q\">$x</a>";
}
}
} catch (Zend_Rest_Client_Exception $e) {
echo "Client error: " . $e->getResponse();
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}
// iterate over current page set
echo '<h2>Popular Artists</h2>';
$ctr = $pages->firstItemNumber;
foreach ($pager->getCurrentItems() as $item) {
echo $ctr . '. ' . $item . '<br/>';
$ctr++;
}
// print page links
echo '<div>';
echo 'Pages:' . implode($pageLinks, $separator);
echo '</div>';
?>
|
清单 6 首先载入 Zend_Paginator 组件。顾名思义,这是一个现成的 Zend Framework 组件,您可以用它来为大型数据集分页。它还提供一套现成的方法来生产页面链接,便于用户浏览结果集的不同页面。
加载 Zend_Paginator 之后,清单 6 执行一个无限制的 SELECT 查询来检索完整的 YQL 结果集。然后将返回的数据报告给一个 PHP 数组,并将其传递给 Zend_Paginator 数组数据源,该数据源负责提取当前页面所需的数组子集。Zend_Paginator 还负责生成页面导航链接,从而允许用户向前或向后浏览结果集。
该方法不是为 YQL 结果分页最有效的方式,您应当避免在生产环境中使用该示例。清单 6 必须重新对每个请求执行 YQL 查询,向分页练习添加时间和性能成本。一种更有效的方法是将 YQL 结果集存储在一个服务器端缓存,并从那里为每个请求检索数据,而不是从 YQL 服务请求它。清单 7 展示了这种改进的方法,使用 Zend_Cache 将 YQL 结果保存在一个服务器端文件缓存中。
清单 7. 使用 Zend_Paginator 和 Zend_Cache 的一种更有效的分页方法
<?php
// set up Zend auto-loader
// load Zend REST client classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Rest_Client');
Zend_Loader::loadClass('Zend_Paginator');
Zend_Loader::loadClass('Zend_Cache');
Zend_Loader::loadClass('Zend_Paginator_Adapter_Array');
try {
// set up cache
$front = array(
'lifetime' => 2000,
'automatic_serialization' => true
);
$back = array(
'cache_dir' => './'
);
$cache = Zend_Cache::factory('Core', 'File', $front, $back);
// look for YQL data in cache and use if available
// if not, execute YQL query, get fresh result set and save to cache
if(!$data = $cache->load('yql')) {
// execute YQL query
// get total count of items
$client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
$client->q("SELECT name FROM music.artist.popular (0)");
$result = $client->get();
$data = array();
foreach ($result->results->Artist as $artist) {
$data[] = (string)$artist['name'];
}
$cache->save($data, 'yql');
}
// initialize pager with data set
$pager = new Zend_Paginator(new Zend_Paginator_Adapter_Array($data));
// set page number from request
$currentPage = isset($_GET['p']) ? (int) htmlentities($_GET['p']) : 1;
$pager->setCurrentPageNumber($currentPage);
// set number of items per page from request
$itemsPerPage = isset($_GET['c']) ? (int) htmlentities($_GET['c']) : 10;
$pager->setItemCountPerPage($itemsPerPage);
// get pages
$pages = $pager->getPages();
// create page links
$pageLinks = array();
$separator = ' | ';
for ($x=1; $x<=$pages->pageCount; $x++) {
if ($x == $pages->current) {
$pageLinks[] = $x;
} else {
$q = http_build_query(array('p' => $x, 'c' => $itemsPerPage));
$pageLinks[] = "<a href=\"?$q\">$x</a>";
}
}
} catch (Zend_Rest_Client_Exception $e) {
echo "Client error: " . $e->getResponse();
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}
// iterate over current page set
echo '<h2>Popular Artists</h2>';
$ctr = $pages->firstItemNumber;
foreach ($pager->getCurrentItems() as $item) {
echo $ctr . '. ' . $item . '<br/>';
$ctr++;
}
// print page links
echo '<div>';
echo 'Pages:' . implode($pageLinks, $separator);
echo '</div>';
?>
|
清单 7 背后的基本逻辑很简单:对于每个请求,脚本检查缓存看 YQL 结果是否已经存在。如果是,缓存的副本被用于填充 Zend_Paginator 数据数组,分页照常继续。仅当无法找到缓存副本或缓存副本过期时才执行 YQL 查询,结果再次保存到缓存以备将来使用。
作为一种替代解决方案,您也可以考虑将结果数据存储在客户端变量中,并在用户在页面间移动时使用 JavaScript 动态填充页面容器。
图 6 展示了输出:
图 6. 经过分页的 YQL 结果
YQL 还提供大量额外实用工具表,可用于各种用途,比如同时运行多个查询或解析外部提要。为了说明,清单 8 使用特殊的 ‘yql.query.multi’ 表来同时运行三个查询:
清单 8. 执行一个 YQL 多查询
<?php
// set up Zend auto-loader
// load Zend REST client classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Rest_Client');
// execute multiple YQL queries
// combine results
try {
$client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
$q=<<<EOF
SELECT *
FROM yql.query.multi
WHERE queries="
SELECT * FROM geo.places WHERE text='london';
SELECT * FROM geo.places WHERE text='hong kong';
SELECT * FROM geo.places WHERE text='tanzania'
"
EOF;
$client->q($q);
$result = $client->get();
} catch (Zend_Rest_Client_Exception $e) {
echo "Client error: " . $e->getResponse();
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}
// iterate over query result set
foreach ($result->results->results as $result) {
foreach ($result->place as $place) {
echo '<div>';
echo '<strong>' . $place->name . ', ' .
$place->country . ' (' . $place->placeTypeName .
') </strong> <br/>';
echo 'Latitude:' . $place->centroid->latitude .
' Longitude:' . $place->centroid->longitude;
echo '</div>';
}
}
?>
|
清单 8 包含三个独立的 YQL 查询,每个查询都从 geo.places 表搜寻有关不同城市的信息。为了更高效,这三个查询被包装成针对 yql.query.multi 表的一个查询,每个查询的结果都被合并到一个结果文档中。以这种方式执行一个多查询具有一些好处:代码实现更高可读性,而且如果所有查询都针对一个表,查询速度会大大提高,因为 YQL 使用内部缓存来避免多次读取相同的表定义文件。
图 7 展示清单 8 的输出。
图 7. YQL 多查询的合并结果
另一套实用工具表简化对标准 XML 格式文档(比如 RSS 或 Atom 提要)中数据的提取和解析。例如,清单 9 展示如何使用 YQL 解析一个外部 RSS 提要并使用 rss 表从其中提取信息子集:
清单 9. 使用 YQL 解析 RSS 提要
<?php
// set up Zend auto-loader
// load Zend REST client classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Rest_Client');
// execute YQL query
// get 5 top stories from Google News RSS feed
try {
$client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
$client->q("SELECT title FROM rss (5)
WHERE url='http://news.google.com/news?ned=us&topic=h&output=rss'");
$result = $client->get();
} catch (Zend_Rest_Client_Exception $e) {
echo "Client error: " . $e->getResponse();
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}
// iterate over query result set
echo '<h2>Top Stories</h2>';
foreach ($result->item as $item) {
echo '<div>' . $item->title . '</div>';
}
?>
|
图 8 展示了输出。
图 8. Google 新闻故事列表,使用 YQL 从一个 RSS 提要中提取
同样地,您可以使用一个 xml 表向任何 XML 文档应用 YQL 过滤器,结合使用 XPath 检索特定的节点集。清单 10 展示该技术的一个示例。
清单 10. 使用 YQL 解析 XML 文档
<?php
// set up Zend auto-loader
// load Zend REST client classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Rest_Client');
// execute YQL query
// get 5 top stories from Google News RSS feed
try {
$client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
$client->q("SELECT title, link FROM xml (5)
WHERE url='http://news.google.com/news?ned=us&topic=h&output=rss'
AND itemPath='//item'");
$result = $client->get();
} catch (Zend_Rest_Client_Exception $e) {
echo "Client error: " . $e->getResponse();
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}
// iterate over query result set
echo '<h2>Top Stories</h2>';
foreach ($result->item as $i) {
echo '<div><a href="' . $i->link . '">' . $i->title .
'</a></div>';
}
?>
|
清单 10 使用一个 SELECT 查询来解析一个外部 XML 文档,并对结果应用一个 XPath 表达式来选取节点的特定子集。以通用的方式向 XML 数据应用类似 SQL 的语法和过滤器的这种能力,可以极大地减轻从完整的 XML 文档树提取信息的工作。
图 9 展示了输出。
图 9. Google 新闻故事列表,使用 YQL 从一个 XML 提要中提取
YQL 还可以从 HTML web 页面提取数据。YQL 包含一个 html 实用工具表,您可以将它作为一个过滤器来从一个 HTML 页面挖出特定的数据。XPath 提供从 HTML 文档树定位和检索单个节点或节点集所需的表达式语言。
为说明这是如何工作的,假设您希望获取印度各邦和中央直辖区的完整列表。该信息在印度官方政府网站中以格式化的 HTML 表予以提供(图 10)。查看 图 10 的放大图。
图 10. 包含印度各邦相关数据的一个 web 页面的 HTML 源
使用 YQL,仅需一个 SELECT 查询即可从源页面提取数据到 YQL 结果集中,如 清单 11 所示。
清单 11. 使用 YQL 解析 HTML 文档
<?php
// set up Zend auto-loader
// load Zend REST client classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Rest_Client');
// execute YQL query
// scrape GOI page to get list of Indian states
try {
$client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
$client->q("SELECT content FROM html
WHERE url='http://india.gov.in/knowindia/state_uts.php'
AND xpath='//ul[@class=\'sec1\']/li/a'");
$result = $client->get();
} catch (Zend_Rest_Client_Exception $e) {
echo "Client error: " . $e->getResponse();
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}
// iterate over query result set
echo '<h2>List of States in India</h2>';
echo '<ul>';
foreach ($result->a as $state) {
echo "<li>$state</li>";
}
echo '</ul>';
?>
|
图 11 展示了输出。
图 11. 印度各邦列表,使用 YQL 提取自一个 web 页面
显然,使用 YQL 可以做的很多,不仅仅是从一个或多个 web 服务提取数据。通过 YQL,可以使用标准的 INSERT、UPDATE 和 DELETE 查询添加和修改第三方 web 应用程序中的数据,这种能力便于创建从多个数据源读写数据的基于云计算的应用程序。YQL 还提供大量实用工具表,可以使用 YQL XPath 组合过滤器从 RSS、Atom、CSV 和 HTML 等流行格式的文档中提取信息。
YQL 目前正处于积极开发中,因此预计在将来会有更多益处。请随意对 YQL 进行试验,或甚至在您开始将 YQL 集成到 PHP 应用程序时将您自己的数据表添加到社区存储库。祝编程愉快!
学习
- 通过 YQL 和 PHP 构建 Web 应用程序,第 1 部分(Vikram Vaswani,developerWorks,2010 年 11 月):开始使用雅虎查询语言(Yahoo! Query Language,YQL)提供一个统一的、类似 SQL 的接口来检索来自多个 web 服务 APIs 的数据。通过一个 PHP 应用程序访问 YQL 服务,使用 SQL 类型的结构来过滤、排列和链接查询结果。
- YQL 指南:了解更多有关 YQL 的信息并通过类似 SQL 的命令访问 Internet 数据。
- YQL 控制台:体验 YQL 查询。
- YDN 论坛 > YQL:参与有关 YQL 开发的讨论。
- Zend_Rest_Client 库:在可搜索的参考指南中阅读有关 Zend_Rest_Client 库的更多信息。
- Zend_Cache 组件:深入了解一种用来缓存任何数据的一种通用方法。
- Zend_Paginator 组件:深入了解一种用于为大量数据分页并将该数据呈现给用户的灵活组件。
- YQL 社区表的完整清单:查看可用于访问数据的内置表清单。
- Wordpress API:了解有关 API 的更多信息,并 免费注册 Wordpress.com 博客。
- Payvment REST API:了解如何检索特定的购物车并在应用程序中显示其内容。请求 一个 API 键,然后开始行动。
- 堆栈溢出讨论:查明如何在嵌套或多重 YQL 查询中转义特殊字符。
- 该作者的更多作品(Vikram Vaswani,developerWorks,2007 年 8 月 — 现在):阅读有关 XML、其他 Google API 和其他技术的文章。
- developerWorks XML 专区:在 XML 专区获取提高您的专业技能所需的资源。
- My developerWorks 中文社区:个性化您的 developerWorks 体验。
- IBM XML 认证:了解如何才能成为一名 IBM 认证的 XML 和相关技术的开发人员。
- XML 技术库:访问 developerWorks XML 专区,获得广泛的技术文章和技巧、教程、标准和 IBM 红皮书。还可以阅读更多 XML 技巧 相关信息。
- developerWorks 技术事件 和 网络广播:随时关注这些活动中的技术。
- developerWorks 播客:收听面向软件开发人员的有趣访谈和讨论。
- developerWorks 按需演示:包括面向初学者的产品安装和设置演示,以及为经验丰富的开发人员提供的高级功能。
获得产品和技术
- Zend Framework:下载最新的 Zend Framework 产品。
- Yahoo! Social SDK:下载并使用这个灵活的试验性 SDK。
- Patched yql-tables / shoppingcart :下载作者的补丁版本的 YQL 表或 查看修补的表。
-
IBM 产品评估试用版软件:下载或 IBM SOA Sandbox for People,并开始使用来自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。
讨论
- XML 专区讨论论坛:阅读这些博客并参与讨论。
- developerWorks 博客::阅读这些博客并参与讨论。
