内容


在 PHP 内创建一个 Atom 提要

借助 PHP 和 MySQL 进行轻松聚合

Comments

何为 Atom?

Atom,正如这里所使用的这样,指的是一种 XML 语言,它能够让 Web 发布者聚合其 Web 站点的内容以提供给各种用户。使用 Atom,发布者就能够创建一种标准格式的 Web 提要。这种提要能够让用户通过一种称为提要阅读器 的软件阅读 Web 站点的内容。它还使其他的 Web 开发人员能够在其各自的 Web 站点上发布该提要的内容。

Atom 并非目前在用的惟一一种聚合标准。RSS 是早于 Atom 的另外一种标准格式(也使用 XML)。实际上,Atom 就是为了解决 RSS 中的某些限制而创建的。

结果,Atom 规范反倒包含了大量超出 RSS 的优势。Atom 提供了一种定义所提供数据的格式的方式 — 比如,HTML、XHTML 等 — 而 RSS 则没有。与 RSS 不同,Atom 通过 xml:lang attribute 能够支持全球化。而且,依据 Request for Comments (RFC) 3339,Atom 还接受更为先进(且标准)的数据格式化,这与 RSS 的 RFC 822 完全不同。

为何 PHP 要与 Atom 联合使用呢?

PHP 代表的是 PHP: Hypertext Processor(超文本处理器)。它可能是英语中惟一一个在展开后仍然包含原始缩写词的缩写词。PHP 最初的意义代表的是 Personal Home Page(个人主页)

PHP 是一种脚本语言,可生成动态的服务器端内容。它能与 HTML 协调工作,并且 PHP 代码也经常会嵌入在标准 HTML Web 页面内以帮助实现动态内容。

PHP 还能与数据库管理系统 MySQL 很好地协作。经过这些年的 Web 开发,这两种技术也在不断发展变化,并已经在无数场合并肩合作过。大部分归功于一个无法否认的基本原理:二者都是免费的。

本节开始部分的问题可以这样回答,PHP 为开发人员提供了以一种易读和易开发的形式生成动态内容的灵活性。动态内容从 MySQL 数据库检索得来。输出页面(提要)用 PHP 编写以便它能呈现符合 Atom 规范的 XML 输出。

请注意,本文假设您已经熟悉 MySQL 和 PHP 的基础知识。如果不是这样,可参见本文 参考资料 部分的介绍性教程。

定义业务用例:垂钓报告

您的老板现在在您的办公室。他非常欣赏公司网站(fishinhole.com)的运行方式。站点目前向专业的钓鱼者推广和销售各种钓具。该站点还提供了一个垂钓报告论坛,以供这些热衷于此的专业钓鱼者分享其垂钓经历。

您的老板在您的办公室拿了把椅子径自坐下,然后抱怨说公司网站的关注度还有待提高。他想利用页面的垂钓报告部分来将更多 的专业钓鱼者吸引到网站中来。他希望您将网站的这个部分做成世界范围的专业垂钓报告的 “一站式商店”。这对于 fishinhole.com 站点的成功非常关键(如他所说)。您的老板啜了一口咖啡,笑了笑,然后走出了您的办公室,没说什么其他的。

您向后依靠在椅子上,开始思考:有什么能让垂钓报告论坛获得更广泛的关注呢?过了一会,一个念头闪入您的脑海:聚合!与其简单地将垂钓报告部分供 fishinhole.com 的用户和顾客所用,还不如聚合此论坛以便人们可以用各自的提要阅读器阅读垂钓报告的梗概。其他的 Web 开发人员还可以在其 Web 页面上包含这个聚合提要。在任何情况下,人们都可单击感兴趣的报告标题,被链接回 fishinhole.com,在这个网站上再让他们接触到一连串的渔具的直接营销。这真是个好主意。

数据库设计

远在您的老板步入您的办公室之前,垂钓报告论坛的数据库就已设计好了。此 Web 页面的垂钓报告部分已经存在。只不过还未被聚合。

那么,需要对此数据库做些什么更改,才能聚合其内容呢?不需要!这就是聚合最好的一面。在很多情况下,我们都可以聚合文章,而又无需更改其底层的模式或数据模型。这是因为,在大多数情况下,我们都将聚合文章,而文章几乎总是会具有 Atom 规范所要求的信息。

清单 1 给出了 fishinhole.com 的垂钓报告部分目前所采用的数据库模型。它还包含了一些 INSERT 以便您能拥有测试数据。

清单 1. 具有 INSERTREPORTS 表结构
CREATE TABLE IF NOT EXISTS `reports` (
  `ID` bigint(20) NOT NULL auto_increment,
  `AUTHOR` varchar(32) NOT NULL,
  `TITLE` varchar(64) NOT NULL,
  `SUBTITLE` varchar(128) NOT NULL,
  `CONTENT` varchar(2000) NOT NULL,
  `POSTED` datetime NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;


INSERT INTO `reports` (`id`, `author`, `title`, `subtitle`, `content`, `posted`) VALUES
(1, 'BigRed', 'Spanish Bite Looking Good!', 'Near the Cape!', 
'Trolled for 3 hours and limited out on Spanish Macks!  Watch out for the shallows 
near green can #4.', '2009-05-03 04:54:33'),
(2, 'JonBoy', 'Big Rock Report', 'Spring has sprung', 
'Caught several blackfins and mahi just outside of the Big Rock on Saturday.  
We were using flourescent squid teasers with ballyhoo for hookups.  
One Mahi weighed over 50#!', '2009-05-03 04:56:06'),
(3, 'Erasmus', 'Drum in the backwaters', 'The bite was hot!', 
'Loaded up against the marsh grass, boys.  Go get em.  I was using gulp 
with 1/4 ounce jigheads.', '2009-05-03 04:57:19'),
(4, 'ReelHooked', 'Speckled Trout In Old River', 'Limited out by noon', 
'They were schooling heavy in Old River.  They would eat anything we would 
throw at them.  Most were undersized, but we managed to keep some 
and had our fill by midday.', '2009-05-03 04:59:00');

若您希望对本文中给出的这些代码进行实际的测试,可以先创建一个名为 fishinhole 的 MySQL 数据库并在该数据库内执行 清单 1 中的代码。

第一列(ID)是此表的主键。请注意,它使用了 auto_increment 规范以便每当一个新行被插入到此表中,ID 栏都会被前一行的 ID 列的增量填充。这一点非常类似于 Oracle 表中的序列。

AUTHOR 列指定发布此垂钓报告的人的用户名。这是用户的显示名,而非用户真实的名和姓(除非该用户的真实姓名就是其实际的显示名)。

TITLE 列是文章的标题。类似地,SUBTITLE 是文章的副题,并在该 Atom 提要内被用于文章的梗概。

CONTENT 列才是实际的垂钓报告本身。由于这里所生成的这个 Atom 提要只包括整篇文章的一个梗概(因而会吸引用户单击链接来访问网站),而内容本身并不显示在这个 Atom 提要内。

最后,POSTED 列是一个 DATETIME 列,存储了文章发布到网站的日期。

为了保持简单,我只提供了几篇文章(4)。在真实的情形下,有可能会有来自不同作者的数千篇这样的文章。

开始工作

数据库设计完毕后,就可以开始编写 PHP 页面,让它生成 Atom 提要。本文将会带您体验创建一个简单 Atom 提要的全过程,并使用 PHP 进行测试。

请注意,如果想要测试代码,您需要访问一个 PHP 处理器。很多托管解决方案都提供这样的访问。不过,您有可能已经能够从本地访问一个 PHP 处理器。若想知道如何在 Web 环境下执行 PHP 文档,可以咨询相关的系统管理员或技术人员。

在 PHP 内访问数据库

创建一个新的 PHP 文件,称为 syndication.php。代码将会放置于此。

正如之前所提到的,PHP 和 MySQL 具有丰富的友好合作经历。有人甚至戏称二者像是联姻了似的。本文无意对这类判断做任何评论。

清单 2 提供了一个基本的代码片段,借此,就能访问用 清单 1 中的代码所创建的这个 MySQL 数据库。

清单 2. 用 PHP 访问这个 MySQL 数据库
$link = mysql_connect('localhost', 'admin', 'password')
    or die('Could not connect: ' . mysql_error());

mysql_select_db('fishinhole') or die('Could not select database');

$query = 'SELECT id,title,subtitle,author,posted 
     FROM reports order by posted desc limit 25';
;
$result = mysql_query($query) 
     or die('Query failed: ' . mysql_error());

该代码实际上涵盖的内容颇丰,所以很有必要对其进行逐步剖析。

首先是 mysql_connect() 函数。您需要针对自己环境的指标相应更改这些参数。第一个参数是数据库主机。在某些情况下,它将非常类似于 清单 2(即 localhost)。在另外一些情况下,它将是一个远端主机(例如,IP 地址 10.92.2.1)。若具备 Domain Name System (DNS) 解析,它还可能是另外一个实际的主机名(比如 mysql.myhost.com)。

第二个参数是将要访问该数据库的 MySQL 用户名。您可以使用如示的 admin,但是必须要确保它是 MySQL 环境内的一个有效帐户。参照 MySQL 文档以了解如何为一个 MySQL 数据库创建帐户。请记住,这里所使用的帐户必须 具有对 REPORTS 数据库的读权限。

第三个参数是用户的密码。它需要与第二个参数内所指定用户的密码相匹配。

or die 子句被包括进来以便在系统故障时为开发人员提供诊断信息。如果无法连接到数据库管理系统,那么在执行 PHP 脚本时,您将会收到一个消息,指出连接失败及失败的原因。

接下来是 mysql_select_db() 函数。在其中选择计划要使用哪个数据库。您自己的 MySQL 可以(并且很有可能)包含很多数据库,所以非常有必要指定想要使用的是哪个数据库,这一点十分重要。

还记得么,我曾建议您如果想要测试本文中提供的代码的功能,需要创建一个名为 fishinhole 的 MySQL 数据库。mysql_select_db() 行就指定了将要使用的是这个数据库。

接下来是实际的查询。在本例中,这个查询在一个字符串中定义。在这里,抓取 REPORTS 表的 IDTITLESUBTITLEAUTHORPOSTED 列。order by posted desc 子句强迫此查询按 POSTED 列中的日期(即文章发布到 Web 站点的日期)的降序返回行。所以,首先检索到的将是最近发布的文章。这是提要的一种标准实践。

末尾处的 limit 25 子句十分重要。在这里,您将提要指定为最多返回 25 篇文章。还记得么,我之前提到过,这类论坛可能会有数千篇文章。但是在一个提要内返回数千篇文章不太实际。因为这样做会占用大量带宽,很多客户将会不得不等待一段时间。

这个查询是一个字符串。它被分配给一个变量,该变量名字很直观,为 $query

mysql_query() 函数内,实际执行在前一行所定义的查询。该查询的结果存储于 $result 变量内。并且,还包括了 or die 子句以备诊断之用。

循环及 Atom 规范

有了来自数据库的数据之后,就可以以符合 Atom 规范的格式显示它了。由于 Atom 是一种 XML 语言,所以 PHP 文件的输出也是 XML 格式的,而不是 HTML 格式的。如果想要利用 Web 浏览器显示输出,那么,请务必注意它会依据您所使用的浏览器和版本来进行不同的显示。要查看 XML 输出,通常一种最好的做法是右键单击浏览器内的输出并选择 View Source。之后,您将会看到原始的 XML 输出。

在显示有关每篇文章的信息之前,最好先将导言包括到这个 Atom 提要。在这个部分,输出被识别为 Atom 提要,而且还会提供有关此提要的信息,如 清单 3 所示。

清单 3. Atom 导言
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom"> 
     <title>Fishing Reports</title> 
     <subtitle>The latest reports from fishinhole.com</subtitle>
     <link href="http://www.fishinhole.com/reports/syndication.php" rel="self"/> 
     <updated><?php echo date3339(); ?></updated>
     <author> 
          <name>NameOfYourBoss</name>
          <email>nameofyourboss@fishinhole.com</email>
     </author>
     <id>
     tag:fishinhole.com,2008:http://www.fishinhole.com/reports/syndication.php
     </id>

您可能会立即注意到 清单 3 中的代码看上去并不像 PHP。这是因为其中的大多数代码都不是 PHP 代码。而只是标准化了的输出,动态内容方面无需太多 PHP。

<feed> 元素指定此 XML 文档为 Atom 提要。用来定义这些元素的名称空间被作为 <feed> 元素的属性提供。也可以使用之前提到的 xml:lang 属性来指定这是一个用英文写的文档。

<title> 元素为整个提要指定了一个标题。同样地,<subtitle> 元素为整个提要指定一个副标题。

<link> 元素指定了这个 syndication.php 文档的 URL。本例中的地址是假想的地址,不能用在现实世界中。实际上,您可以包括一个能够生成此提要输出的链接。

<updated> 元素生成一个时间戳(符合 RFC 3339 标准)并告知此提要的用户它最后一次被更新的时间。在本例中,提要总是检索来自数据库的最新数据,提要总是最新的,因而我们总是可以使用最新的时间戳。并且您可能已经注意到了,在这个元素内有一小段 PHP 代码。这是一个定制的 PHP 函数,可以生成 RFC 3339 格式的时间戳。

<author> 元素定义整个提要的作者。您将使用您老板的姓名作为作者,因为这是他的主意。

最后,<id> 元素以 Internationalized Resource Identifier (IRI) 格式惟一标识该提要。

清单 4 所示的循环用来生成这个 Atom 提要内的每个条目。生成此提要的大多数工作都在这里完成。

清单 4. 循环
<?php
     $i = 0;
     while($row = mysql_fetch_array($result))
       {
          if ($i > 0) {
               echo "</entry>";
           }

           $articleDate = $row['posted'];
           $articleDateRfc3339 = date3339(strtotime($articleDate));
           echo "<entry>";
           echo "<title>";
           echo $row['title'];
           echo "</title>";
           echo "<link type='text/html' 
                    href='http://www.fishinhole.com/reports/report.php?
                    id=".$row['id']."'/>";
           echo "<id>";
           echo "tag:fishinhole.com,2008:http:
                    //www.fishinhole.com/reports/report.php?id=".$row['id'];
           echo "</id>";
           echo "<updated>";
           echo $articleDateRfc3339;
           echo "</updated>";
           echo "<author>";
           echo "<name>";
           echo $row['author'];
           echo "</name>";
           echo "</author>"; 
           echo "<summary>";
           echo $row['subtitle'];
           echo "</summary>";

           $i++;
     }			
?>

同样的,清单 4 也涵盖了相当多的内容。首先是 while 循环。通常,这部分代码的意思是 “只要该表内还有未被包含在输出中的行,就一直进行”。每个迭代内的当前行存储于名为 $row 的一个 PHP 变量内。

之后,再检查计数器($i)。如果计数器大于 0,就意味着这至少 是第二次迭代。在这种情况下,有必要关闭之前迭代的 <entry> 元素。

接下来的两行代码检索文章日期(从 POSTED 列)并使用之前提到的函数将其转变为 RFC 3339 格式。

接下来,是 <entry> 元素。随之而来的是 <title> 元素,由当前行的 TITLE 列填充。

<link> 元素则与众不同,它不包含任何的子文本。相反,实际的链接作为属性引用。这也是 Atom 标准的一部分。链接只简单将用户导向此 URL,用户可在这里读取整篇文章。别忘了,此提要只为用户提供一个梗概。

<id> 元素类似于我们之前所讨论的那个 ID 元素。它以 IRI 格式惟一标识这个元素。而且,正如之前那样,它由相关的 URL 构造而来。

<updated> 元素包含 POSTED 列的 DATETIME 值(RFC 3339 格式)。还记得么,此文档的 $articleDateRfc3339 变量早先在这个迭代内被填充。

接下来是 <author> 元素。与其他元素不同(但与导言内的 <author> 元素类似),这个元素具有子元素。对于本文而言,只使用了这些子元素中的一个:作者的姓名。作者的姓名由当前行的 AUTHOR 列填充。

<summary> 元素包含由当前行的 SUBTITLE 列得到的信息。

最后,循环计数器($i)增加,循环继续。

简言之,以上就是与从 REPORTS 表生成一个 Atom 文档相关的全部代码。如您所见,它完全没有初看上去那么复杂。

并且,请注意 Atom 规范内的很多元素,这里并未涵盖。您可以按照我在这段代码中描述的相同的模式轻松添加这些元素。更多信息,请参见 参考资料

进行测试!

现在要做的是最为有趣的部分:测试!

无需重新键入(或是复制粘贴)在上述代码清单中所列的所有代码,您可以简单地使用本文 下载 部分所附的这个 PHP 文件。将该文件复制到一个本地目录并进行我之前所描述的必要的数据库更改(用户名、密码和主机)。然后将它复制到一个能访问数据库的 PHP 文件结构。

准备好 PHP 文件后,启动浏览器并像下面这样访问文件: http://your host/context/syndication.php。

对于任何定制解决方案,您都需要更改以斜体标出的那些值以便适应您特定的环境设置。

正如我之前所述,使用的浏览器和版本不同,结果也会不同。某些更为现代的浏览器能感觉到这是一个 Atom 提要并能相应显示结果。其他的浏览器则以原始的 XML 格式显示它。还有一些浏览器可能什么都不会显示,因为此文档不是一个标准的 HTML 文档。

如果浏览器不显示原始 XML,可以右键单击此文档并选择 View Source。这么做之后,应该会看到类似 清单 5 的结果。

清单 5. 输出(节选)
<?xml version='1.0' encoding='iso-8859-1' ?>
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom">
  <title>Fishing Reports</title>
  <subtitle>The latest reports from fishinhole.com</subtitle>
  <link href="http://www.fishinhole.com/reports" rel="self"/>
  <updated>2009-05-03T16:19:54-05:00</updated>
  <author>
   <name>NameOfYourBoss</name>
   <email>nameofyourboss@fishinhole.com</email>
  </author>
  <id>tag:fishinhole.com,2008:http://www.fishinhole.com/reports</id>
  <entry>
   <title>Speckled Trout In Old River</title>
   <link type='text/html' href='http://www.fishinhole.com/reports/report.php?id=4'/>
   <id>tag:fishinhole.com,2008:http://www.fishinhole.com/reports/report.php?id=4</id>
   <updated>2009-05-03T04:59:00-05:00</updated>
   <author>
    <name>ReelHooked</name>
   </author>
   <summary>Limited out by noon</summary>
  </entry>
...
</feed>

另一种测试方式是验证提要是否有效。在网上可以找到很多 Atom 提要验证器。其中一种很好使用的验证器可在 http://www.feedvalidator.org 找到。该 Web 站点可验证 Atom、RSS 和 Keyhole Markup Language (KML) 格式的提要。

业务结果

实现并部署了 Atom 提要之后,来自全世界的数千名新的专业钓鱼者现在都能接触到您网站上的垂钓报告了。现在您有了来自数百个专业钓鱼网站的链接,这些网站均嵌入了您的 Atom 提要。有些专业钓鱼者甚至每天都会使用提要阅读器来查阅报告。

您的老板看了最近的流量报告后急急地走进您的办公室。他对这些多出来的访问量非常满意并告诉您说访客已经增加了 10%。他对您表示赞许,并啜着咖啡满意地走开了。

结束语

Atom 规范是一种非常理想的、聚合 Web 内容的方式。通过联合使用 PHP 和 MySQL,您可以轻松生成一个符合 Atom 标准的 Web 提要,由于这种提要直接从数据库读取,因此它总是最新的。提要之后可由一个提要阅读器读取并嵌入到其他的 Web 站点内。其结果是您的 Web 内容所能触及到的范围将更为广泛,而这也就意味着网站将会有更多访客,并且很有可能您公司的盈利状况还会得到改善。


下载资源


相关主题

  • Atom (standard):查看 Wikipedia 上对 Atom 规范的解释。
  • RFC 4827:从这个 Atom 规范了解有关这种基于 XML 的文档格式的更多信息,这种文档格式描述了相关信息的列表,即提要。
  • RFC 3339:探究 Atom 所用的日期/时间规范。
  • PHP 教程:了解 PHP 的基础知识。
  • MySQL 教程:了解如何用 MySQL 创建并使用一个简单的数据库。
  • developerWorks 技术活动网络广播:随时关注技术的最新进展。
  • IBM 产品评估试用版软件:下载或 在线试用 IBM SOA Sandbox 并开始尝试使用来自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。

评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML, Open source
ArticleID=428882
ArticleTitle=在 PHP 内创建一个 Atom 提要
publish-date=09172009