PHP4 和 PostgreSQL:使用开源软件构建严肃的 Web 应用程序

作者:Tim Perdue

不久之前,轻松构建一个严肃的 Web 应用程序意味着要为 Cold Fusion 许可证、Sybase 等专有数据库和 Sun 服务器支付巨额资金。幸运的是,那些日子早已过去。随着 Apache 的广泛普及和免费数据库的日益成熟,专有软件有了现实的,甚至可能更优越的替代方案。

开源领域中最优秀的是 PHP,一种可以比作 Perl 的脚本语言;以及 PostgreSQL,一个强大的对象关系数据库。当您将 PHP 和 PostgreSQL 结合在一起时,您可以构建从简单的留言簿到庞大的基于 Web 的会计应用程序的任何东西。PHP 提供大脑,而 Postgres 提供肌肉。

我将介绍一个非常基本的 PHP 购物车和库存应用程序,它利用了 Postgres 的事务能力。您可以在我的网站 PHPBuilder.com 上下载源代码并阅读其他教程。

我想介绍的第一件事是应用程序的结构。在我使用 PHP 构建的每个 Web 应用程序中,我首先设置一个全面的库,该库包含在整个站点的每个页面上。这个库将被称为 common.php 并存储在一个名为 include 的目录中。

我们的库将为我们处理例行任务,例如设置到数据库的连接、确定用户是谁、设置站点的页眉/页脚等。通过将这些功能放在一个地方,我们的应用程序将更加简洁且更易于维护。例如,与其在每个页面上硬编码数据库连接,不如在库中编码一次。

清单 1. 示例库代码

在那里,我们库的第一个版本已经很有用了。它将我们连接到数据库,并为我们提供了一个简单的 HTML 抽象。随着我们网站 HTML 变得更加复杂,我们可以将其大部分包含在 site_header/site_footer 中,并使更改应用于我们的整个应用程序。

该库还包括一个用于发出 Postgres 查询的简单抽象层。我这样做只是为了减少我必须编写的代码量。

最后,我们调用 PHP4 的内置会话管理代码。调用 session_start 将导致 PHP4 重新加载您注册的任何变量,以便它们可用于应用程序的其余部分。

我们网站上的每个页面从根本上都像这样

<?php<\n>
//include the common library
require ($DOCUMENT_ROOT.'/include/common.php');
echo site_header('Example Page');
/*
        page logic
*/
echo site_footer();
?>

一般来说,在构建应用程序时,明智的做法是将繁重的逻辑与实际呈现(在本例中为 HTML)尽可能地分开。我通常通过将逻辑包装在函数调用中来做到这一点,这些函数调用随后包含在整个站点的页面中并从中调用。原因是您最终可能想要为您的应用程序构建不同的界面——也许是用于无线应用程序的极其轻量级的界面。如果逻辑与 HTML 呈现交织在一起,您将不得不复制逻辑以创建另一个界面。如果将其分离到函数库中,则两个界面都可以重用相同的逻辑。

PHP 中函数调用的问题在于没有标准的异常处理过程。如果函数中存在内部错误,调用代码如何知道并将该信息传递给用户?在其他语言中,例如 Java,您会在函数内部抛出异常(Java 中的方法)。当您调用该方法时,您会将其包装在 try/catch 语句中并相应地处理问题。

我通常在 PHP 中通过始终从函数调用返回 true 或 false,并设置一个名为 $feedback 的全局变量来解决这个问题。然后可以测试结果,并且可以在需要时将 $feedback 打印到屏幕上。目前正在进行一项名为 PEAR (http://pear.php.net/) 的工作,该工作正尝试标准化错误处理和数据库访问等,但目前还没有具体的成果。

这是一个如何使用我的 true/false 方法调用函数的示例

<?php<\n>
$result=function_call_name();
if (!$result) {
        //there was an error-display it
        echo $feedback;
} else {
        //continue on with success
}
?>

现在让我们开始考虑我们的购物车。我们需要一些基本的数据结构来存储我们的购物车数据。首先,我们需要一个库存数据库,其中列出商品名称、零件号、价格和现有数量。我们还需要跟踪我们的客户以及客户正在购买的商品。这就是我们在这里要达到的复杂程度。

清单 2. 购物车数据结构

这应该足以让我们拥有一个基本的购物车。为了规范化我们的数据库模式,我创建了一个单独的表,其中列出了客户购物车的内容。这使客户可以在他们的购物车中拥有多个商品,同时允许我们轻松地将购物车内容与库存数据库连接起来。

现在我们需要考虑在线商店各种功能的操作。最基本的功能包括获取新购物车、向购物车添加商品,然后结账。一个真正的在线商店需要更多功能,例如浏览商品、调整数量等的能力,但我会将这些功能留给您。

我将从一个简单的函数调用开始,以创建一个新客户。真正需要做的就是从我们创建的客户序列中获取下一个值,将其插入到客户表中的新记录中,然后在 PHP4 的内置会话管理代码中注册该号码。

清单 3. 创建新客户

这比我希望的代码多了一点,但它向您展示了如何在 Postgres 中正确启动和终止事务,以及如何检查每个查询的错误。我将在我的所有代码中使用相同的例行错误检查,您也应该这样做。

始终计划好当查询失败时您将如何处理。您是会直接终止脚本、再次尝试查询,还是像什么都没发生一样继续进行?仔细考虑每个选项的后果。例如,如果您未能获得下一个 customer_id,您就无法真正继续创建新的客户记录。如果创建客户记录失败,您以后就无法更新她的地址信息或向她的购物车添加商品。逻辑,对吧?

现在让我们看看向购物车添加商品的过程。这也很容易。在将商品插入购物车之前,我们应该首先检查该商品是否存在于数据库中。这被认为是最佳实践,因为商品编号将来自 Web 浏览器,并且可能已被篡改。

一旦我们知道商品存在,我们就可以测试它是否已在购物车中。如果在购物车中,则增加数量,而不是插入另一行。如果它尚未在购物车中,则将其插入购物车,默认数量为一。

清单 4. 向购物车添加商品

现在我们可以创建新客户,他们可以将商品添加到他们的购物车中。现在我们只需要能够从商店结账并同时减少商店库存。这是整个商店最复杂的部分,并且很好地利用了 Postgres 的事务和高级锁定方案。

首先,我们将使用 Postgres 的 SELECT...FOR UPDATE 语法,该语法有效地锁定选定的行,以便您可以在事务中更新它们并提交您的更改。

通过在事务中使用此语法,您可以保证您的数据保持一致。对于某些数据库(如 MySQL),您无法轻松锁定特定的数据行,以防止其他进程在您也尝试这样做时减少库存。最终结果是数字不准确且库存计数无用。

此查询还将使用子查询,这是更强大的可用数据库上的另一个标准功能。子查询允许您基本上将两个查询组合成一个,以使您的生活更轻松。

在我们锁定行之后,我们需要发出一个查询来减少购物车中每个商品的库存计数。为了简单起见,我们不会警告库存不足,但如果我们的库存水平低于 0,我们将成功地将 item_count 设置为负数。然后,您可以设置一个管理页面来查看库存余额为负数的商品并订购新的库存。

最后,我们将希望使用访问者的信用卡、送货信息和总销售额更新客户表,然后从 PHP4 中销毁客户的会话。

清单 5. 结账和减少库存

现在这是一个相当复杂的事务,每个步骤都必须正确执行。如果不是,则必须回滚整个事务以使一切恢复到正确的顺序。

如果您在本示例中没有事务的优势,并且在更新其中一个库存商品的过程中查询失败,您将遇到很多麻烦。您将有部分库存已更新,部分库存未更新。如果访问者重试该页面,您将如何知道再次减少哪些库存商品?答案是,您不会知道,并且您最终会得到不准确的库存。

这篇文章绝不是一个全面的购物车解决方案——如果我有时间,我可能会写一整本书来介绍它。但是,它确实奠定了基础,并演示了我为每个 Web 开发人员推荐的设计和执行方法。有关更多信息和讨论,请访问 PHPBuilder.com。

我遗漏的一些部分包括查看购物车内容的页面以及查看商店中商品的功能。这些部分以及此处的所有代码都可以在 .zip 文件中找到,以及讨论/评论区;www.phpbuilder.com/columns/linuxjournal200009.php3

PHP4 and PostgreSQL: Building Serious Web Applications with Open-Source Software
Tim Perdue (tim@perdue.net) 居住在加利福尼亚州森尼维尔,是 SourceForge.net 的首席架构师,也是 PHPBuilder.com 和 Geocrawler.com 的创始人。他还喜欢在旧金山湾航行和研究星星。Tim 和他的妻子 Lisa 预计他们的第一个孩子将在 11 月出生。
加载 Disqus 评论