燕之庐网站建设 - 优质网站设计公司

Posts Tagged ‘oracle’

网站建设

2010/02/04

Sun被收购Linux和Solaris将何去何从

Tags:

甲骨文收购SUN后,由SUN引领的UNIX技术会何去何从也 引发了业界的担忧。不过随着这项并购交易的获批,甲骨文高管也在上周三的新闻发布会上证实, 他们将努力保持原来的产品线,Solaris操作系统的用户也就不必为此太过担心了。

在 Solaris操作系统方面,甲骨文公司(纳斯达克代 码:ORCL)目前已经是Linux操 作系统的有力支持者。甲骨文拥有自己的企业版Linux产品(以红帽的Linux企业版为基 础)。对于甲骨文公司首 席执行官拉里。埃利森来说,Linux和Solaris的相互排斥显然是一个错误的选择。

埃利森在一次网上直播的问答环节对这项技 术发表 了自己的看法”我不认为这是一个必须二选其一的命题,因为UNIX操作系统在高端领域就做的很出色。我想Solaris作为单机版计算机操作系统运行的时 候会有所减少。Solaris操作系统将更多的用在计算机集群上”。

在SUN的时代,OpenSolaris的开放源代码可以用于独立的 计算机。作为未来技术的实验平台,OpenSolaris也代表了新一代的 Solaris系统。虽然埃利森没有在他的讲话中具体提到 OpenSolaris,但他向大家介绍了Linux的未来规划。

埃利森 表示”我喜欢Linux系统,甲骨文一直是Linux的大力拥趸者,而Solaris又是一款历史更长,功 能更强的操作系统”。

虽然埃里森预计Solaris会主要用于高端市场,不过按照它的发展规划也 会向桌面系统延伸。同时埃里森也强调,高端市场是Solaris系统的主攻领域,可能是云或者x86或者Sun SPARC服务器。

埃利森表示”我们认为Linux还需要很长一段时间才能迎头赶上。但是我们仍然热爱Linux-我是一个Linux的爱好者,如果你需要Linux系统, 我们 将奉献给你世界上最好的Linux操作系统。如果你想要UNIX,我们也拥有世界上最好的UNIX系统。再者这是两款不同的系统,我认为定位高端市场根本 不存在任何问题”。

埃利森对Linux与Unix的关系的评价与竞争对手-Unix厂商惠普公司(纽约证券交易所代码:HPQ)的 看法类 似。除了HP – UX,惠普还同时向用户提供Linux和Unix系统,他们将Unix系统专门定位为高端关键任务部署。

“我们从事开源业务已经很长一段时 间了。我们一直是Apache的分销商,也拥有自己的 Linux版本”埃利森表示”同时拥有Linux和Solaris对于我们来说 没有问题,我们希望能让两款系统都发展的更好”。

网站建设

php

2009/11/14

Andi Gutmans看 PHP 5、Oracle 的未来

Tags: ,

PHP 5 的发布经理对 PHP 5 一些新特性的概述及对其未来(对 Oracle 用户而言)的评述。

PHP 5(PHP:超文本预处理语言版本 5) 于 2004 年 7 月 13 日正式发布。毫不奇怪,由于 PHP 在 Web 应用程序市场中的领先地位,因此该版本获得了媒体的广泛报道。.NET 和 J2EE 等技术确实在曝光率和宣传报道方面超过 PHP,但易用性、高性能、与 Apache Web 服务器的紧密集成以及大量应用程序构建块使 PHP 成为领先的 Web 应用程序开发语言之一。

您可能会问自己,既然提供了 Zend Engine 功能的 PHP 4 已经如此成功,为什么还需要 PHP 5 和 Zend Engine II 呢?事实是,PHP 4 在某些方面并不擅长。这些方面中的大多数对于大型项目和公司(项目管理的结构化程度更高,且系统之间必须具备协同工作的能力)而言更为重要。PHP 5 解决了这些问题,使 PHP 不但对此类项目更具吸引力,而且还仍是 Web 应用程序开发的领先技术。

在本文中,我将介绍:

  • PHP 5 的幕后知识
  • 它的某些新特性的简短概述
  • 简要展望 PHP 和 Oracle 用户的未来。

Zend Engine II 新的面向对象的模型

背景随 着 PHP 使用范围的稳步增长,它在较大项目中的使用率也在不断上升。大型项目好像都使用面向对象 (OO) 的方法。并不是说您不能编写小型 OO 应用程序,而且即便不使用面向对象的编程 (OOP) 方法也完全可以编写令人印象深刻的大型应用程序。但人们往往在这些情况下选择 OO 范例 — 可能是因为 OOP 为功能和技术设计提供更多惯用的工具(UML — 统一建模语言)、为重复出现的问题提供重用解决方案(设计模式)以及 OO 语言本身的内置机制(帮助强化软件设计和合同)。

PHP 先前版本中的对象模型存在的主要问题是,将对象实现为具有与整数和字符串相似的副本语意的自带类型。这不但会因为 PHP 有时进行的意外隐式对象克隆而导致的某种非常令人混淆的行为,而且使我们无法实现某些基本功能,如取消对方法返回对象的引用的功能。

以下示例演示了这两个问题。
a) 隐式对象克隆:

<?php

class Person {
var $name;

function Person($name) {
$this->name = $name;
}

function setName($name) {
$this->name = $name;
}

function getName() {
return $this->name;
}
}

function lowerCaseName($obj)
{
$new_name = strtolower($obj->getName());
$obj->setName($new_name);
}

$obj = new Person("Andi");
lowerCaseName($obj);
print $obj->getName();

?>

大多数开发人员认为此示例会打印出“andi”。但令人吃惊的是,此示例在 PHP 4 中却打印出“Andi”。这是因为正如前面提到的,PHP 4 将对象作为常规自带类型处理,所以将$obj按值传递给lowerCaseName()实际是克隆该对象。lowerCaseName()$obj执行的最终操作在该对象的克隆版本上进行。此行为不但导致令人吃惊的结果,而且对于认识到此问题的开发人员而言,它将需要按引用传递和返回对象,由于开发人员必须在许多位置插入“&”(按引用传递、按引用返回和按引用赋值),因此将使代码的维护更加困难。

b) 无法取消对方法返回对象的引用:

$obj->getParentObject()->method();

如果您不熟悉 PHP 4,则可能认为此示例可以正常运行。但由于前面提到的隐式克隆问题,因此不具有取消对方法返回对象的引用的功能,从而无法实现这一功能。作为变通方法,很多 PHP 4 代码将如下所示:

$temp_obj &= $obj->getParentObject();
$temp_obj->method();

还有更多示例可以说明对象的基本结构如何在 PHP 4 变得存在缺陷,但这两个示例足以让您认识到这一点。

主要的新语言特性。PHP 5 中最基本、最重要的变化是使用了对象句柄(或 Id),而不是将其实现为自带数据类型。复制时实际上只复制句柄(Id 编号)本身;并不复制这些句柄所代表的对象。该语言在语义方面看似较小的这个变化却是催生 PHP 5 大多数新特性的主要动力。它允许添加新的语言特性和新的 PHP 扩展,如完全利用新语义的很棒的 SimpleXML。

下面列出了 PHP 5 中的新语言特性,但本文不对其进行详细介绍(否则本文就成一本书了)。

新的对象克隆语义正如所指出的,无论是对象赋值、按值传递对象,还是从函数中按值返回对象,脚本引擎均不会自动克隆 PHP 5 中的对象。如果需要克隆,则开发人员可以通过使用新的clone关键字(例如,clone $obj;)显式克隆对象。开发人员还可以在类中实现一个名为__clone()的 方法,当克隆操作复制了所有原始对象的属性后,将在得到的新对象中调用此方法。实现此回调不是必需的,但如果开发人员希望每个对象自身拥有特定资源的一个 副本(以便为客隆对象创建该资源的一个新版本,否则,这两个对象将使用相同的资源),则实现此回调很有用。此类资源的一个实例就是文件。

公共/私有/受保护的访问修饰符。PHP 5 支持其他面向对象的语言(如 C++ 和 Java)中通常都具有的 PPP(公共/私有/受保护的)访问修饰符。可以将这些访问修饰符用于属性和方法,以施加访问限制。

接口、抽象类和方法。我 们在 Zend Technologies 收到许多要求在 PHP 5 中提供多重继承 (MI) 的请求,因此我们决定在 PHP 5 中解决此问题。在比较了许多语言(主要是 C++ 和 Java)的实现,了解了哪种语言最容易适应 PHP 的动态本质后,我们决定使用 Java 样式的接口和抽象类为 MI 提供一个解决方案。

允许 PHP 扩展重载 PHP 对象语法。PHP 5 最重要的特性之一可能就是 Zend Engine II 在对象语法和其语义之间存在一个抽象层。此方法允许 PHP 扩展创建它们自己的对象,这些对象的行为与用户级 PHP 对象的行为不同。例如,新的 COM 扩展使用这些重载功能,以便使用常规 PHP 对象语法以一种对 PHP 开发人员而言比较自然的方式访问 COM 对象:

$ie = new COM("InternetExplorer.Application");
$ie->Visible = true;
$ie->Navigate("http://www.php.net/");

其他利用此功能的扩展包括 SimpleXML、SOAP 和 Perl 扩展。

其他新特性PHP 5 中大概有 12 个以上的新语言特性,如类常量、静态属性和方法、__autoload()以及instanceof运算符。您可以在http://www.zend.com/php5中找到一个更完整的列表。

设计模式正如前面所讨论的,能够在大型(通常也可以是小型)PHP 软件项目中使用设计模式非常重要。虽然可以在 PHP 4 中利用此类模式,但由于缺少重要的语言特性(如静态属性和方法、PPP 访问修饰符和接口,因此通常很难推行这些模式的所有语义。

单实例模式经常使用并且非常出色的是单实例模式。尽管它是一个比较简单的模式,但要完整地实现它,必须使用静态属性和方法以及 PPP 访问修饰符。

例如:

<?php

class MySingleton {
static private $instance = NULL;

private function __construct() {
}

private function __clone() {
}

static public function Instance() {
if (self::$instance == NULL) {
self::$instance = new MySingleton();
}
return self::$instance;
}
// ... Additional code for the MySingleton class.
}

此实现利用了 PHP 5 新特性,从而获得了一个比较整洁并且不易出错的单实例实现。例如,将构造函数和客隆方法声明为private的功能可以防止开发人员不小心多实例化一个MySingleton类的副本,这是因为只有该类自己才可以访问这些方法。使用静态属性支持是为了实现一个可以全局访问的属性 (self::$instance),该属性引用类的单一实例。将属性声明为private可以确保只有该类自己才可以使用该属性。

不可变对象模式另一个不太常用的设计模式是不可变对象模式。此模式通常用于对相对较少的值进行大量引用的应用程序。它允许通过使对象不可变(禁止其状态变化)并强制实现此目的代码创建类的一个新实例来使应用程序代码共享对象。

以下示例演示了如何创建一个表示 SQL 查询的类。该组合的查询语句本身可能用在应用程序的多个位置。要更改该查询的值的代码可以使用changeStmt()方法实现此目的,该方法返回代表指定查询字符串的新对象的句柄。

<?php

final class ImmutableQueryStatement {
private $stmt;

public function __construct($stmt) {
$this->stmt = $stmt;
}

public function getStmt() {
return $this->stmt;
}

public function changeStmt($stmt) {
return new ImmutableQueryStatement($stmt);
}
}

此示例利用了几个 PHP 5 新语言特性。首先,它使用final关键字,以使该类不会再有“子类”。该方法使开发人员不能再继承和实现可变类。此外,changeStmt()利用新对象句柄并按值返回新建的对象(在 PHP 4 中,必须按引用返回新对象实例,从而使实现复杂化)。最后(但不是最次要的),与上一个示例相似,访问修饰符用于指定该类应遵守的访问合同。

PHP 5 中的 XML 和 Web 服务

背景在过去的几年里,XML 变得越来越重要,它允许不同的应用程序和系统使用标准工具和方法协同处理数据。这是 Oracle 及其他供应商坚定不移地支持和采用该技术的原因之一。在每个公司中,数据都是组织的核心部分。

PHP 4 中的 XML 支持非常混乱。尽管它支持 SAX(用于 XML 的简单 API)、DOM(文档对象模型)和 XSL(可扩展样式表语言),但却没有统一、符合标准的实现。SAX 实现基于逐渐过时的 Expat XML 分析器,DOM 扩展的命名惯例不符合标准,并且该扩展仍处于试验状态。此外,对 XSL 的支持还要使用称为 Sablotron 的 XML 库。

因此决定在 PHP 5 中重新编写 XML 支持。来自 PHP 社区的几位开发人员将此工作变为了现实。第一个也是最重要的决定是所有 XML 功能将基于Gnome 项目优秀的 libxml2 库。依照此原则,重新编写了现有的三个扩展。最重要的是,修改了 DOM 扩展并将其接口重新设计为符合 W3C。当 PHP 5 发布时,DOM 已脱离了试验阶段,变得功能完备且稳定。

SimpleXML除 了对现有的 XML 扩展进行重要的重新编写和统一以外,还出现了一个新的 XML 扩展。此扩展名为 SimpleXML,它允许开发人员能像访问自带 PHP 对象那样访问 XML 文件。回顾本文的前几个部分,这之所以成为可能是因为新的 Zend Engine II 使扩展能够重载面向对象的语法。

考虑以下 XML 文件:

<clients>
<client>
<name>John Doe</name>
<account_number>87234838</account_number>
</client>
<client>
<name>Janet Smith</name>
<account_number>72384329</account_number>
</client>
</clients>

以下 PHP 5 代码迭代 XML 文件,打印客户的名称和帐号:

<?php

$clients = simplexml_load_file('clients.xml');

foreach($clients->client as $client) {
print "$client->name account:$client->account_number\n";
}

运行此示例脚本将获得以下输出:

John Doe account: 87234838
Janet Smith account: 72384329

有 了 SimpleXML,访问 XML 文件变得非常容易。我相信,SimpleXML 将使 PHP 开发人员在处理 XML 文件时获得革命性的易用性。如果 SimpleXML 无法执行某些操作,则由于 SimpleXML 和 DOM 扩展都使用同一基础库,因此可以将 SimpleXML 对象转换为 DOM 树,在 DOM 中可以执行更高级的 XML 操作。SimpleXML 与 DOM 之间的这种相互转换是零复制的,也就是它既不花费时间也不占用额外的内存。

SOAP回顾前面的介绍,我曾将互操作性看作是大公司的主要问题。使用 Web 服务(更具体地说是 SOAP 协议)解决两个或更多系统之间的互操作性问题已变得越来越普遍。

由于 PHP 4 的默认发行套件未提供自带集成的 SOAP 支持,因此我们认为在 PHP 5 中必须解决此问题。因此,我们为 SOAP(客户端以及服务器 API)创建了一个新的自带实现,它允许 PHP 开发人员轻松创建和使用 Web 服务。

以下示例演示了从 PHP 中调用 SOAP 服务是何等简单。您可能注意到,此扩展使用了与前面相同的面向对象的重载功能。

<?php

$client =
new SoapClient("http://services.xmethods.net/soap/urn:xmethods-delayed-quotes.wsdl");

print($client->getQuote("ORCL"));

编写本文档时,运行此示例将打印11.23

未来和 Oracle

总论2004 年 7 月发布的 PHP 5 诞生时间很短。尽管如此,在 PHP 发展过程中却发生了很多令人关注的事情。在改进脚本引擎的性能方面已经做了大量的工作,而对 Oracle 读者而言,最重要的是有很多与数据库相关的新革新措施。在 PHP 社区中,Oracle 的使用范围很广,很多 Zend 客户都是 Oracle 用户。他们使用 Oracle 的方式虽然与其他数据库有所不同,但通常都是一个非常明智的选择,这与 Oracle 的公认跟踪记录、高级特性,以及(往往)Oracle 基础架构中的现有投资相关。

脚本引擎性能开发 PHP 5 时,Zend 和社区更专注于功能而不是性能。因此,除了几个个别情况以外,PHP 4 与 PHP 5 的脚本引擎性能差别并不大。

在大多数 PHP 应用程序中,PHP 的原始执行性能不是主要瓶颈。最常见的瓶颈与 I/O 相关,并通常与数据库相关。尽管如此,我们仍相信改进脚本引擎本身的性能将肯定为 PHP 用户带来好处。因此,我们决定投入大量资源来改进 PHP 5.1.x 的性能。

自 从 PHP 5 发布以来,我们投入了大量资源来调整脚本引擎。许多构思都出自由 Thies Arntzen 和 Sterling Hughes 在大约一年前发布的性能补丁。其他构思出自 Zend 和 PHP 开发人员社区的内部。最终我们设计出一种引擎,它在综合基准测试(不包括 I/O 和实际代码的基准测试)中的速度通常为 PHP 4.0 和 PHP 5.0 的两倍以上。

改进非常引人注目,并且当 PHP 5.1.0 发布时,所有 PHP 用户都可以使用它,而不必对他们的源代码进行任何更改。我认为,PHP 5.1.0 将在 2005 年第一个季度之初发布,但对于开放源项目来说,谁也不敢保证。

SQL Relay。SQL Relay(是一个非常有趣的第三方项目。它是一个为 SQL 连接(包括 Oracle)实现代理中介的项目,允许使用 PHP 实现数据库连接池。

该项目提供了它自己的 PHP 数据库扩展(您必须更改您的 PHP 数据库代码)。此扩展与 SQL Relay 中介通信,后者在扩展与数据库之间传递查询和结果。

SQL Relay 的某些优点:

  • 使用连接池,您可以限制打开的数据库连接数。
  • 在 PHP 持久连接无法用在您环境的情形,此解决方案可以解决初始化 Oracle 数据库连接时连接时间较长这一问题。
  • 该项目还支持其他编程语言。如果您使用的是一个混合环境,则可以利用从 PHP 中使用的同一个 SQL Relay 后台程序。除 PHP 以外,还支持 C、Java、Perl 以及许多其他语言。
  • 启动和运行 SQL Relay 非常简单。我还要指出,SQL Relay 作者对我的问题的反应非常积极。

SQL Relay 的某些缺点:

  • 必须使用一个不同于 PHP Oracle 扩展的 API。
  • 结果集被复制两次:首先复制到 SQL Relay 中介,然后复制到 PHP。
  • 没有哪个 API 像 PHP 自带的 oci8 扩展一样功能丰富。

我认为,如果您的项目确实需要 Oracle 数据库连接池,则您最好试一试 SQL Relay。它可能并不尽善尽美,但在一个更好的解决方案推出之前可能需要一段时间,且此解决方案确实有效。

PDO除了现有的 oci8 PHP 扩展(具有一个自带的 Oracle 数据库接口)以外,PHP 社区还一直致力于开发一个新的数据库抽象层。由于 Oracle 技术网已经提供了一篇深入介绍PHP 数据对象 (PDO)的 文章(作者 Wez Furlong),因此我们完全可以认为 PDO 值得期待。PHP 在很长时间以来一直期待一个好的自带数据库抽象。我认为,PDO 就是我们一直期待的解决方案。PDO 的设计者是 PHP 社区的一些高水平的开发人员,我很喜欢他们开发 PDO 的方法。以下是他们的设计目标清单(已写入 PDO README 文件中:

  1. 是轻型的。
  2. 为常见的数据库操作提供通用 API。
  3. 性能很高。
  4. 将大多数 PHP 特定代码置于 PDO 内核(如持续性资源管理)中;驱动程序只应关注如何获取数据而不是 PHP 的内部结构。

一方面,它为使用数据库提供通用 API。但它还允许每个驱动程序添加自己的附加功能,因此 PDO 不但支持最常见的数据库 API,而且还使您能够使用数据库提供的所有特性。我们都知道 Oracle 提供了很多特性。

PropelPropel是 一个对象持久性和查询框架。它采用对象/关系映射 (ORM) 模型并基于 Apache Torque 项目,该项目对于 Java 执行相同操作。与 PDO 不同,Propel 是一个非常高级的数据库抽象层,它重新定义了您查询、创建和操作持久性对象的方式。正如对 OO/RDBMS 映射系统所期望的,Propel 还处理数据库模式创建。

此类系统有很多优点。对于新手,开发人员可以集中大部分时间编写业务逻辑,并且不必处理数据库的复杂性 — 无论是模式管理还是编写精致的 SQL 语句。数据库操作非常自然,这是因为开发人员只使用常规对象,而持久层处理更新数据库中相应字段和行的低级细节。

缺 点是您失去了某些控制。OO 模型到关系数据库的自动映射并非总是很顺利。它不但使得手动编写精致、强大的查询很困难,而且也不允许您这样做 — 您破坏了抽象,并且微小的映射更新就可能破坏应用程序。因此,使用这样的系统意味着您必须遵守工具的规则。在大多数情况下,这样的代价是可以接受的,这是 因为提高的生产率有助于缩短开发时间并提高代码质量。但在某些情况下,您可能绝对需要这样的控制。

Propel 是一个非常有趣的项目,一定会派上用场。此外,它是基于名为Creole的数据库抽象层构建的。与 PDO 不同,此抽象层尝试尽可能地模仿 JDBC,并且在您将现有 Java 代码转换为 PHP 的情况下更易于使用。也就是说,如果 PDO 成为主流并作为标准 PHP 的一部分发行,那么最好遵守它。

Java 集成一年前,Zend 和 Sun Microsystems 启动了Java 规范请求 (JSR) 223, 用以定义 PHP 和 Java 的接口标准。如今,JSR 的专家组由许多软件供应商(包括 Oracle)组成。尽管 JSR 提到了所有脚本语言,但最初的兴趣却在于 PHP 以及主要是看能否从 PHP 中调用 Java 代码。您可以猜测此类连接的主要动机之一是将前端 PHP 服务器连接到后端 J2EE 应用服务器,更具体地说,是能够从 PHP 代码直接调用 Enterprise Java Bean (EJB)。

以下是使用 Java 接口实现的 Oracle JDBC 查询示例:

图 1:连接 PHP 和 Java:Oracle JDBC 查询

您可以看到您将可以在 PHP 中编写 Java 代码。这会使您能够调用您可能拥有的任何 Java 业务逻辑(特别是 EJB)。

此连接支持为已经投资了后端业务逻辑但希望利用 PHP 的快速开发时间和特性的 Oracle 应用服务器用户提供了新的可能性。

结论

PHP 5 无疑是 PHP 和 PHP 社区的重大进步。在 O’Reilly 开放源代码会议上,一位记者询问某些 PHP 社区领导者:PHP 5 是否是我们翘首期望的一切?回答是一致的;PHP 5 远远超过了我们最初的计划和期望。

更具体地说,我认为 Oracle 用户有很多值得期望的事情。就 Oracle发布的有 关在未来的 Oracle 应用服务器版本中包含 PHP 的发展方向声明而言,显而易见,公司已经认识到了 PHP 技术的重要性。我相信,在认识到 PHP 技术的重要性之后将出现各种提高 Oracle/PHP 生产率和灵活性(两者在如今不断变化的市场上的需求程度很高)的解决方案。即将推出的 Oracle 10g版本中的初始 PHP 绑定以及用于 Oracle JDeveloper 的 PHP 扩展是 Oracle 支持广泛流行的 PHP 的重要先期步骤。


Andi Gutmans是 Zend Technologies 的创办人和副总裁。1997 年之后,他一直致力于开发 PHP,并与 Zeev Suraski 携手,先后创建了 PHP 3 和 PHP 4。最近,Andi 为即将推出的 PHP 5 版本领导了 Zend Engine II 的面向对象的改进,并参与编写了PHP 5 强大编程(Prentice Hall) 一书。欢迎您将有关 PHP 5 的建议发送到andi@zend.com

php

PHP 5 数据对象 (PDO) 抽象层与 Oracle

Tags: , ,

一名新 PHP 数据对象 (PDO) 数据抽象层的原始开发人员为您简要介绍该抽象层,重点讲述与 Oracle 一起运行的情况。

需要 PHP:5.0
需要其他:Oracle 8 或更高版本客户端库
下载用于 Oracle 的 PDO (Windows):php_pdo.dll,php_pdo_oci.dll
下载用于 Oracle 的 PDO (Unix):pdo,pdo_oci
下载示例代码

PDO 简介

PHP 主要是由志愿者完成的项目;尽管有少数一些固定的“核心”开发人员,但是我们没有一个人在全职受薪的开发 PHP。除此之外,我们分别位于世界不同地方,您可以想象长期开发的协调工作是何等困难。因此,PHP 主要是基于突发奇想的个人短期需求来发展的,其原因也多种多样,有的是试验,有的则是因为“明天有活要交”。尽管这样通常每一步都会改善 PHP,但从长远来看则是缺乏完整性 - 数据库扩展就是一个重要的例子。

在各种不同的数据扩展(oci、mysql、 postgresql、mssql 等)之间根本没有真正的一致性,甚至在某些情况下,在这些扩展内部也没有真正的一致性。几乎所有这些扩展都在使用与基础数据库 API 紧密相连的不同代码完成着相同种类的任务。而且因为我们(PHP 核心开发人员和扩展开发人员)的人手非常有限,因此这就造成了代码更加难以维护,从而为 PHP 带来了很大的问题。

由于 PHP 越来越受欢迎并不断成功,因此主要 PHP 数据库扩展的维护者们参加了在德国举行的 LinuxTag 2003 大会,在会上我们交换了对 PHP 前景的看法。在讨论 PHP 发展的随机性时,我们确定了在 PHP 中进行数据库访问的一些目标:

  • 提供一种轻型、清晰、方便的 API
  • 统一各种不同 RDBMS 库的共有特性,但不排除更高级的特性。
  • 通过 PHP 脚本提供可选的较大程度的抽象/兼容性。

我们之所以提出了这种 PHP 数据对象 (PDO) 的概念,是因为我们希望通过采用 Zend Engine 2(PHP 5 的核心)先进的面向对象特性获得该 API 的一些更优秀的性能。

PHP 中的数据抽象层概念一点都算不上新;在 Google 中查询“PHP database abstraction”会找到大约 83,200 个匹配项。它几乎是许多 PHP 开发人员梦寐以求的,而其产生则部分归因于我们不完整的 API。如果您曾经尝试过使用第三方抽象层来完成任何真正重要的工作,通常会发现这些抽象层对于手头的工作来说设计的功能过于强大了 - 或者表现为在使用前需要进行大量学习,或者表现为接口速度缓慢,参数需要经过多层脚本函数调用才能到达数据库自有的 API;通常是存在上述两种表象。

为什么这些抽象层会存在这种问题?这些抽象层总是在试图完成太多的任务,甚至可能是不可能的任务。我们决定以实用为目标,仅将一些最常见的数据库 API 特性作为我们的基础,并使得 PDO 驱动程序能够将它们特定于产品的特性暴露为常规扩展函数。

为什么使用 PDO?

听 过有关数据库抽象扩展谣传的大多数人会立刻对 PDO 的扩展方面产生疑惑 - 我们是否要分析 SQL,将其转换为相应的后端方言呢?我们如何处理特性 X 或特性 Y,等等。因此,当您听说我们在 PDO 中根本不用为此而担忧时可能会大吃一惊;我们不希望使所有内容都完全统一,因为要使得这种统一成为可能,只能是将自己限制在最低的通用标准。

如果 PDO 不是一个整体的抽象层,那还有什么别的原因值得您考虑使用它吗?

  • 性能。PDO 从一开始就吸取了现有数据库扩展成功和失败的经验教训。因为 PDO 的代码是全新的,所以我们有机会重新开始设计性能,以利用 PHP 5 的最新特性。
  • 能力。PDO 旨在将常见的数据库功能作为基础提供,同时提供对于 RDBMS 独特功能的方便访问。
  • 简单。PDO 旨在使您能够轻松使用数据库。API 不会强行介入您的代码,同时会清楚地表明每个函数调用的过程。
  • 运 行时可扩展。PDO 扩展是模块化的,使您能够在运行时为您的数据库后端加载驱动程序,而不必重新编译或重新安装整个 PHP 程序。例如,PDO_OCI 扩展会替代 PDO 扩展实现 Oracle 数据库 API。还有一些用于 MySQL、PostgreSQL、ODBC 和 Firebird 的驱动程序,更多的驱动程序尚在开发。

您可能想了解 PDO 与其他常用的抽象层的对比情况,例如PEAR DBADODB。无论在 API 方面还是在性能方面,PDO 都比其他常见抽象层要轻型,但是涉及到在各个数据库后端之间提供统一性方面,则不如那些抽象层,例如用于处理大量可移植性问题的PEAR MDB 2抽象层。

在哪里可以获得 PDO?

PDO 是通过 PECL(发音为“pee-kle”,欧洲语言风格),即 PHP 扩展库提供的。如果您在运行 Linux 计算机,请按照下面的说明进行设置;稍后是在 Windows 上安装的详细信息。

请注意,PDO 及其驱动程序当前处于“alpha”状态;这就意味着我们会合理保证没有重大缺陷,但是该程序包功能并不完善 - 我们还要添加很多功能。虽然我们鼓励您测试该程序包,但是实在不推荐在现阶段将其用于生产。

Unix/Linux 安装

如果您以前尚未尝试过 PHP 5,则请花一点时间来通读一下“新闻”和各种声明。在 UNIX 计算机上,您可能要安装或升级 libxml2;如果没有 libxml2,“pear”程序包管理工具就无法运行,您安装 PDO 时就会遇到很多困难。获取PHP 5,并将其编译和安装。确保指定的前缀不是 /usr/local/,这样它就不会与 PHP 4 安装发生冲突了:

% ./configure –prefix=/usr/local/php5 –with-zlib [此处指定其他选项]
% make install

现 在您就可以使用“pear”工具获取并安装 PDO 以及用于 PDO 的 Oracle 驱动程序了。因为 PDO 当前标记为 alpha,所以默认情况下 pear 工具不会下载该程序包。在该程序包名称后面添加后缀“-alpha”,通知该 pear 工具可以安装 alpha 版本:

% PATH=”/usr/local/php5/bin:$PATH”
% pear install PDO-alpha

您需要告知 PHP 从专用于 PHP 5 的 php.ini 文件加载 PDO 驱动程序。如果您使用的前缀与我使用的一样,PHP 则会在 /usr/local/php5/lib/php.ini 中查找 php.ini 文件。向该文件中添加以下行:

extension=pdo.so

现在您需要获取数据库特定的驱动程序;对于 Oracle,此特定程序称为 PDO_OCI。在 shell 中,键入:

% pear install PDO_OCI-alpha

此驱动程序也需要从 php.ini 文件加载;将下行添加到前面添加的那行之后

extension=pdo_oci.so

现在检查一下,确保它能够运行:

% php -m

在模块列表中,您应该会看到 PDO 和 PDO_OCI。

防火墙碍事了?

如果您位于防火墙的后面,则在使用 pear 安装程序获取程序包时可能会遇到一些问题。如果发生这种情况,则可以按照下列说明手动下载并安装这些程序包:

% wget http://pecl.php.net/get/PDO
% pear install PDO-0.1.1.tgz
[ 将 extension=pdo.so 添加到 php.ini ]

% wget http://pecl.php.net/get/PDO_OCI
% pear install PDO_OCI-0.1.tgz
[ 将 extension=pdo_oci.so 添加到 php.ini ]

在上述两种情况下,都需要首先调用“pear install”(后跟下载的真正程序包);上述示例中的版本号在本文编写之时是最新的,但随着开发的继续进行会发生变化。

Windows 安装

如果您正在运行 Windows,则请按照下列说明执行:

  1. http://www.php.net/downloads.php#v5获取 PHP 5,将其解压缩到 C:\php5。
  2. http://snaps.php.net/win32/PECL_5_0/php_pdo.dllhttp://snaps.php.net/win32/PECL_5_0/php_pdo_oci.dll分 别获取 PDO 和 PDO_OCI,将其放入 C:\php5\ext。或者,您可以从 PHP 5 下载页上列出的“用于 PHP 5.0.0 的 PECL 模块集合”zip 文件中找到所有这些 PDO 驱动程序,以及所有 PECL 程序包的所有 Windows 版本。
  3. 编辑 C:\php5\php.ini 文件,并添加下列内容:extension=php_pdo.dll
    extension=php_pdo_oci.dll

编辑 php.ini 文件时,有一点很重要,即要在任何其他 PDO 驱动程序之前先加载 PDO 扩展,否则就不能正确初始化(在这种情况下会出错)。

如 果在 Windows 目录中有一个 PHP 4 的全局 php.ini 文件,则可能会遇到问题。最好的解决方法是,移动该 php.ini 文件,使其与 PHP 4 SAPI 位于相同的文件夹中,以隔离 PHP 4 安装;例如,将其移动到与 php4apache.dll 相同的文件夹中。请注意,PHP 5 程序中并非所有文档都是最新的;推荐的安装过程如上面所述 - 如 install.txt 文件所声明的,请勿将任何 DLL 复制到 windows 文件夹或 system 文件夹中 - 任何内容都是自包含的。如果您运行的是 apache,并且遇到无法加载 DLL 的错误,则检查一下是否将 C:\php5 添加到了 PATH 中。另外,还要注意 PHP 5 的 CGI 版本现在的名称为 php-cgi.exe。

连接 PDO

首 先创建 PDO 类的一个实例,将其用作数据库句柄。使用哪个基础驱动程序并不重要;您总要使用 PDO 类名。构造函数的第一个参数为数据源名称 (DSN),第二个参数为用户名,第三个参数为该用户名的口令。DSN 的 PDO 命名惯例为 PDO 驱动程序的名称,后面一个冒号,再后面是可选的驱动程序特定的信息。在我们的示例中,会加载 OCI 驱动程序但不指定任何其他信息;这样会使用默认的数据库。对于其他驱动程序,如 ODBC 驱动程序,第一个冒号后面的所有内容都将被用作 ODBC DSN。MySQL 驱动程序会同样以不同的方式解释它的 DSN。

如果无法加载该驱动程序,或者发生了连接失败,则会抛出一个 PDOException,以便您可以决定如何最好地处理该故障。

<?php
try {
$dbh = new PDO(”OCI:”, “scott”, “tiger”);
} catch (PDOException $e) {
echo “Failed to obtain database handle ” .$e->getMessage();
}
?>

在连接字符串中,您可以指定两个可选参数;第一个是数据库名称,第二个是字符集;这些参数与可选的第三个和第四个参数相对应,后两个参数您可能在 oci8 扩展函数ociconnect()ociplogon()中使用过。要使用特定的字符集连接一个特定的数据库,则可以执行下列操作:

<?php
try {
$dbh = new PDO(”OCI:dbname=accounts;charset=UTF-8″, “scott”, “tiger”);
} catch (PDOException $e) {
echo “Failed to obtain database handle ” .$e->getMessage();
}
?>

省略 try..catch 控制结构并无裨益。如果在应用程序的较高级别没有定义异常处理,则在无法建立数据库连接的情况下,该脚本会终止。

连接管理

目前,PDO 完全没有执行自己的任何连接管理,因此每个“新 PDO”调用都会建立一个新的数据库连接。该连接在 $dbh 变量越界时,或者当您为其指定 NULL 值时会被释放。

<?php
try {
$dbh = new PDO(”OCI:dbname=accounts;charset=UTF-8″, “scott”, “tiger”);
} catch (PDOException $e) {
echo “Failed to obtain database handle ” .$e->getMessage();
exit;
}
// 在此处对数据库执行一些操作
// …

// 现在完成,释放该连接
$dbh = null;
?>

计划在不久的将来为 PDO 增加连接缓存功能;就当前的 oci8 扩展而言,会重用与现有服务器的连接,并且在这些连接中,还会重用闲置的登录。当在缓存连接模式中运行时,如上面的代码段所示释放 $dbh 时会将该登录标记为可由其他连接重用。

如果您使用 ODBC 驱动程序访问 Oracle,则可能会很高兴地注意到,默认情况下 PDO_ODBC 驱动程序支持 ODBC 连接池。

使用 PDO

了解一个编程 API 的最好方式就是使用它,因此我们来看一下附带的这个演示,以了解如何进行批次更新(请参见示例代码)。

既 然我们已经成功连接到了 Oracle,那么现在就可以创建一个表来保存一些数据了。对于此示例,我们使用一些 PHP 扩展及其作者,并将这些内容输入一个数据库中。数据库句柄对象的 exec() 方法可用来发出不会返回结果集的快速一次性查询,因此我们在这里使用该方法来发出 CREATE TABLE 查询。

为了使得示例更自然,我从 PHP 源代码中抽取了扩展及其作者的信息,并将其存储到了一个 CSV 文件中(请参见“credits.csv”)。这就代表一个常见情形:从 CSV 文件批次导入数据。在我们的示例中,我们充分利用了 Oracle 的预处理语句和绑定参数,以获得一个高效的数据导入脚本。在讲述该示例之前,有必要了解一下 PDO 处理事务的方式。

PDO 中的事务处理

Oracle 具有一个敏感的默认操作模式:当您进行连接时,将会位于一个隐式事务处理中,在提交事务之前其中的更改不会完全生效。除了事务处理的标准优点(原子性、一 致性、隔离性、可持久性 - ACID)之外,数据库服务器在执行每次更新之后还不需要重新构建索引和其他内部结构;它可以延迟到提交之后进行。这样会加速代码的执行。Oracle 这点确实很好。

但不幸的是,并非每个数据库供应商都支持事务处理,并且因为 PDO 旨在以一种相对可移植的方式支持这些事务处理,所以它默认情况下以自动提交模式运行。启用自动提交模式后,数据库驱动程序会隐式提交每个成功的更新。当您调用$dbh->beginTransaction()时,就会请求关闭自动提交,直到调用$dbh->commit()或者$dbh->rollBack()才会重新启用,具体取决于您的代码是怎样编写的。如果基础驱动程序不支持事务处理,则会抛出一个 PDOException。

如果发生了问题并且 PHP 出错,您的脚本将退出并且事务处于待批状态;或者您关闭数据库句柄时,PDO 会自动针对任何待批的事务调用$dbh->rollBack()。此行为会减少向数据库中提交可能未定义或者已损坏数据的可能性,这是用于处理已放弃事务的标准语义。

预处理语句、存储过程

PDO 支持使用 Oracle 样式命名的占位符语法将变量帮定到 SQL 中的预处理语句(与 oci8 扩展中的ocibindbyname()类 似)。PDO 还为其他数据库(如 ODBC)提供了命名占位符模拟,甚至可以为生来就不支持该概念的数据库(如 MySQL)模拟预处理语句和绑定参数。这是 PHP 向前迈进的积极一步,因为这样可以使开发人员能够用 PHP 编写“企业级”的数据库应用程序,而不必特别关注数据库平台的能力。

使用 PDO 预处理语句非常简单,调用数据库句柄的prepare()方法即可。它会返回一个语句句柄对象,然后您可以使用该对象来绑定参数和执行语句。在此示例中,我们将要定义两个命名占位符,“:extension”和“:name”,这两个占位符分别与 .CSV 文件中的 PHP 扩展名称和其中一个作者的姓名相对应。

$stmt = $dbh->prepare(”INSERT INTO CREDITS (extension, name) VALUES (:extension, :name)”);

预处理了语句之后,我们使用bindParam()方法来将这些命名参数分别与 PHP 变量名称“$extension”和“$name”相关联(这与ocibindbyname()类似)。我们还会通知 Oracle,这些数据将要格式化为字符串,最大长度为 64 个字符。

$stmt->bindParam(’:extension’, $extension, PDO_PARAM_STR, 64);
$stmt->bindParam(’:name’, $name, PDO_PARAM_STR, 64);

我们现在即准备好插入数据了 - 我们只需要打开该 CSV 文件,并从中获取数据即可。通过使用fopen()fgetcsv()函数可以相当简单地完成此操作。然后,我们可以使用 PHP list() 构造函数直接将 CSV 的列指定给变量“$extension”和“$name”。因为这些变量已经绑定到了语句中,所以我们现在要做的只是调用该语句对象的execute()方法使其执行插入。这种方式既方便又快捷 - 在事务处理时每个迭代循环只有两行。到达文件尾时,我们就可以立即使用数据库句柄的commit()方法来提交这些更改了。

如果您只是要传递输入参数,并且有许多这样的参数要传递,那么您会觉得下面所示的快捷方式语法非常有帮助;此语法使您能够省去对$stmt->bindParam()的调用。

$stmt = $dbh->prepare(”INSERT INTO CREDITS (extension, name) VALUES (:extension, :name)”);
$stmt->execute(array(’:extension’ => $extension, ‘:name’ => $name));

您还可以使用bindParam来为存储过程设置输入/输出参数;语法是完全相同的,只是查询有所不同。下面的代码演示如何调用一个名为“sp_add_item”的存储过程;其目的是要针对输入设置$item_name,然后该存储过程将在返回时更新$error_code

$stmt = $dbh->prepare(”begin sp_add_item(:item_name, :error_code); end”);
$stmt->bindParam(’:item_name’, $item_name, PDO_PARAM_STR, 12);
$stmt->bindParam(’:error_code’, $error_code, PDO_PARAM_STR, 12);
$stmt->execute();

抓取数据

使用 PDO 抓取数据与进行插入或更新相似,只是您执行完查询之后,将要重复调用fetch()方法来获取结果集的下一行。进行获取的最简单情况如下所示,值得注意的一点是,您还可以将参数绑定到查询,以控制如 WHERE 子句这样的内容;执行此操作的语法与我们已经看到的bindParam()代码完全相同。

$stmt = $dbh->prepare(”SELECT extension, name from CREDITS”);
if ($stmt->execute()) {
while ($row = stmt->fetch()) {
print_r($row);
}
}

PDO 支持一些不同的抓取策略,这些策略在方便性和性能方面有所差别;通过将下列选项之一指定为fetch()方法的参数,您可以更改其返回值以适应您的语法:

  • PDO_FETCH_NUM- 每个行抓取返回一个按照列位置索引的数组,并且以 0 为基数(第一列是第 0 个元素)。while ($row = $stmt->fetch(PDO_FETCH_NUM)) {
    printf(”Extension %s, by %s<br>”, $row[0], $row[1]);
    }
  • PDO_FETCH_ASSOC- 每个行抓取根据行集中的列名,返回一个按列名索引的数组。while ($row = $stmt->fetch(PDO_FETCH_ASSOC)) {
    echo “Extension $row[EXTENSION] by $row[NAME]<br>”;
    }
  • PDO_FETCH_BOTH- 每个行抓取返回一个既按照列位置又按照列名索引的数组。也就是上述两种情况的直接组合。如果没有指定抓取模式,则该模式为默认模式。
  • PDO_FETCH_OBJ- 每个行抓取返回一个匿名对象,其属性名与列名对应。while ($row = $stmt->fetch(PDO_FETCH_ASSOC)) {
    echo “Extension {$row->EXTENSION} by {$row->NAME}<br>”;
    }
  • PDO_FETCH_LAZY- 每个行抓取返回一个引用语句对象的重载对象。这“看起来”好像是PDO_FETCH_OBJPDO_FETCH_BOTH的组合,只是只有当您在脚本中访问 PHP 变量时才创建这些变量。
  • PDO_FETCH_BOUND- 抓取每行,返回 TRUE。在使用绑定输出列时这种方式非常有用,它可以避免创建不需要的任何数组或对象。(请参见下面的示例)。

无论您使用哪种抓取策略,当没有其他行可抓取时,fetch()方法将会返回 FALSE。

现 在我要讲述一些技巧,如果您需要最后再调整一下脚本性能的话,这些技巧可能会对您有所帮助。但先给你一个忠告:要像躲避瘟疫一样避免不成熟的优化。您应该 总是首选最清晰、可维护性最好的解决方案。请记住,在一个典型的 Web 应用程序中,您不能衡量各种抓取模式间的区别,除非脚本要处理很多行。我再重复一遍:抓取模式间的性能区别非常小 - 请使用最适合您代码的模式。

请记住,使用PDO_FETCH_NUM的花销最小,因为访问列数据只是一个简单的数值查询。PDO_FETCH_OBJ使您能够使用 OO 语法将数据集的列作为对象的属性来访问,但是每个属性访问都涉及一个附加的散列查询,使得使用它的花销基本上与PDO_FETCH_ASSOC相同。每个这样的模式都会复制整行,从而占用稍多的内存。

很多数据库驱动程序都会代表您预先抓取并缓存一定数量的行。PHP 每次访问其中一个这样行中的列时,它都需要将其复制到自己的专用内存区域中。如果您的查询涉及很多行,而只需要基于某种复杂的逻辑访问给定行的特定列,则您会发现PDO_FETCH_LAZY是一种避免使用很多内存的有用方法,因为它只有在您访问给定列时才复制该列。使用此方式时要注意,从某个给定语句为每个fetch()抓取的“惰性对象”是每次迭代时使用的同一对象(以减少每次创建/销毁它的开销)。这就暗示着您不能只是简单地存储该对象用于以后的比较,因为它仍然会引用该语句的当前行 - 您需要手动复制所需要的部分。

最后一种模式为PDO_FETCH_BOUND, 该模式会告知 PDO 您已经将所有列绑定到了 PHP 变量,并且除了要它在到达行集的末尾时通知您外不需要它执行别的任何操作。绑定输出列在概念上与绑定输入参数相似,只是绑定输出列可以用于所有数据库驱动 程序。您可以将 PHP 变量绑定到命名列,PDO 将在每次调用execute()时对其进行更新。此技术可用来剃去结果集中每列、每行的一些虚拟机器操作码(这种代码速度比原生码要慢)。这种技术的缺点在于,可能会使您的代码难以跟踪(也称为 WTF 系数较高),您使用变量名称时需要倍加小心。下面的代码说明了绑定输出列的使用。请注意,您不必指定PDO_FETCH_BOUND即可使用$stmt->bindColumn();PDO_FETCH_BOUND只是一个对于您了解只能使用绑定值的情况的一种优化。

$stmt = $dbh->prepare(”SELECT extension, name from CREDITS”);
if ($stmt->execute()) {
$stmt->bindColumn(’EXTENSION’, $extension);
$stmt->bindColumn(’NAME’, $name);
while ($stmt->fetch(PDO_FETCH_BOUND)) {
echo “Extension:$extension, Author:$name\n”;
}
}

可移植性

区分大小写的列

PDO 旨在令使用可移植 SQL 的脚本运行良好、可移植。本文中提及的所有查询(调用存储过程除外)在使用任何 PDO 驱动程序时其运行性能应该相同 - 包括所有绑定输入变量和绑定输出列。

但有一个转换问题 - 当您使用PDO_FETCH_ASSOC抓 取数据时,不同的驱动程序会以不同的方式返回列名 - 某些会将列名转化为大写,某些转换为小写,某些则会使其呈查询中指定的样式。这对于 PHP 脚本来说是一个潜在的问题,因为数组键区分大小写。PDO 提供了一个兼容性属性来帮助规范脚本的结果。下面的小代码段是上面PDO_FETCH_BOUND示例的可移植版本,因为setAttribute()方法调用会指导 PDO 将抓取返回的列名全部转换为大写:

$dbh = new PDO(’OCI:’, ’scott’, ‘tiger’);
$dbh->setAttribute(PDO_ATTR_CASE, PDO_CASE_UPPER);
stmt = $dbh->prepare(”SELECT extension, name from CREDITS”);
if ($stmt->execute()) {
$stmt->bindColumn(’EXTENSION’, $extension);
$stmt->bindColumn(’NAME’, $name);
while ($stmt->fetch(PDO_FETCH_BOUND)) {
echo “Extension:$extension, Author:$name\n”;
}
}

除了PDO_CASE_UPPER之外,还有PDO_CASE_LOWER(它会将列名转换为小写)和PDO_CASE_NATURAL(它是默认选项:使列保持数据库驱动程序返回的形式)。

错误和错误处理

可 移植脚本的另一个难题是处理从各种数据库处理程序返回的各种不同的错误消息;某些数据库对于程序化处理错误的支持能力很差,而其他一些数据库则具有非常丰 富的错误代码。只要可行,PDO 将为您的脚本提供一个统一的错误代码,从而使您不必为应对可移植性的这个方面所累。当然,PDO 还会为驱动程序提供原生错误代码和错误消息,以防您需要用它来进行诊断,或者错误代码映射不完整。

另一个困扰 PHP 数据库扩展的一致性问题是错误处理策略的一致性:某些扩展会返回的错误代码需要您手动抓取错误字符串,而其他一些扩展则只是发出 PHP 警告。PDO 允许您从下列三种不同的错误处理策略中选择一种:

  • PDO_ERRMODE_SILENT
    这是默认模式;它只是使用语句和数据库句柄对象的 errorCode() 和 errorInfo() 方法为您设置要检查的错误代码。if (!$dbh->exec($sql)) {
    echo $dbh->errorCode() .”<BR>”;
    $info = $dbh->errorInfo();
    // $info[0] == $dbh->errorCode() 统一的错误代码
    // $info[1] 是驱动程序特定的错误代码
    // $info[2] 是驱动程序特定的错误字符串
    }
  • PDO_ERRMODE_WARNING
    除了设置错误代码之外,PDO 还会发出 PHP 警告,您可以使用常规的 PHP 错误处理程序捕获该警告,并集中应用您准备好用于应用程序的任何错误处理/记录策略,或者只是使该错误显示在浏览器中(在内部测试过程中非常有用)。
  • PDO_ERRMODE_EXCEPTION
    除了设置错误代码之外,PDO 还会抛出一个 PDOException,并将其属性设置为包含该错误代码和信息。然后,您可以在代码的较高级别捕获该异常,使用全局异常处理程序捕获该异常,或者不对其进行处理而终止脚本(此时将回滚任何未决的事务)。try {
    $dbh->exec($sql);
    } catch (PDOException $e) {
    // 显示警告消息
    print $e->getMessage();
    $info = $e->errorInfo;
    // $info[0] == $e->code; unified error code
    // $info[1] 是驱动程序特定的错误代码
    // $info[2] 是驱动程序特定的错误字符串
    }

请注意,与警告或异常相比,静默模式针对运行时错误使用的资源最少,但是为了获得该速度,您牺牲了一些简单性,而变得有一点复杂。

统一错误代码表当前包括下列常量:PDO_ERR_NONE、 PDO_ERR_CANT_MAP、PDO_ERR_SYNTAX、PDO_ERR_CONSTRAINT、PDO_ERR_NOT_FOUND、 PDO_ERR_ALREADY_EXISTS、PDO_ERR_NOT_IMPLEMENTED、PDO_ERR_MISMATCH、 PDO_ERR_TRUNCATED、PDO_ERR_DISCONNECTED。

这些常量所代表的意思字面即可推知,但是 PDO_ERR_CANT_MAP 代码除外;这是一个 PDO 特定的代码,也就是说它无法将驱动程序特定的代码映射到统一的错误代码,因此您应该查询 errorInfo() 方法返回的驱动程序特定代码来获得更多信息。

数据类型

PDO 在某种程度上类型不可知,因此它喜欢将数据表示为字符串,而不是将其转换为整数或双精度类型。此时您可能对此有些迷惑,但是原因非常简单:字符串类型是最 精确的类型,在 PHP 中具有最广泛的应用范围;过早地将数据转换为整数或者双精度类型可能会导致截断或舍入错误。通过将数据以字符串抽出,PDO 为您提供了一些脚本控制,您可以使用普通的 PHP 类型转换工具(如数学运算过程中的转换和隐式)来控制如何进行转换以及何时进行转换。

NULL

如 果结果集中的某列包含一个 NULL 值,PDO 则会将其映射为 PHP null 值。Oracle 在将数据返回 PDO 时会将空字符串转换为 NULL,但是 PHP 支持的任何其他数据库都不会这样处理,从而导致了可移植性问题。PDO 提供了一个驱动程序级属性PDO_ATTR_ORACLE_NULLS,该属性会为其他数据驱动程序模拟此行为:

$dbh = new PDO(’OCI:’, ’scott’, ‘tiger’);
$dbh->setAttribute(PDO_ATTR_ORACLE_NULLS, true);
// 现在从此 $dbh 打开的任何语句中的
// 空字符串都将被转换为 NULL

POD 的现状和未来

PDO 现在仍相当不成熟,但是会快速成熟起来。在编写本文之时,我在本文中提到的任何内容都能够通过 PDO_OCI 驱动程序适用于 Oracle 8 或更高版本(在 Oracle 8.0 和 9.2 上测试过)。

已经计划增加以下主要特性,在不久将可以使用:

  • 使用 PHP 流的 LOB 支持。使 用绑定参数,您能够将任何流资源(如文件、套接字、HTTP 资源、压缩/筛选的流)作为输入或输出参数传递到在 LOB 上运行的查询中。与之相似,类型为 LOB 的输出参数将表现为 PHP 流,因此您可以使用 fread()、fwrite()、fseek() 和其他流函数来访问这些参数。此时,在 PDO 中根本没有 LOB 支持。
  • 持久性连接和缓存的预处理语句。持久性连接使您能够避免在每个页面命中时打开和关闭数据库服务器连接。缓存的预处理语句又前进了一步,它使您能够持久保持查询的预处理版本以及数据库句柄。
  • 游标。目前,PDO 只提供前向只读游标,但是将来会提供可滚动游标(需要基础驱动程序支持)、REF-CURSOR、使用游标进行定位更新,以及可更新滚动游标。

我 们希望在 PHP 5.1 中默认启用 PHP 扩展(距此目标尚远),但是在此之前,我们希望能让 PDO 在 PHP 5.0 发布时稳定运行,但是我们日常工作中的压力稍稍拖延了这些工作。同时,通过 PECL 发布 PDO 使我们能够在收到问题报告时做出回应,并根据不同于 PHP 5.0 发布时间表的时间表发布修复版本,因此您在 PHP 5.1 发布前即可使用 PDO。

我们需要您的反馈

如果您试用了 PDO,并且发现了问题,请务必使用我们的错误跟踪软件将其报告给我们。如果您使用的是 Oracle 驱动程序,则请使用此页:

http://pecl.php.net/bugs/report.php?package=PDO_OCI

如果您使用的是其他驱动程序,则请用其名称替换该 URL 中 PDO_OCI。

如果您使用 PDO 时遇到问题,或者针对某些特性存在疑问,或者具有特性请求,请联系pecl-dev@lists.php.net。如果您愿意,当然还可以直接联系我 (wez@php.net),但是请注意,我每天都会收到大量有关 PHP 的电子邮件;您可能会发现如果首先与前面的邮件列表联系会更快得到答复。


Wez Furlong是 Brain Room Ltd. 的技术总监,他在该公司不但使用 PHP 用于 Web 开发,还将其用作 Linux 和 Windows 应用程序和系统的嵌入式脚本引擎。Wez 是 PHP 的核心开发人员,经常向 SQLite、COM/.Net、ActivePHP、mailparse 和 Streams API 等投稿,他是 PECL 即 PHP 扩展社区库的“头儿”。他的咨询公司的网页为http://www.thebrainroom.net

网站建设

php

可扩展 可伸缩的 Oracle 与 PHP

Tags: ,

了解一般的技术和设计,用于编写与 Oracle 数据库的使用直接相关的可管理、可伸缩的快速 PHP 代码。

在过去九年中,PHP 已经从组装个人网站的小型语言发展到为世界上某些最大和流量最高的网站提供动力。任何高流量网站的三个最重要的设计方面是可伸缩性、性能和可维护性。可伸缩性意味着您的应用程序流量负载可以不断增长,而不会从根本上破坏其工作方式。性能是快速为单个请求提供服务的能力。 可维护性是能够在不造成过多负担的情况下修复、重新调整、扩增或更改应用程序的品质。

利用 PHP 来实现这三个设计目标并不困难,但确实需要预先考虑如何设计和构建您的应用程序。关于编写可管理、可伸缩的快速 PHP 代码的论题范围很广;针对每个论题都有大量的技术和文章。在本文中,我们将讨论那些与使用 Oracle 及 PHP 直接相关的因素。有很多一般(非 Oracle 专用)技术和设计可能非常有用。

我喜欢以一个尖锐的警告作为任何与性能相关的谈话或文章的开始:始终要记住,最后总有一天,快速却不完善的应用程序将毫无价值。性能调整以及对应用程序不利因素的设计提取都很容易分散您的精力。Web 的性质就是这样,经常发布版本的方法非常有效。(发布网站“新版本”的成本很低,因为最终用户始终需要这些代码。)这就允许您延迟对代码的重大调整,直到需要这样做为止。因此,首要目标应该是创建便于重新调整的代码。

创建和管理连接

与 Oracle 数据库最基本的交互之一是连接。要了解连接如何影响您的应用程序的性能和可伸缩性,需要了解连接的生命周期,如图 1所示。每个步骤所涉及的工作如下:

  • 客户创建连接:客户创建与 Oracle 监听器的网络连接,提供其认证证书,并请求会话。
  • 服务器创建一个新会话:在认证之后,服务器为客户创建一个新会话。如果您没有通过 Oracle 多线程服务器(MTS — 它在可伸缩性及性能问题上臭名昭著)使用共享会话,则此步骤包括服务器为会话创建一个专用进程。该进程通常称为影子进程。创建此进程需要不少工作量。除了创建进程的正常开销之外,影子进程在其创建期间还必须临时锁定某些共享系统资源。
  • 客户端执行查询:既然客户端已经具有开放的连接,就可以根据需要来执行查询。
  • 客户端关闭连接:当客户端完成工作后,关闭与服务器的连接。
  • 服务器毁坏会话:与用户会话相关的影子进程被毁坏,任何未提交的事务被回滚。

图 1:连接的生命周期

由于创建新的影子进程的成本相当大,我们应该在必要时努力避开它。达到此目的的最简单方法是使用持续连接。PHP 被设计为一种非会话状态的语言。这意味着在默认情况下,在请求期间创建的任何信息(或例程化的资源)都会在请求结束时被彻底清除并毁坏。对于 Oracle 客户连接,我们希望避免这种行为。

为了使连接能够从一个请求保留到下一个请求,您可以使用以下两种连接变通方法之一:

OCIPLogin($username, $password [, $tnsname])

OCINLogin($username, $password [, $tnsname])

这两个函数都创建持续的服务器连接,尽管 OCINLogin() 将为每个请求创建一个新会话句柄。如果您的应用程序要使用事务,并且您希望将同时发生的事务分散到多个会话中,则可以使用 OCINLogin()。

使用持续连接的一个副作用是您更容易出现进程不足的情况。基于专用 Oracle 数据库运行单个 Apache Web 服务器(子进程的最大默认数量为 256)时,可能从不会遇到问题。但是如果增加到 4 个 Web 服务器,每个服务器运行 256 个具有持续 Oracle 连接的子进程,现在则要创建 1024 个与 Oracle 数据库的连接,并且很快就会与 Oracle 实例的资源限制发生冲突。

在 Oracle 实例配置文件 (init.ora) 中,有两个可调整的参数:

sessions = NNNNprocesses = NNNN

这两个参数控制着实例可以支持的最大会话数和最大进程数。如果您需要支持 1024 个同时出现的连接,则需要至少 1024 个会话(因为OCINLogin()连接和某些递归查询可能在每个连接中需要多个会话),而需要的进程还会更多(因为我们还需要考虑 Oracle 后台进程)。不幸的是,不能任意将这些进程设得很高。Oracle 进程消耗不少的专用内存(在多数系统中每个进程需要 2 到 3MB)。从个体来说,这些进程很小,但当把它们作为一组并与服务器系统全局区 (SGA) 所需要的共享内存相结合时,很快就会让您因为数据库服务器的物理内存限制而感到烦恼。

来自 MySQL 环境的用户可能试图避开持续连接(这是 MySQL 环境中的建议)。由 Oracle 影子进程启动所导致的栓锁和文件争用使得非持续连接的使用效率极低。那么解决方案是什么呢?

  • 应该确定我们的数据库能够支持多少个同时发生的会话,并相应地设置其限制。有些文章详细说明了如何完成此工作,但主要是一个计算过程:计算系统中物理内存的总量,并减去内核、支持程序和 Oracle 后台进程所使用的内存。然后减去所有被配置为 Oracle 共享内存的那些内存(共享池和缓冲区高速缓存)。剩下的内存可以用于影子进程。将该数量除以一个影子进程所使用的专用进程内存的平均数量(应该自己测出该数量,因为它根据您所运行的查询性质而变化),则我们得到可支持的进程数量。
  • 对我们的 Web 服务器进行配置,以便使其永远不能创建超过您的进程设置允许数量的连接。其实现方法是将每个 Web 服务器的 MaxChildren 可调参数设置得足够低,使得所有 Web 服务器总共拥有的子进程数量低于可支持的 Oracle 连接数。这意味着每个 Apache 实例不再支持 256 个子进程。
  • 重新设计我们的应用程序,使其不会遗漏额外的子进程。我们在后文中还会讨论这个话题。

这样有多重要?作为一个老板,我们即使启用了持续连接,也一直遇到栓锁问题。任何遇到过严重栓锁争用的人都能证实,这是一个极为严重的问题,服务器在很大程度上不响应,因为它花费过多时间来进行锁定操作。我们在调查中发现,尽管在终止前有大量进程为数以百计的请求提供服务,但很多进程只服务于单个请求。这一切是由于我们将 Apache 的MaxSpareServers设置得太低。我们所使用的负载均衡设备的一些问题导致了“突然爆发”的行为,此时 Web 服务器被多个同时发生的请求所冲击,然后闲置数秒时间。在 Apache 内部,这导致要创建额外的子进程,来为高请求等级提供服务;但当它一旦平息时(几乎立即平息),就会出现大部分目前处于闲置状态的子进程(终止进程并关闭其 Oracle 连接)。总体看来,这与运行非持续进程的效果相似。将MaxSpareServers设得较高就可消除此问题,并消除了栓锁争用。

执行 SQL

任何 Oracle 客户服务器关系的主要内容是执行查询。这里没有篇幅来谈论对查询的调整 — 那是需要整本书来讨论的主题。相反,我们将集中讨论如何尽可能地使已经调整过的查询高效运行。

使用 PHP 编写良好的 Oracle 应用程序代码的第一步是始终使用绑定 SQL。当我们编写类似以下的查询时:

SELECT * FROM USERS WHERE USERNAME = 'george'

Oracle 必须对该查询进行软分析,查看以前是否曾编译过该查询。在默认情况下,”george” 值被作为文字项,这意味着如果我们使用不同的名字 (’bob’) 执行此查询,则 Oracle 将把它看作完全不同的查询。Oracle 在其共享池中保留它执行的每个查询的分析副本,因此如果您使用数千个名字来执行此查询,则在您的共享池中将有数千个该查询的不同副本。即使在轻度活跃的网站上,这也会导致严重的内存碎片以及 ORA-4031 错误增殖。

对这个问题的解决方案是使用绑定 SQL。绑定 SQL 允许我们将 WHERE 子句中的文字值替换为占位符,如下所示:

SELECT * FROM USERS WHERE USERNAME = ':NAME'

在这里,查询只须被完全分析一次(硬分析);以后所有的分析都是所谓的软分析,此时引擎只是简单地从 SGA 中提取已编译的查询。此外,将只有一个单次分析的副本被存储,从而显著减少了不断执行的查询对内存的需求。

现在我们可以执行如下:

<?php
$db = OCIPLogin('scott', 'tiger', 'testdb');
$stmt = OCIParse("SELECT * FROM USERS WHERE USERNAME = ':NAME'");
OCIBindByName($stmt, ":NAME", "george");
OCIExecute($stmt);
?>

在 Oracle8i之前,我们必须手动绑定查询;从 8i开始,通过设置 init.ora 的参数 “cursor_sharing = FORCE”,我们可以指示优化器为我们完成这一工作。该设置通知优化器查找可以绑定的文字值,并手动执行绑定。自 9i起,我们可以使用设置“cursor_sharing = SIMILAR”,该设置指示优化器深入查看基表的统计信息,了解自动绑定文字是否有好处(如果某个字段的分布极不均匀,则可能没有好处)。尽管应该启用这些设置(在 8i中为FORCE,在 9i及更高版本中为SIMILAR),但深入到查询中分析潜在绑定的这种操作对于优化器而言成本很高,因此在可能的情况下,应该手动绑定您的查询。

Oracle 客户服务器在 SQLNet 协议基础上运行,众所周知,这种协议的对话很多。例如,如果您执行一个返回 100 行的查询,则会有分析查询的对话交换、对每个绑定变量的交换、对执行的查询以及对每个提取行的查询。其中每次交换都包括客户端与服务器之间的网络数据包交换(称为一次往返)。减少往返次数可能具有深刻的性能影响。

解决这个问题的第一种方法是建立客户的预取缓冲区。在网络上传送单独一行的效率极低,因此最好主动将很多行集中在一起,并将它们从本地缓冲区中读出。如果我们将语句的句柄设置如下,则 Oracle 客户库能够自动执行这种服务:

$stmt = OCIParse($db, $query);
OCISetPreFetch($stmt, 1000);
//  execute and fetch

这些设置指示 OCI 客户库每次内部缓冲 1000 行,对任何返回超过一行的查询提供积极的回报。1000 是个相当随意的数字 — 在选择预取缓冲区大小时仅有的限制因素是:

  1. 在执行返回之前需要填充缓冲区 — 如果对部分结果集的即时访问非常重要,将不希望缓冲区过于庞大。
  2. 被缓冲的结果集保留在本地内存中,直到被刷新为止 — 如果您具有非常大的行或非常大的结果集,则不应将缓冲区设置得过大,以防止客户端出现内存不足。

管理与 Oracle 的交互操作

现 在您已看到了一些通过 PHP 改进与 Oracle 数据库交互操作方式的技术。对您的程序作出这类结构性更改是管理上的争论焦点,因为它通常需要对代码中的很多地方作出不少更改。解决问题的正确方法是将所 有的数据库访问代码封装在一个打包的库中,这样就可以对如何准备和执行查询等内部问题进行更改,而不必审计您的全部编码内容。

我说的是“打包的库”而不是“抽象层”,因为很多所谓的抽象层强调不仅向您隐藏低级数据库调用的细节,而且还隐藏 SQL。它们实施各自的与数据库无关的语法,以便您能够毫不费力地将您的应用程序改到其他数据库中。我认为这种方法存在三个主要问题:

  1. SQL 是很多开发人员了解的一种功能强大的描述性语言。不让他们使用这种语言并要求他们学习一种新的、灵活性较差的语言是很可笑的。
  2. 每种主要的数据库产品都具有非标准的 SQL 语法,以便完成特定的任务。放弃这些差异会限制您的灵活性,并使您失去所选择平台的某些价值。如果只考虑它们所支持特性的交集,最终只得到最低程度的通用特性。
  3. 对于多数人而言,更换数据库供应商是很少见的现象,无论您的应用程序是否识别数据库,这都会涉及到相当多的数据移植工作。

用于 PHP 的两种最流行的数据库封装/抽象库是 PEAR::DB(从http://pear.php.net/获取)和 ADODB(从http://php.weblogs.com/adodb获取)。不管这两种实施方法的流行程度(和品质)如何,,我通常从头开始实施自己的数据库打包的库。我不需要 ADODB 或 PEAR::DB 中的很多高级特性,而保持库的简单性可以使它速度更快并且更易于维护。没有复杂的特性,一个紧密的库可以用大约 100 行代码来完成它。如果提供了 Oracle 数据库和语句句柄中所执行的信息量,则我更喜欢面向对象的封装,尽管我也曾看到过效果良好的程序性封装。

清单 1显示了一个完整的打包的库,它包含一个围绕数据库连接的包 (DB_Oracle) 和一个围绕游标的包 (DB_OracleStatement)。

这些类的目标是使对数据库交互操作的管理变得简单、直接和清晰。以下显示了如何使用它们来执行一个简单的查询:

<?php
include_once("DB_Oracle.inc");
$dbh =& new DB_Oracle('scott', 'tiger', 'testdb');
$stmt =& $dbh->prepare("SELECT * FROM users WHERE name = :name");
$stmt->execute(array(':name' => 'george'));
$result = $stmt->fetch();
// ...
?>

由于封装是面向对象的,通过对类进行扩展,可以轻易隐藏所有的连接参数。这对于提供一个简单的、无参数的连接类非常有用,如下所示:

class DB_Oracle_Test extends DB_Oracle {
var $user   = "scott";
var $pass   = "tiger";
var $tnsname = "testdb";

function DB_Oracle_Test() {}
}

这个新类向程序员隐藏了所有的连接标准,允许透明地更改实例的TNSNAME或连接标准。还要注意如何向用户隐藏OCISetPreFetch()调用。如果您需要删除它,或者需要添加另一个连接修改器,其操作很容易,并且对您的所有连接起作用。这就是使用打包的库的价值。

使您的进程更加高效

在文章的开始部分,我提到我将帮助您不遗漏在 Apache 中为满足可伸缩性需求而减少的子进程。利用更少资源来完成任务的三种最简单方法是:

  1. 更快地完成任务。任何人都会告诉您,增加工作负载的最快方法是更快地完成现有的工作。无论是安装编译器高速缓存、配置代码还是调整数据库查询,您能够从应用程序中取得的任何性能增益都会在可伸缩性方面得到回报。
  2. 将某些任务转包给专家。例如,网站通常包含动态组件(您的 PHP 脚本就是这种组件)和静态组件(图像和非动态 HTML)。访问 Oracle 的 Apache 实例在服务于静态内容时,您受到严格的约束,这是在浪费时间。可将它外包给专用于处理这类负载的 Web 服务器。
  3. 完全跳过工作。分析应用程序时经常会发现一些数据库驱动的组件,这些组件不必为每个请求而从数据库生成。找到这些组件并尽可能将其重新设计为静态组件,这样会显著提高性能和可伸缩性。

卸载静态内容。如果 Web 应用程序中的页面一般包含九幅图像,则发送到您的 Web 服务器的请求中只有百分之十实际上使用了为其分配的持续连接。换句话说,百分之九十的请求正在浪费有用的(从可伸缩性的观点来看,还是成本很高的)Oracle 连接句柄。您的目标应该是确保只有那些需要 Oracle 连接(或至少需要动态内容)的请求才能接受动态 Web 服务器的服务。这会增加由每个进程所完成的与 Oracle 相关的工作量,而这又会减少生成动态内容所需的子进程数量。

改善这种状况的最简单方法是将您的所有图像卸载到单独的 Web 服务器(或一组 Web 服务器)上。这非常容易。第一步是建立第二个 Web 服务器处理静态请求。虽然您可以为此而使用 Apache,但是还有些专门擅长于为静态数据提供服务的其他 Web 服务器(例如 tux 和 thttpd),它们的结果或许更好。应该建立这个 Web 服务器,为另外子域的请求提供服务。常见做法是将 “www.example.com” 的图像流量委托管给 “images.example.com“。某些硬件负载均衡器实际上允许您为同一域中的图像提供服务,并在内部进行分配 - 有关详细信息请查看负载均衡器文档。

一旦建立了域,就应该创建一个全局配置文件,它包含您在整个应用程序中使用的所有全局常数,并应该至少添加下面一行:

define(CDN_URL, "http://images.example.com");

此文件可以手动地包含在每个文件的顶部,或者可以在 php.ini 配置文件中添加下行,从而在每个脚本开始时能够自动运行该文件:

auto_prepend_file = /path/to/config.inc

现在,不管何时在 HTML 中创建图像标记,都应该添加以下代码:

<img src="<?= CDN_URL ?>/path/to/image.png">

如果您更愿意拥有标记编写库,则可以编写一个图像标记创建函数,如下所示:

function img_tag($local_uri, $attr)
{
$attribute = '';
foreach ($attr as $k=>$v) {
$k = urlencode($k);
$v = urlencode($v);
$attribute .= " $k=\"$v\" ";
}
return "<img src=\"".CDN_URL."$local_uri\" $attribute>";
}

即使不准备立即使用图像专用网络,还是应该对您的应用程序进行编码,为图像使用单独的基础路径,并简单地将其设置如下:

define(CDN_URL, "http://www.example.com/images");

这允许您在任何时候仅仅更改单行代码,就将整个网站削减为一个替代性的图像服务网络。根据您的网站中静态内容/图像的比例,可以看到服务器资源明显减少甚至极大地减少。在一个客户端,将静态内容从 Apache 移动到专用的系列 thttpd 箱中,可以使它们的总体基础架构减少百分之五十。

为什么要在不必工作的时候而工作呢?对 任何查询的最终性能增强是根本不运行查询。很多“动态”Web 页面实际上不是动态的,在短期时间内纯粹是静态的。假设有一个新网站:在新闻项目更新之前,其内容并不改变。无论这种更新是每分钟发生还是每小时发生,在 两次更新之间,网站是静态的。这意味着,不必为每个页面请求而在当前新闻项目中进行数据库查找,只需在每个更新周期中作一次查找。

最简单类型的高速缓存是全页随需高速缓存。在这种情况下,当请求到来时,应用程序在高速缓存中查找所需文件。如果存在高速缓存的副本,则将其返回给请求者;否则创建一个高速缓存副本,并将其返回给用户。要刷新高速缓存,只需简单地将旧的高速缓存副本删除即可 - 下一个请求将会自动重新生成高速缓存。

图 2显示了我们希望完成的流程图:当一个请求需要 /archive/123.html 时,Web 服务器应该查看该文件是否实际存在。如果该文件存在,则返回该文件。如果该文件不存在,用户会被重定向到一个 PHP 页面 generate.php,并将页面标识符 “123″ 作为参数传递。随后该页面生成页面的高速缓存项。在 Apache 环境中,执行这种高速缓存查找的两种典型方法是使用自定义的 ErrorHandler 或者 mod_rewrite。mod_rewrite 提供更好的灵活性,因此我将实施这种方法。

图 2:全页随需高速缓存流程图

首先,需要在 httpd.conf 中建立重写规则。以下是一个示例片段:

RewriteEngine   On
RewriteConf     %{REQUEST_FILENAME}        ^/archive/[0-9]+\.html
RewriteConf     %{REQUEST_FILENAME}        !-f
RewriteRule     ^/archive/([0-9]+)\.html   /generate.php?id=$1

此示例首先启动重写引擎,然后说明将会重写任何与归档模式 (^/archive/[0-9]+\.html) 相匹配并且不存在 (!-f) 的被请求文件名,将页面标识符作为参数传递,转到生成页面处。

生成器页面同样很简单:

<?php
$id = $_GET['id'];
$dbh =& new DB_Oracle_TestDB;
$cursor =& $dbh->execute("SELECT content FROM news WHERE id = $id");
$result = $cursor->fetch();
if(!$result) {
header("HTTP/1.0 404 Not Found");
exit
}
} else
echo $result['CONTENT'];
$outfile = $_SERVER['DOCUMENT_ROOT']."/archive/$id.html";
if(($fp = fopen($outfile, "w")) === false) {
exit;
}
fwrite($fp, $result['CONTENT']);
fclose($fp);
}
?>

在本示例中,生成器假定新闻表的内容列中包含完全格式化的内容。实际上,您也最可能在脚本中执行某种格式化,使用输出缓冲功能来捕获输出。

结论

您在这里看到的只是关于扩展 Oracle 与 PHP 技术的简短说明。尽管这些示例可能很有用,但我希望您从本文中了解的主要内容是:

  • 严密管理您的 Oracle 连接,以避免资源耗竭。
  • 速度为您带来高效率,这有助于可伸缩性。
  • 在可能的情况下,使用高速缓存技术来完全避免数据库查询。

George Schlossnagle是 OmniTI Computer Consulting 的负责人,这家位于马里兰的技术公司专门从事高容量 Web 及电子邮件系统方面的工作。在加入 OmniTI 之前,Schlossnagle 在几家高水平的社区网站主持技术工作,在这些地方他获得了在非常大的企业环境中管理 PHP 的经验。Schlossnagle 经常为 PHP 团队作出贡献。在 PHP 核心以及 PEAR 和 PECL 扩展库中可以发现他的踪迹。

网站建设

php

使用 PHP 和 Oracle 开发企业应用程序

Tags: , ,

作者 Kevin Kardasz、Vadim Kudryavtsev、Robert Mark、Mikhail Seliverstov

麦吉尔大学的开发与校友关系部如何使用 PHP 和 Oracle 在网上支持多达 16 万名用户

在麦吉尔大学(位于加拿大魁北克省的蒙特利尔)的开发与校友关系部,我们正在使用行业级的 PHP 应用程序和 Oracle9i,将范围广泛的多层次服务转移到 Web 上,这些服务由 200 到 300 名受薪的和志愿的工作人员以及多达 16 万名校友和捐赠人使用。预计该程序在主要的电子广播时段每小时将会接纳数万名用户。我们利用 Oracle 功能强大的行级安全特性以及 PHP 的高性能和快速开发平台来完成这一工作。本文将探讨以下方面:

  • 背景
  • 我们为什么选择 PHP、Oracle9i、Apache 和 Linux
  • 应用程序的结构
  • 安全性
  • 同步
  • 学到的教训以及对开发人员的建议。

背景

我们的机构面临着很多机构所面临的问题:

  • 中央数据库应用程序
    • 散乱而繁琐
    • 属于关键任务并且不可替代
    • 难以更改或者几乎无法更改
    • 依赖生产商的版本升级进行更改
    • 出于安全和成本的考虑,不能访问互联网
  • 大量半独立状态的辅助数据库和 Web 站点
    • 与中央数据库没有充分同步
    • 对于适当的安全性和升级维护而言,数量过多
    • 为最终用户提供过多的 ID 和口令
    • 没有遵循已确立的机构业务惯例
  • 越来越多地要求
    • 通过基于 Web 的 IT 工具获得机构的高效率和生产率
    • 易于使用
    • 在功能间同步数据
    • 可伸缩性
    • 灵活性
    • 功能的扩展性
    • 加强 IT 和一般业务惯例的应用

那么,我们正在如何应对这些挑战呢?

我们已经启动了安全的、基于 Web 的模块化数据库应用程序开发的第一阶段。它以 PHP 和 Oracle9i编写而成,在替代卫星应用程序及 Web 站点之后,必将为用户提供更好的服务。它对公共数据进行同步,并在总体上以及数据方面加强最佳业务惯例的应用。

该应用程序称为 Companion,因为它增加了反向中央数据库的功能,准确地报告中心数据,并在出现新数据时将其回送。

它包含超过 100 个表,这些表被分为 6 个模块。最长的表大约有一百万行。有些表有 20 到 30 列。全部用途是由用于行级安全性的 Oracle 细粒度访问控制 (FGAC) 和虚拟专用数据库 (VPD) 构成的。

Companion 应用程序包含三个域,而且能够容纳更多的域,这些域全都安全地访问同一 PHP/Oracle 应用程序中的相同数据。这些域包括以下内容:

  1. 工作人员内联网
    为 200 到 300 名工作人员提供的内联网,包含人们在许多工作人员内联网中可以看到的所有 Web 网站、在线文档和目录,还包含一组模块,用于完成以前由辅助应用程序所做的所有工作。工作人员的定义已经扩展为包含志愿人员(我们是非赢利组织)以及从我们单位之外挑选的员工。工作人员内联网包含创建页面、文章以及在另外两个域中产生的事件的工具。
  2. “会员”内联网
    一个由口令保护的在线社区,用于我们所服务的多达 16 万名会员 — 这里指我们的毕业生
  3. 公共域
    一个信息站点,链接面向公众的子网站和新闻文章。

我们为什么选择 PHP、Oracle、Apache 和 Linux

Apache 和 Linux。二者都以强健、低成本和安全性(在正确配置的情况下)而著称。除了作为数据库服务器操作系统给人留下深刻印象之外,我们还发现 Linux 的 Debian 版本(来自http://www.debian.org/的 Debian 项目)特别适合作为 Web 服务器,原因在于它在开放源代码社区中具有非常高的支持率、其集成软件的高质量以及它在稳定性方面的良好声誉。

PHP。我们没有选择 Active Server Pages、ColdFusion 和 JSP,而是选择 PHP 作为我们的 Web 服务器端脚本环境,原因在于它的速度、成本效益、大量的库文件以及比其他 Web 技术更好的自定义功能。使用 PHP 还使我们免于束缚在特定厂商的开发工具集上。通过一个由 Web 网站以及程序员论坛(提供关于如何处理高级故障的有用提示)组成的网络,可以在 Web 上获得支持。有些 Web 网站还提供大量可用于开发更大型应用程序组件的有效代码。大部分程序员很容易理解 PHP 代码,因为它使用了与 C 相似的语法。我们讨论过,使用 Java 来开发我们所期望的服务器对象,其运行效率高于脚本代码。但是,我们尚未发现需要与 Java 集成,因为已经证明 PHP 非常灵活和快速,足以满足我们的需要。清单 1显示了一个通过 PHP 查询 Oracle 的示例。

在进行更加复杂的面向对象的编程时,使用 PHP 的不足就会显现出来。这是因为它最初是作为一种脚本语言构建的,所以在 PHP 第 4 版中面向对象的特性非常少。这一问题已在 PHP 第 5 版中得以解决,该版本中引入了高级的面向对象特性。

Oracle9i在不使用第三方工具的情况下,保持 Companion 数据库与中央数据库的同步是我们的首要需求之一。在两个 Oracle9i系统之间交换数据将会证明比开发其他转换和传输数据的方法要容易得多。在我们的案例中,使用 Oracle9i带来的好处与我们的投入相比要大得多,这是因为我们能够利用先前 PL/SQL 代码,从而节省了开发时间。由于 Oracle 也移植到了 Linux 上,因此我们现在在 PC 体系结构的数据库服务器操作系统方面有了更多的选择。虽然中央数据库运行在 Oracle8i上,但我们选择使用 9i进行开发,以利用其更加先进的 VPD 特性(如 FGAC 和安全应用程序环境),它是市场上具有这些功能的唯一平台。

Companion 应用程序的结构

如同许多软件项目一样,我们开发团队的目标就是,基于可重用安全性组件和数据访问组件的框架,创建由多个功能独立的模块组成的灵活、可伸缩的应用程序。以下就是我们如何做到这一点的。

N 层体系结构。为了确保应用程序的可移植性和按需扩展的能力,需要对总的体系结构进行分层 — 换句话说,分为两层或多层,从而减少在某些基础性技术过时或者不能满足我们的需要时重新编写整个应用程序的风险。

在经过大量思考后,我们决定选用自定义的 n 层体系结构(该结构考虑了应用程序的需求以及机构的具体情况),而没有采用较传统的两层或三层的方法。(参见图 1。)

图 1:Companion 的分层结构

GUI 呈现层是最终用户利用其本地浏览器实际看到并处理的内容。该层包含由底层生成的 HTML 页面和表单,还包括用来强化外观和导航的客户端 Javascript 和 CSS(层叠样式表)。该层的主要目的是收集用户输入和提供输出。

呈现逻辑层位于服务器上,只包含那些按功能逻辑分类的 PHP 脚本。它包含呈现模板和布局、验证例程以及特定模块专用的脚本。该层主要用于验证和处理用户输入,并基于从用户和更低层次接收的数据准备输出。

业务逻辑层是 实施大部分业务规则的层次(由于安全原因,将某些关键规则严格限制在数据库级,而没有暴露给应用程序级)。它提供了 PHP 类的集合,这些 PHP 类封装了系统各部分专用的规则。此外,该层包含安全框架。该框架通过提供用户验证、会话管理和请求授权等机制,在应用程序级上管理用户的访问。特别是,该 框架提供了很多公共方法,允许较高的层次基于安全权限对用户接口进行自定义。最后,该层包含可重用的通用类。应用程序使用这些类来进行数据加密、压缩、数 据类型转换等。

数据抽象逻辑层由一组通用方法构成,用于为应用程序提供与存储在数据库中的实际数据的接口。换句话说,它在数据层与系统的其余部分之间提供了一个额外的抽象级别,这使应用程序真正独立于数据库引擎。在经过大量测试后,我们选择了另一种开放源代码工具 ADODB 库 (http://php.weblogs.com/) 来提供这种功能。

数据层又称 Oracle,无疑是该系统中最重要且最复杂的部分。其安全机制将在以下的安全章节中进行解释。

图 2中描述了 Companion 应用程序的物理布局:

图 2:Companion 应用程序的物理布局

模块。每个模块均由数据结构(表和视图)和 PL/SQL 程序包的组成。结构与程序包都通过业务逻辑层中的 PHP 类与 GUI 应用程序相集成,以提供一系列主题统一的功能。模块化结构不仅使开发和组织功能的工作变得合乎逻辑且一致,而且对安全性也是一样。

模块在逻辑上是相互分离的。每个模块都拥有一套独有的安全策略,在 Web 服务器上拥有自己的装满其 PHP 应用程序文件的文件夹,并且拥有自己的 GUI 子站点和表集。每个表名以两个表示该表所属模块的字母作为前缀。文件、策略、过程以及其他存储的代码集均遵循一种命名惯例,这种惯例将它们标识为特定模块的一部分。

在某种意义上,模块是相互分离的,但每个模块都嵌入到更大的应用程序中。模块共享相同的导航元素和公用数据,并且全都重用业务逻辑层提供的类。

表 HR_Employee

c1_id     c1_emp_category      web_emp_photo     c2_emp_email
-------   ----------------     --------------    ------------
M290171   central staff        (blob)            james.milton@mcgill.ca
C179022   central staff        (blob)            doris.seagal@mcgill.ca
M109022   casual               (blob)            luke.grande@mcgill.ca
C390101   work-study           (blob)            patrick.roy@mcgill.ca
M203400   faculty based staff  (blob)            mcmaster.philip@mcgill.ca

表和字段的前缀码:

HR — 人力资源模块(每个模块的每个表都有两个字母的前缀)
C1 — 中央数据库 1*
C2 — 中央数据库 2*
WEB — Companion 数据库应用程序*

* 字段的前缀依据其源数据库确定。
由于该应用程序是 companion 数据库,
它同步并使用来自于不同来源的数据,
尤其是来自于主要中央数据库的数据。

虽然大部分模块遵守相似的安全结构和原则,但是在项目开始的时候,我们的每个模块都需要有专门针对其需求的安全方案。共同安全原则的一个例子是指导者原则。为每个模块大致分配三位指导者。指导者通常是符合条件的非技术性用户。他们在其模块内拥有完全的管理权限,并可以向其他较低安全级的管理员分配权限。不同安全原则的一个示例是 HR 模块中保密信息原则。当员工访问其保密信息时,他们的行级安全依赖于其实体 ID。但是在大多数其他模块中,他们的行级安全依赖于其工作的职位 ID。

安全性

如果我们的数据非常敏感,则通过传统的基于 cookie 的会话机制在应用程序级上限制访问就不足以保证安全性。对于具有复杂的访问规则并且预期具有庞大流量的数据库而言,在管理行级安全的同时获得良好性能的最佳方法是将大部分安全功能 — 那些用于强化数据限制的特定规则的安全功能 — 转移到数据库级。

通 过 VPD 保护数据库引擎级上的数据访问的安全而将访问控制放在了最低级别上,从而确保入侵者的查询工具(能够越过应用程序安全机制)无法获得数据。此外,它为机构 提供了更低的拥有成本:在数据服务器上一次性构建安全性,而不用将安全性构建到所有访问数据的应用程序工具中。最后,它集中了数据访问的管理和控制。

Companion 利用了 Oracle 传统的基本角色安全和用户帐户安全功能。更重要的是,它使用了两个功能强大的 VPD 工具:安全应用程序环境和 FGAC。(有关 VPD 的详细说明,参见otn.oracle.com/deploy/security/oracle9ir2/pdf/VPD9ir2twp.pdf)。使用这四个安全性概念作为框架,让我们来探讨我们的业务模型如何与 Oracle 相融合。

基本用户帐户安全。安全性和数据库体系结构是围绕一个每天从中央数据库进行更新的主要名称列表而设计的。这些名称被称为实体而非用户。

Companion 包含两个用户组,工作人员毕业生。用户组被 Oracle 的基本安全功能看作单个帐户。举例来说,这意味着 3 万个并发用户只共享两个帐户。其他用户(包括某些实体和所有的非实体)无法共享任何帐户,但它们仍然可以访问未加限制的公共数据。(注意:在本文中,用户指访问系统的人,而帐户指 Oracle 基本用户帐户。)如图 3所示,用户是公共实体和成员的子集。

图 3:用户:包含某些数据库实体和某些公共成员的子集。

那么为什么仅为所有经过验证的用户分配两个共享帐户,而不是分别为每个用户分配一个帐户?因为对许多应用程序安全性的管理,需要依据用户可能赋予这些应用程序的各种属性进行,而非依据用户所拥有的帐户进行。可完成此目标的工具将在以下关于 VPD 的章节中进行探讨。

基本角色安全。当用户通过单击某个链接访问 Companion 应用程序时,从安全性的观点来看,用户是在访问模块。每个模块都与一个 Oracle 安全角色相关联,该角色被分配或被拒绝赋予每个用户帐户。这些安全角色管理表、视图、过程和函数等的对象级安全。基本角色安全只是将广泛的用户权限(选择、插入、执行等)分配给对象,它是建立访问的第一步。特定的用户权限由 FGAC 控制。

VPD 安全应用程序环境。Companion 应用程序使用 Oracle 的安全应用程序环境,将实体 ID 分配给登录的用户。利用两个简单的标识符 — 帐户(工作人员或毕业生)和环境(实体 ID)— 即可确立大部分的访问规则,并且很容易派生出其余的规则。

这些用于用户登录会话的标识符由一个加密的安全令牌所携带,每次进行查询(即点击数据库中某项内容的超链接)时都会读取该令牌。在加密令牌的会话持续期间,用户登录(名称/口令)以及实体 ID 都存储在 Web 服务器上。登录用于建立与数据库的连接并获得对数据结构的访问权限,而实体 ID 则用于通过安全策略将数据访问权限细化到在行级。

VPD 细粒度访问控制。使用由安全应用程序环境提供的实体 ID 标识符,Oracle 安全策略能够在行级管理访问。这些 FGAC 安全策略 — 与基本角色安全一样 — 附加在对象上。与基本角色安全不同的是,FGAC 策略进一步将访问权限限制在与实体 ID 关联的属性上。附加到所有非公共数据库对象上的安全策略有助于完全剥离受限制的数据。因此,即使数据库登录名和口令被盗取,对任何受策略保护的表进行查询的企图(使用标准 SQL 工具)都将得到空记录集或有限的记录集。策略对用户的属性以及行的属性进行鉴定,并确定用户可以看到哪些行(如果有的话)。例如,只有那些住在温哥华并应邀出席西海岸地区经理宴会的实体才可以查看出席同一活动的登记者名单。另一个例子:分配到 Web 站点的编辑人员不能删除网站的页面,但该网站的作者可以这样做。

共性。我们的 160,000 个实体构成了一个变化的复杂群体。每个实体都有很多共性,如年龄、地位、财政群体、地区等等。为了便于没有经验的管理员分配访问权限,已经将许多这样的属性汇总到一系列的共性组中。有一个共性查询表每天都进行更新,并列出所有实体及其相应的共性。FGAC 也使用该表管理对那些已经分配了共性的数据行的访问。共性还用于为站点的访问者灵活地提供个性化的内容。

图 4描述了在用户调用页面时筛选数据请求的五个步骤。

图 4:筛选数据访问的五个步骤。同步

使存储在 Companion 数据库中的数据与存储在中央数据库中的数据保持同步已变成了一项挑战,因为二者都在不断地更新。我们设计了一个系统,它比实时系统慢,但是保证符合严格的数据标准。

起初,每夜在 Companion 数据库中刷新由中央数据库提供的“同步”数据(参见图 5)。这一转储过程中最重要的对象是实体表,它构成了我们内部安全性的基础。

虽然该组同步表在 Companion 应用程序的模块中被频繁使用,但它们并不由这些模块进行更新。实际上,这些更新存放在保持表中,如果有必要,由记录工作人员使用专门设计的客户服务器接口对其进行仔细的编辑。然后,更新的数据在经过编辑从而符合标准之后,被回送到中央数据库。由于中央数据库和 Companion 数据库中的所有行都有日期时间标记,管理这一流程的程序仔细地检查相同记录的更新情况(这些更新可能相互冲突)。与以前相比,准确、更新的数据能够以更大的数量、在利用更少手动操作的情况下流向中央数据库。

由于我们的数据库是 Companion 数据库,它从一些其他的大型数据库中借用字段和数据。Companion 数据库中每个字段的名称都以一个字母的代码作为前缀,该代码表示其源数据库。由于强制在每个表的每个字段名中识别数据源,因此不会漏掉进行同步的机会。

图 5:Companion 数据库的同步

学到的教训以及对开发人员的建议

Kardasz 的团队从这一正在进行的项目中所学到的主要教训如下:

  • 编码标准:拥有关于如何构造代码、命名字段和命名表的适当样式指南及格式,这对于程序员很有裨益。还有其他的规则和格式,并始终遵照这些规则和格式来使用和布置存储过程及项目。
    参见样式手册
  • 注释:Oracle 允许设计人员在每个字段和每个表上添加注释。TOAD (http://www.qsft.com/toad/) 具有产生包含这些注释的数据字典报表的功能。这样,您可以自行编写注释。我们最初并没有这样做,回过头来去做真是痛苦。您会惊奇地发现,您在很短时间内忘记了如此多的事情。
  • 线程:Oracle 多线程会话处理的性能优于并发会话。我们本应该从开始时就这样做。
  • 行级安全性:设计和编写行级安全系统更加容易和直观,这种系统将会从表和应用程序代码开始运行。对于每一行数据,都在表中的某处存有相应的安全属性。不幸的是,这种应用程序级的安全性使性能降到很低的程度,并且在应用程序编程过程中很容易有疏漏。对于性能和可伸缩性,我们建议使用 Oracle9i的精确访问控制和虚拟专用数据库技术,而不要顾及它们具有挑战性的学习曲线。
  • 团队规模和极限编程:我们在开始时实施了不太正规的“极限编程”。后来我们发现,即使在规模很小的程序员小组中(在本案例中为三人),当更清楚地确定责任、功能和专业技术时,开发工作就会更加高效地进行。我们的 DBA 指导者、网络指导者以及管理中央数据库的 Oracle 程序员所提供的支持是无价之宝。
  • 质量保证:由于缺少资源,我们没有专门针对该项目的质量保证测试人员。相反,我们依靠各种技术人员和非技术人员提供反馈信息。这种非正规“志愿者”测试人员的工作效果并不佳。很多错误和遗漏的功能在很长时间内未被发现。结果是,调试和更改过程花费了比预期长得多的时间。
  • 副本服务器:我们不仅为所有开发工作维护着一系列测试服务器(Web 服务器加上 Oracle9i服务器),而且还维护着一个每 24 小时从产品服务器刷新一次的 Oracle9i副 本服务器。副本服务器是一个快速而方便的备份。我们从副本服务器恢复文件和数据的经验非常有效。副本服务器能够在很短的时间内替换产品数据库服务器。经常 出现的情况是,测试数据库服务器上的更改在最终上载到产品服务器之前,首先转移到副本服务器上进行测试。我们还计划装备 Oracle 备用服务器,这样就在产品服务器出现严重故障时确保数据的零丢失。
  • 备份:我们仍然配备着一个传统的磁带备份系统,进行离线存储。不幸的是,备份磁带难以处理、容易出故障并且恢复速度很慢。我们喜欢一种更好的转储机制。Oracle9i数据库和 Web 服务器文件被转储到另一台服务器上的可用驱动器中。每日的数据保留一周时间。每周的数据保留一个月。
  • 资源分配:我们估计,花费在 PHP 和 PL/SQL 编程方面的总时间只占所分配时间的 22%。这么小的数字使我们进一步确信这些工具所带来的具有低成本的效益。图 6显示了时间分配的完全突破情况。

图 6:项目时间分配

我们认为,本文中所描述的开发模型的使用范围是无限的。总而言之,该模型包括了保持核心同步数据的伴随数据库、多层应用程序结构、半独立的模块式开发、为新模块提供框架的核心安全组件、全部范围的 Oracle 安全特性(特别是 FGAC)以及 PHP 编程的快速和可靠性。我们强大的中央数据库的功能、准确性和相关性得以增强,而在每次发布新的模块时,都在具有最少逻辑障碍的情况下为用户添加功能。总的说来,最令人满意的是,它为用户提供非常良好的服务并且技术支持很易于管理。


Kevin Kardasz(kevin.kardasz@mcgill.ca),麦吉尔大学信息系统开发经理,他设计了该项目并担任项目经理。Vadim Kudryavtsev(vadim.kudryavtsev@mcgill.ca),高级程序员,是他们的数据库开发和安全专家 (VPD, FGAC)。Robert Mark(robert.mark@mcgill.ca),Web 程序员,是他们的首席 PHP 专家。Mikhail Seliverstov(mikhail.seliverstov@mcgill.ca),Web 程序员,是业务分析专家和系统设计师。

php

ORACLE PHP教程–从原型到产品的最短距离

Tags: ,

一个关于 PHP/Oracle 开发模型如何在 Myers Internet 缩短应用程序生命周期的案例研究。

对 于主要由应收款业务模型驱动的公司而言,其核心的业务功能之一是输入、跟踪和记录订单。在这方面比较出色的公司可以伸缩它们的机构,并提高它们的利润,而 不会遇到基础架构的限制。当订单处理很麻烦、容易出错或不一致时,公司将因为直接的成本和降低的生产效率而蒙受经济上的损失。

在我的公司 Myers Internet,核心的业务事项围绕着建立客户基础,为 Myers 提供持续的服务并帮助它在客户问题出现时解决问题。公司正使用许多不同的系统来处理订单输入和实施周期的各个方面。这些系统既不是彼此集成的,也不具备确保每一份订单都得到正确记帐的机制。

Myers 订单跟踪系统 (MOTS)

就像其它许多机构一样,Myers 从一个小型公司成长为一个中型公司,同时在它的整个成长期间始终保留了相同的过程和系统。大多数这些过程在建立时,所有的事务处理都通过电子邮件、纸质记录和实地拜访来人工地完成。5 或 6 年前,Myers 的一个工程师利用 Allaire 的 Cold Fusion 和一个 Microsoft SQL Server 数据库组装了一个系统来跟踪订单实施,这个系统称为 MOTS (Myers 订单跟踪系统),它允许销售和帐目管理部门输入订单,然后由支持、工程、设计、信息系统和会计部门实施这些订单。虽然这个系统是向前迈进的重要的一步,但它仍然留有许多人工的步骤,并且没有和任何其它的业务系统集成在一起。

大概在同一时间,还创建了一个系统,在这个系统中客户和销售代表可以在线订购 Myers 网站的产品。这个系统可以创建新的 Web 站点,并计算提供的 Web 站点程序包的安装和重复性费用的总和。然后它发送电子邮件给各个部门,各个部门可以将订单输入到 MOTS 中,并在帐目管理系统中创建记帐信息。

体系结构障碍

这种类型的体系结构饱受几种系统问题之苦。在 Myers,较明显的问题之一包括启动订单跟踪所需的人工数据输入,以及作为这种人工过程的结果而产生的错误。另一个问题是公司中的订单输入、订单跟踪和记帐系统之间的脱节、订单丢失、信息遗漏和其导致的错误。

另一个仅偶而出现的问题是 MOTS 系统本身有内在的缺陷。由于编写 MOTS 的方式,可以输入没有部门分配信息或者丢失了部门分配信息的订单。当这种情况发生时,订单最终将在系统中丢失。当订单丢失时,准确、及时的记帐就更难实现了。

随着业务的成长,体系结构中的缺陷变得越来越明显,并且随着客户和订单数量的增加,丢失和错误输入的订单出现的频率越来越高,从而给公司收入带来了难于估量的影响。此外,人工输入的数据的数量导致了延迟和处理效率低下。

由于在实施机构内对收入的影响加大和效率降低,很明显必须要有一个替换系统来将一切联系起来,并提高效率和降低错误率。旧系统图示如下。

图 1:旧的系统体系结构

该图显示了需要人工数据输入的所有区域。由于这些系统都不是集成的,所以数据丢失或失真的可能性非常大。全局需求马上变得明显起来。

  • 订单系统需要直接和实施跟踪系统联系起来。
  • 该系统需要安全保护来防止订单在未经处理之前脱离系统。
  • 需要保持精确性,以确保准确的记帐和正确的订单实施。
  • 系统需要使内部成本最小化。所以,要达到那个目的,需要快速地创建系统,但系统必须拥有完整的功能。

虽然一个好的订单输入和跟踪系统可以帮助降低成本,但它本身并不创造收入。

深入结构

在 开始模式设计之前,需要解决一些基本的体系结构问题。第一个底层的技术需求是系统必须可配置,且无需额外的编码。本质上,这意味着需要把工作流嵌入到数据 库中,而不是用解释/处理代码来进行硬编码。第二,数据库需要包含足够的信息,以便能够表现订单输入界面的主要(和可更改)的方面以及实施处理。

在 努力解决上述问题的过程中,该系统逐渐适合于两个部分 — 订单输入和订单跟踪,并在两者之间提供了明确定义的联系。订单输入系统需要知道如何用准确的产品代码、折扣和定价条款来表示订单。订单实施系统需要知道如 何跟踪各种类型的任务、相关的作业和各个部门,以处理和记录每份订单。最后,需要定期和可预测地把订单转化成实施作业。下图显示了目前存在的新系统的结 构。

图 2:新的系统体系结构

该图显示了通向新的订单系统的所有信息路径,新的订单系统位于后端的门户管理站点。所有的初始数据输入都仅一次性完成,并且只需要每个小组在处理的各个阶段验证数据。通过引入从订单系统到帐目管理系统的自动数据传输,至关重要的数据传输的另一个主要的领域也变为自动化。

依赖 PHP

在纯技术的层面上,早期决定使用 PHP 作为主要的开发语言和 Oracle 作为系统的数据信息库,这有几个主要的原因。首先,Myers 现有的后端门户几乎完全是用 PHP 根据一个现有的 Oracle 数据库编写的,这消除了一个产生不兼容性的潜在来源。这还意味着要创建这个新的系统,Myers 可以利用自身的能力,这些能力创建了现有的后端门户。

第二,实验测试显示,与其它开发语言相比,PHP 提供了一个比较高的性能水平。因为 PHP 是作为一个动态加载的资料库驻留在 Apache 服务器内部的,所以每一次与系统连接都无需额外的启动时间。此外,PHP 优化的改善(通过 Zend 项目)意味着在代码内部执行的一般操作不会明显变慢。最后,为 PHP 编写的 OCI 接口模块是用 C 代码编译和优化的,这使得访问 Oracle 数据库非常高效。

第三,我们了解到因为 PHP 代码将其自身嵌入到了 HTML 环境中,所以对于设计人员和编程人员而言,创建协作用户接口功能代码变得更加自然。虽然最后这个特性其它的服务器端脚本语言也具备,但 Myers 发现 PHP 更不可能带来开发人员和设计人员之间的冲突。此外,PHP 的语法和提供的代码库意味着它可以做它需要做的所有事情。

最后,将所有代码嵌入到 HTML 代码中的另一个好处是,仅需要对标准文本文件进行修改控制就可以控制源代码。我们用 CVS 作为它的标准修改控制系统。因为 PHP 代码不一定要用某一种方式进行编译,所以创建系统的一次“编译”仅涉及到从信息库中检索文本源代码文件,然后把它们放到 web 服务器上。这意味着我们可以使用 CVS 中的控制机制为它的测试和生产环境发布增量的 bug 补丁,而无需创建复杂的编译系统。

设计模式来支持可重新配置性

下面的基本模式示意图显示了订单系统是如何构建的。两种主要的模式都分为原型表和事务表。无论何时当业务情况发生变化时,原型表都允许重新配置系统,而无需重新编码。事务表包含实际客户订单的订单详情和作业详情。

图 3:基本模式示意图

图 4:基本模式示意图

这 些模式示意图看起来很复杂,当然,它们的确很复杂。不过,如果把它们分开,使得只出现原型表(以 _def 结束的表),那么该体系结构的基本结构就变得很清楚了。订单由行组组成,这些行组包括详细信息、订单行或两者。订单行可以随意地创建作业,作业由一个任务 序列组成,并且包含几条详细信息。必需要为各种任务输入这些详细信息。任务出现在不同的队列中,这些队列可以由不同部门的特定用户进行访问。

为了检验系统,策略是分阶段将订单系统原型化。系统要检验的第一部分是它单独从订单原型表中创建一份清楚的订单的能力。一旦完成了最初的模式定义,订单生成器就是原形化的系统的第一个可视部分。

为 构建和配置这个系统而组成的小组除含受这个系统影响最大的各个部门的经理之外,还包括三个开发人员。开发人员的分工分别为:构建配置功能、显示功能和事务 处理功能。在整个最初的构建周期内,部门经理提供了关于界面(这些界面使用户能够输入和处理数据)类型的有价值的反馈。

利用 PHP 绘制用户界面

要原型化的初始订单是基本的 Web 站点订单,在webwiz.myersinternet.com/上提供。得到的订单是由一个开发人员用 PHP 在三天的时间内创建的。如果订单原型定义 — 依靠只在数据库和浏览器之间的一层 PHP 代码,就能够完全定义订单输入的外观和行为,那么在数据库设计中需要一定程度的折衷。为此,诸如订单行组之类的结构必须支持两个用途:(1) 在输入表单上提供可视化的区分,以使类似的产品组可以绘制在一起 (2) 从功能上对类似的商品分组,比如说打了一定折扣的商品,或一个选项列表,从中可以作出唯一的选择。

因为 PHP 是开发语言,所以原型组建相当快速,从而可以快速地完成模式所需的修改并且为表单生成器重新编码(一前一后)。此外,因为模式是考虑了绘制的用户界面而设计的,所以当在原型构建过程中出现新的可视化需求时,可以容易地进行模式修改和改编。生成的表单外观与下图相似:

图 5:订单生成

创建一个功能完全的系统

在 提供订单之后,需要使它变得功能完全。首先,系统需要保存在订单中输入的用于事务处理的订单数据。第二,填写订单的人需要能够根据正在进行中的订单数据来 填写订单。第三,对于在提交时需要付款的订单,一个支付过程是必需的。最后,提交的订单需要创建必要的实施作业和它们的任务依赖性。

PHP 开发提供了快速周转时间,允许在两周的时间内,从外观和功能上将系统的整个订单输入部分原型化。这使得对系统体系结构和它的实现可以作出快速反馈和反应。快速周转意味着问题能够在系统在每个人的脑海里仍记忆犹新的时候得到解决。此外,因为在 UI 和后端(如中间层服务器)之间没有额外的代码层,所以创建原型的过程需要的开发步骤和开发人员更少。

一旦能够从事务上创建作为订单的结果的作业,订单实施周期的各个方面就能够得到创建。作业实施过程主要集中在两个 UI 屏幕上:任务分配和任务处理。任务分配页面包括查看队列中未分配的任务(用户能够访问这些任务)以及显示分配给用户的任务(这些任务需要处理)。

开 发周期中,对使整个系统保持一致非常关键的一部分是搜索可以提取到代码库中的常用功能。存在几种情况,在这些情况下特定的子功能和数据节点需要在多个页面 上使用。无论何时当开发人员发现他们自己在一个接口页面上执行的一系列步骤实质上和他们在先前的一个接口页面上所执行的步骤相同时,就应该评估将这种功能 提取到 PHP 代码库中的好处。

任务处理页面需要显示来自订单的相关详细信息和作业的详细信息、以及由先前的用户完成的任务和必须由当前用户完成的任务。这个页面作为一个初始的原型被快速组装起来,但在它真正变得对将要每天使用它的用户有用之前,需要经过若干次演进。

任务处理页面上的最复杂的操作与实现关闭任务的所有数据库步骤有关:

  1. 当一项任务关闭时,需要打开依赖于当前任务的强制任务。
  2. 此外,当一项任务关闭并且它是作业的最后一个需要打开的任务时,需要关闭该作业。
  3. 最后,如果一个特定订单的所有作业都关闭了,则需要关闭该订单。

虽然这种逻辑可能已经放在 PHP 代码中了,但因为 Perl 脚本将用于一些自动任务处理,所以决定以一种通用的形式来执行这些操作。对于任务关闭过程,所有的操作都只基于数据库,因此用 PL/SQL 来编写这个过程是合理的。这允许需要关闭任务的任意编程语言调用一个一致的过程来执行所需的所有数据库操作。

自 动任务系统是用自动任务定义表作为配置和自动任务运行表作为事务表为各个自动任务而创建的。因为自动任务需要做诸如调用先前编写的 CGI 脚本等事情,所以我们确定 Perl 能够实现所需的所有技术细节。可快速提供的 Perl 模块的巨大资料库允许各种类型的操作,从而提供了所需的全部灵活性。系统本身包括一个服务器进程,该进程驻留在一个每分钟唤醒一次的系统上,以搜寻待处理 的自动任务。如果找到了任意的待处理自动任务,就利用相关的任务详细信息和自动任务配置参数来执行它们。自动任务成功或失败都将被记录到数据库中。当自动 任务成功时,调用必要的 PL/SQL 过程来关闭任务。

将原型组装在一起

一旦系统拥有了所有主要部分的功能原型,就可以和所需的所有订单类型以及所有当前的商务实施周期配置在一起。订单原型包括新客户订单、现有客户订单和特殊订单。新客户订单一般由销售代表来输入新客户的详细信息,并且通常要求预付安装费用。现有客户订单涉及到从 Web 站点升级到完全和部分取消,再到服务和记帐修改的全部范围。特殊订单一般涉及到仅可由相对高级的人员订购的产品。

随 着配置得到创建和系统开始成形,PHP 的快速开发时间的优势再次证明了它们的价值。当这种需求(任务处理页面不仅需要显示当前作业的详细信息,还需要显示作为同一订单一部分的所有其它作业的详 细信息)出现时,在仅仅几天的开发和测试之后就增加了那种功能。当需要一个新的自动任务,以使一个作业保持在休眠模式,直到一个特定的日期或直到一定的天 数过去之后时,开发、测试和部署仅在一周内就完成了。

在系统配置开始之后变得明显的另一个系统优势是,任务依赖性配置以一种易于查看和调整的方式清楚地显示了当前的业务过程,特别是那些跨部门的业务过程。将数据库依赖性信息与一个来自 AT&T Research Graphviz 程序包(参见www.graphviz.org)的名称为 “dot” 的程序包结合意味着实施周期可以根据动态的配置数据进行图形化显示。下图是安装一个 Web 站点程序包所需的作业实施周期的一个简单的示例图。

图 6:作业实施周期

除事务处理页面之外,系统收集的各种量度允许生成更多的业务过程报表。在业务需求出现和变化时,可以创建和修改基于订购的产品的收入报表和关于订单周转的实施报表。这些报表可以用 PHP 通过标准的 HTML 输出来创建,或者用 Perl 来编写,以按需要创建 Excel 电子数据表。利用 Oracle 的分析和分组功能创建概要视图实现了报表的一致性以及为数据库管理员提供了一种容易的方法来执行优化。

项目时间表

系统开发从 2003 年 5 月开始。到 2003 年 6 月,主要的系统组件全部完成,系统最后的和完整的配置开始。到 2003 年 7 月底,系统全部完成配置,内部培训开始。自 2003 年 8 月以来,系统一直处于生产模式,并且已经为大约 2,000 个客户处理了 4,000 多份订单,以及 6,000 多个实施作业和 20,000 个已完成的任务。自从系统创建以来,用户能够创建新的订单和作业原型,而不需开发人员的介入。此外,现在将配置该系统来充当一个客户故障单系统,且只需新的配置,无需新的编码。

一个灵活的体系结构与快速的开发周转的结合使这个系统成为了一个成功的系统。虽然这个系统可以用几种不同的方式来创建,但我们的选择意味着可以用非常高的灵活性和非常低的开发成本来快速地创建这个系统。

下表显示了创建每个组件和使它们进入生产设置所需的开发时间(以开发人员工时为单位)。此外,它还在适当的地方指出了创建被新订单系统所取代的那个系统所花费的开发时间。

组件 新系统开发时间 旧系统
Web 站点销售订单 32 个开发人员工时
7 个业务配置工时
200 个开发人员工时
升级订单 35 个业务配置工时 NA
作业分配表单 12 个开发人员工时 40 个开发人员工时
作业处理表单 72 个开发人员工时
15 个业务配置工时
240 个开发人员工时
作业会话日志表单 12 个开发人员工时 NA
每小时记帐跟踪表单 8 个开发人员工时 24 个开发人员工时
生产效率报表 48 个开发人员工时 NA
委托报表 6 个开发人员工时 NA
收入报表 40 个开发人员工时 NA
销售报表 24 个开发人员工时 NA

吸取的教训

从这个系统的体系结构和实施中吸取的主要教训是快速周转在这几个方面 — 创建功能原型、创建 UI 配置和 UI 代码之间的紧密集成,以及保持这个系统尽可能多的部分是可配置的而不是需要编码的 — 中的重要性。本文清楚地演示了在 PHP 开发模型的简单性和系统中的灵活的原型配置之间的时间上的最短距离,以及最初的概念、功能原型和生产系统之间的相关内容。


John Neil(JNeil@myersinternet.com) 是 Myers Internet 的首席技术架构师,Myers Internet 是一家技术提供商,它向房屋抵押贷款和房地产行业提供基于 Web 的技术。他是所有产品开发的技术设计师。Neil 从 3.0 版起就开始结合使用 PHP 和 Oracle。他一般在与运行在 Solaris 上的 Oracle 连接的 Linux 环境中使用 PHP。此外,他撰写了目前的 Brainbench PHP4 认证考试中的高级问题。自 2000 年起,Myers Internet 在所有新的开发计划中使用了 PHP,并继续承诺在可预见的将来用它作为主要的编程平台。

网站建设

php

ORACLE PHP教程–您了解 PHP 吗?

Tags: ,

Rasmus Lerdorf  PHP 的创始人讲述了他对 PHP 现象、如何形成和推动了这种语言的发展、以及 PHP 的发展方向的看法

PHP 无处不在。在其 2004 年 2 月的 Web 服务器调查中,Netcraft [www.netcraft.com] 调查了 47,173,415 个域,并发现 15,205,474 个已经安装了 PHP。这大约为 Web 上所有域的 32%,并且这种增长势头没有减缓的迹象。

[phpstats.png]

PHP 的初期

我在大约 10 年前开始开发 PHP。那时在名词“开放源代码”出现且 GPL 和自由软件广为人知之前很久。正如许多已经变得流行的开放源代码项目一样,动机决不是哲学上的或者甚至是自我陶醉。它纯粹是因为需要一种工具来解决现实中与 Web 相关的问题。在 1994 年,当提到 Web 开发工具时,选择相当有限。我发现当我自己在用 C 或 Perl 为 Web 站点编写动态组件时,从一个问题到另一个问题的代码重叠现象十分显著。出于性能的考虑,我越来越远离 Perl,而倾向于 C,因为必须将 Perl 作为一个单独的 CGI 运行的 fork+exec 开销限制性太强了。

PHP 最早的未发布版本主要是常用 C 函数的一个 C 资料库,我编写了这些 C 函数,以便能够容易地从一个开放源代码项目到另一个开放源代码项目进行重用。我有一个简单的状态机驱动的分析器,它从 HTML 文件中挑出标记,然后调用我编写的后端 C 函数。这些代码最初是作为一个称为个人主页工具 (Personal Home Page Tools) 的程序包公开发布的,这个程序包中的每个工具都是关于如何使用系统来解决关于个人主页的常见问题的一个例子。在之后的某段时间,我从中分出了一部分工具并把它称为 FI,代表表单解释程序 (Form Interpreter)。FI 幕后的想法是,当您接收到一个表单提交的结果时,您需要做一些常见的事情,而它能够处理所有这些事情。一些早期的例子:

<!--getenv HTTP_USER_AGENT-->
<!--ifsubstr $exec_result Mozilla-->
Hey, you are using Netscape!<p>
<!--endif-->

<!--sql database select * from table where user='$username'-->

<!--ifless $numentries 1-->
Sorry, that record does not exist<p>
<!--endif exit-->

Welcome <!--$user-->!<p>
You have <!--$index:0--> credits left in your account.<p>

<!--include /text/footer.html-->

我的 FI 的分析器非常糟糕,这促使我试着去编写一个更好的分析器。我抛弃了<!-- cmd -->语法,而改用<? cmd >, 重新将 Personal Home Page Tools 的某些部分和这个新的 FI 工具结合起来,并在 1995 年底将它作为一个名称为 PHP/FI (这个名称有点半开玩笑性质地模仿了 TCP/IP)的程序包对外发布。PHP/FI 在接下来的几年里正好和 Web 一起得到了发展。在 1997 年,两个正使用 PHP/FI 的以色列人 Zeev Suraski 和 Andi Gutmans 问我是否有兴趣使用一个新的分析引擎,他们将为下一个版本的 PHP 编写这个引擎。我召集了其它一些一直在为 PHP/FI 提供补丁和代码的人,我们共同协作在 1998 年中期发布了 PHP 第 3 版。这可能是 PHP 发展期间最关键的时刻。如果这个项目一直是一个人在努力,那么当时它可能已经失败了,如果一群刚刚组合在一起的陌生人弄不清楚如何朝着一个共同的目标去努 力,那么这个项目也可能很容易就失败了。无论怎样,我们设法解决了我们以自我为中心的问题和其它的个人问题,这个项目成长了起来。为这个项目做出贡献的人 的数量在稳定地增长,现在我们打算在 2004 年上半年的某个时候推出 PHP 5.0 版本。

编程语言中的“丑小鸭”

关于 PHP 的流行观点可以分为两个极端。语言纯化论者往往不喜欢许多有点随意的特性实施和这些年来出现的一些前后不一致的地方。同时,实际问题的解决者往往喜欢 PHP 似乎已经读懂您的意图,并表现出它就是理想的 Web 问题解决工具。

在使纯化论者抓狂的事情中有:函数名称不区分大小写但变量名称区分大小写;内置函数的名称前后不一致;没有强制 PHP 开发人员使用任何真正的结构,从而使得很容易写出凌乱的代码。实际上我不得不同意这些批评意见,但我至少可以试着解释一下我们如何以及为什么到了这种状态。

首先,关于函数名区分大小写的问题:这可以回溯到最早的 PHP 版本。在 Web 的早期(XHTML 之前很久),所有的 HTML 标记标签都是大写的是很常见的。但因为这些标签是不区分大小写的,所以人们在这上面并不是非常一致。我希望人们对待具体的 PHP 标签基本就像和对待其它的标记标签一样,这意味着 PHP 的标签也将是不区分大小写的。当 PHP 变得更加高级并且拥有了如变量之类的特性时,使这些新的特性区分大小写并没有什么害处,因为它没有破坏对已有的 PHP 页面的向后兼容性。回过头去,突然处理最初的简单标签(这些标签实质上只是函数调用)时,区分大小写将破坏那些页面,并使它们在更新的 PHP 版本中不可用。无论如何,人们不应该拥有只是名称大小写不同的函数。尽管如此,回想起来,当相对少的人在使用 PHP 时,尽早地打破向后兼容性是个好主意;但在那时,没有人预料到 PHP 的惊人成长。

至于函数命名本身,我往往是从我熟悉的其它语言和 API 借鉴一些想法。这意味着 PHP 拥有诸如strlen()substr()之类的函数,它们如果写成str_len()sub_str(),那看起来将有点可笑。我增加了如stripslashes()之类的函数,这些函数由于长度的原因常常被写为 StripSlashes(),以使其更易于读懂。同时,我模仿了底层的数据库 API,诸如函数msql_connect()— miniSQL 是第一个被 PHP 支持的数据库 — 它们可使用下划线命名。熟悉这些不同来源的人将非常熟悉 PHP 中的命名。当 PHP 作为 Web 服务器和您想要挂在 Web 服务器上的所有不同后端工具之间的一个接口时,它并不是这样一种独立的语言。因此,今天当人们看到 PHP 作为一种独立的语言,而没有考虑其前后关系的时候,它可能看起来有点前后不一致。

关于缺少强制的结构,我能说的是,我绝对痛恨使我限制在解决问题的某种特定方法上的编程框架。这不意味着我不相信结构和框架,但我的确相信人们能够提出他们自己的、与他们的环境相称的方法。在本文稍后我论述各种 PHP 项目的可能体系结构时,将更多地涉及这个问题。

所有这些归结为 PHP 从来不打算去赢得任何“选美比赛”。它的目的不是要引入任何新的革命性的编程范例。它的目的是解决单个问题:Web 问题。这种问题可能会相当难看,有时您需要一种难看的工具来解决难看的问题。实际上,虽然一种漂亮的工具也可能解决问题,但常常一种难看的 PHP 解决方案能够更快速地得到实施,并且使用更少的资源。这大概总结了这些年来 PHP 的固执的“功能优于形式”的方法。

给设计师的建议

PHP 的最流行的部署模式是将它直接和预先分支的多进程 Apache 1.3.x Web 服务器连接起来。和使用 Java 不同的是,不存在独立的过程(如 JVM)。PHP 类似于如 Perl 和 Python 之类的脚本语言,在这些脚本语言中,直接分析和执行脚本。

没 有中央控制过程是一个特色,同时也是给许多人带来极大挫折的原因。PHP 不共享任何体系结构 — 其中每条请求都完全不同并且可以同其它任何请求分开 — 使这种语言本身具有无限的平行可伸缩性。PHP 鼓励您把可伸缩性问题放到需要它的层次上去。如果您需要共享的数据存储,那么就使用一个支持复制和能够扩展到您需要的层次上的数据库。如果您需要负载均衡 请求或将特定的请求分配到特定的服务器上,那么就使用一个支持这种功能的前端负载均衡器。通过消除中央控制过程,PHP 避免了成为系统中的瓶颈。这是使 PHP 区分于人们通常称之为应用服务器的工具的判定特性。


[oarch1.png] 用于高端 PHP
部署的一个非常常见的体系结构

在上图中,一个或更多的负载均衡器将进入的请求分配在任意数量的 Web 服务器上。考虑到数据存储,您可能在每个 Web 服务器上部署一个只读的数据库拷贝(如果数据集足够小,允许您那么做的话),或者您可能创建一个单独的数据库服务器树来处理各种类型的请求。

增加结构

PHP 超过其它许多针对解决 Web 问题的工具的巨大优势之一是,其它的工具往往把这种非常具体的目标问题的解决和从结构上控制用户解决问题的方式的要求联系在一起。PHP 不强制使用任何这种结构,相反选择专注于使问题的各个单独的功能方面尽可能地易于使用。例如,PHP 提供目的性非常强的函数来与后端数据库通信。这些函数是各个数据库专用的,并且不牺牲任何性能来获取和其它后端数据库的统一性或一致性。在文件布局方面,也没有规定的方法来构建 PHP 应用程序的结构。

PHP 不强制使用结构的事实并不意味着您不应该以一种井然有序和结构化的方式来构建 PHP 应用程序。当人们问我将如何着手构建一个大型 PHP 应用程序的结构时,我喜欢向他们展示下面这种方法。

+--------------------------------+
|     HTML TEMPLATES             |
|     $DOC_ROOT/*.php            |
+--------------------------------+
|     TEMPLATE HELPERS           |
|     $DOC_ROOT/*.inc            |
+--------------------------------+
|     BUSINESS LOGIC             |
|     /usr/local/php/*.inc       |
+--------------------------------+
|     C/C++ CORE CODE            |
|     /usr/local/lib/php/*.so    |
+--------------------------------+

这个分四个层次的方法解决了一些问题。首先,它将一个典型项目中的内容沿着职责的线索分开。Web 前端开发人员从顶层进行工作,后端工程师从底层进行工作。他们在模板助手层有一点交叠。它还分开了包含 HTML 的任何文件,将它们放到 document_root 中,而将不包含 HTML 的任何文件放到 document_root 外。

顶层的模板层一般包含非常少的 PHP — 只是简单的函数调用和偶然的包含。可能一个循环。这些文件通常用一个 HTML 编辑工具来进行编辑。第二层,即模板助手,是定义商务逻辑和布局之间的接口的地方。这一层可能有一些方便的函数(如start_table()、 show_user_record())和其它任何可重用的组件,这些可重用组件使得模板制作者的工作更加容易。

商务逻辑层完全不包含任何 HTML。这是实施诸如 SQL 查询和任何其它的 PHP 用户空间商务逻辑之类的东西的地方。您可以期望看到一个诸如get_user_record()之类的函数在这一层得到实施。这个函数将获取一个 ID,执行相应的 SQL 查询,然后返回包含结果的一个联合数组。然后,该层中的一个函数获取该数组,并为它包装一些 HTML,使它看起来好一点。

最后的 C/C++ 层是您安放一个项目所需的任何定制的后端代码的地方。许多人在这一层上没有任何东西,但如果您有一个专用的 C 或 C++ 资料库,您就可以在这里编写一个 PHP 扩展来和资料库接口。有时当一个用用户空间 PHP 编写的商务逻辑函数太慢时,也将使用这一层。

招聘和培训 PHP 开发人员

PHP 不是一种新的语言。它没有引进任何新的概念。这意味着培训已经了解 C、C++、Perl 或者甚至 Java 中的任意一种语言的编程人员来编写 PHP 代码相当容易。当我找 PHP 开发人员来做一个项目的时候,我倾向于找拥有 C 或 C++ 技能的人,我的想法是招聘经验丰富的编程人员要比招聘必须对 PHP 非常了解的人员容易得多。如果他们能够掌握这些语言,PHP 对他们来说将是小菜一碟。当然,如果他们有两方面的经验,那就更好了。

随意部署 PHP

使用合适的工具进行工作。我遇到过一些完全采用了 PHP 的公司,它们绝对是将 PHP 部署到了每个地方,但 PHP 从未打算成为一种适合于所有问题的通用语言。它主要适合于作为 Web 的前端脚本语言。取决于 Web 站点的通信量,它还可以用来执行大批量的后端工作。但在某些地方,您将需要用一种强类型的编译语言(如 C 或 C++)来编写您的部分代码,以获得最优的性能。

PHP 将去向何方?

人们常常问我 PHP 将去向何方。这是一个很难回答的问题,因为 PHP 主要是一个应运而生的开放源代码项目,它的发展是为了满足其社区的需要。在 PHP5 中,OO 功能和与 XML 的集成得到了大大的改进。我们集成了一个称为 SQL-Lite 的有趣工具,这个工具直接为文件提供了一个 SQL 接口,而无需服务器。很明显,它替代不了实际的数据库,但使用它无疑是比试图编写您自己的纯文本文件处理例程好得多的一种方法。它为您提供了一个 SQL 接口的事实意味着到一个实际数据库的移植(如果在任何时候需要这么做的话)变得更容易。

在 PHP5 中的这些变化虽然很重大,但却是渐进的。我们并不打算用这个版本把 PHP 的世界搞个天翻地覆。为 PHP4 编写的脚本中,基本不作修改就可在 PHP5 下工作。最大的变化是在 PHP5 中对对象的处理不同了。当您在 PHP5 中新建一个对象时,现在将默认地获得到该对象的一个引用,您可以到处传递它,而无需像您在 PHP4 中必须做的那样显式地声明您想通过引用来传递对象。在 PHP5 中,如果您想实际获得对象的一个拷贝,那么您需要“克隆”它。

更长远地看,有人在研究 Parrot 引擎的使用。Parrot 是作为 Perl6 后端的引擎编写的,但它实际上是一个与语言无关的通用脚本引擎。如果各种脚本语言可以全部在单个后端引擎上达成一致,然后这个引擎可以用作公共扩展和更好的语言交互的基础,那么这将非常有趣。

此外还有其它一些人在研究通过 JSR 223 的 Java 连通性,认为 Java 是可以成为脚本语言的单个通用后端。

不管 PHP 可能拥有怎样的一个未来,有一点是不变的。我们将继续和许多人似乎沉迷于其中的复杂性作斗争。最复杂的解决方案很少是正确的解决方案。我们为解决 Web 问题而提供的专一、直接的方法正是使 PHP 从一开始就与众不同的地方,当我们周围的其它解决方案似乎变得越来越庞大、越来越复杂时,我们正努力地简化和优化 PHP 和它解决 Web 问题的方法。

Rasmus Lerdorf(rasmus@lerdorf.com) 于 1968 年出生于格陵兰沿海的 Disko Island 岛上的 Godhavn/Qeqertarsuaq。他自 1985 起开始涉足基于 UNIX 的解决方案。他以在 1995 年开始了 PHP 项目,因 mod_info Apache 模块而闻名,他也因 mSQL 1.x 中的挑战 ANSI92 SQL 的 LIMIT 子句而受到责难,mSQL 1.x 现在(至少从概念上)发展成了 MySQL 和 PostgreSQL。

他往往否认自己是一个程序员,更喜欢将他看作是一个暴躁的解决问题的老手。如果解决方案需要一些代码而他找不到其它任何人来编写代码,那么他只能很不情愿地作出让步自己来编写代码。他现在是加州 Sunnyvale 的 Yahoo! Inc. 的基础架构工程师。

php

2009/11/12

为 Linux 和 Windows 安装 PHP 和 Oracle 10g Instant Client

Tags: , ,

Oracle 10gInstant Client(免费下载)是PHP 与远程 Oracle 数据库连接的最简单方式,它只需要安装三个库。

PHP 访问 Oracle 的当前 API 所使用的 Instant Client 库称作 OCI8.(此 C 接口的名称最早是在 Oracle8 中引入的。)PHPOracle 8 函数可以直接调用 Oracle 8.1.7、9.x 或 10.x,或者也可以为了方便起见,使用可选的抽象类,如PEAR MDB2ADOdb

Instant Client 也可以使用老版本的 PHP“oracle”扩展,但它调用不赞成使用的 Oracle API。PHP 界或 Oracle 建议不要使用此扩展进行新的开发。

要 在 Apache 上将 Instant Client 与 PHP 4 或 连用,请遵循以下步骤。需要一个现有的 Oracle 数据库;Instant Client 不提供 Oracle 数据库。通常情况下,此数据库将位于其他计算机上。如果数据库位于本地,则 Oracle 组件一般早已可用,从而不需要 Instant Client。

软件需求:

软件 附注
Oracle Instant Client 下载“Instant Client Package – Basic”。在 Linux 上,还应下载“Instant Client Package – SDK”。
Apache HTTPD Server PHP 界仍推荐 Apache 1.3
PHP — PHP 超文本处理器 4.3 版或更高版本


在 Windows 上启用 PHP OCI8 扩展
Instant Client 二进制文件是 PHP 的 Windows 预构建二进制文件的补充。

  1. 下载 PHP 二进制压缩文件(不是安装程序版本)和 Apache。按照 PHP 手册中的Windows 系统上的安装安装它们。OTN 的开放源代码开发人员中心包含有用背景资料的链接,如“在 Windows 2000/XP 上安装 Oracle、PHP 和 Apache”,它介绍了如何安装传统、完整的 Oracle 10g版本(Instant Client 不需要此版本)。

    继续操作之前检查 PHP 是否正常运行。此阶段未启用 Oracle 支持。

  2. 从 OTN 的Instant Client 页面下载用于 Windows 的 Instant Client Basic 程序包。此压缩文件的大小大约为 30MB。
  3. 创建一个子目录(例如,c:\instantclient10_1),然后从压缩文件中复制以下库:
    • oraociei10.dll
    • orannzsbb10.dll
    • oci.dll

    这三个文件的总大小大约为 80MB。

    要使用 PHP 老版本的“oracle”扩展(在 php.ini 中使用“extension=php_oracle.dll”启用),则复制 ociw32.dll 而非 oci.dll。

  4. 编辑此环境,将 c:\instantclient10_1 添加到 PATH 中(位于其他 Oracle 目录之前)。

    例如,在 Windows 2000 上,依次单击“开始”->“设置”->“控制面板”->“系统”->“高级”->“环境变量”,编辑系统变量列表中的 PATH。

    如 果使用了 tnsnames.ora 文件定义 Oracle Net 服务名称,则将 tnsnames.ora 复制到 c:\instantclient10_1,并将用户环境变量 TNS_ADMIN 设置为 c:\instantclient10_1。也可以在用户环境变量 LOCAL 中定义默认的服务名称。

    设置必要的 Oracle 全球化语言环境变量,如 NLS_LANG。如果没有设置,则使用默认的本地环境。有关更多详细信息,请参见Oracle PHP 应用程序全球化概述

    无需设置不必要的 Oracle 变量,如 ORACLE_HOME 和 ORACLE_SID。

  5. 编辑 php.ini,并不要将 OCI8 扩展设为注释:

    将 extension_dir 指令设置为完整的 PHP 扩展 DLL 路径。在 PHP 4 中,DLL 位于 PHP 软件的“extensions”子目录中。在 PHP 5 中,它们位于“ext”中。

    extension=php_oci8.dll
  6. 重新启动 Apache。

要检查是否配置了扩展,请在 web 服务器可以读取的地方创建一个简单的 PHP 脚本。

使用“http://”URL 将此脚本加载到浏览器中。浏览器页面应包含一个显示“OCI8 Support enabled”的“oci8”部分。

在 Linux 上启用 PHP OCI8 扩展

要在 Linux 上添加 Oracle 连接,需要重新编译 PHP。

开放源代码开发人员中心包含有用背景资料的链接,如在 Linux 上安装 Oracle、PHP 和 Apache,它介绍了如何安装传统、完整的 Oracle 10g版本(Instant Client 不需要此版本)。

  1. 下载并安装 Apache。例如,在您的主目录中安装它:
    cd apache_1.3.31
    ./configure --enable-module=so --prefix=$HOME/apache --with-port=8888
    make
    make install

    编辑 $HOME/apache/conf/httpd.conf 并添加:

    AddType application/x-httpd-php .php
    AddType application/x-httpd-php-source .phps
  2. 下载并解压缩 PHP。

  3. 从 OTN 上的Instant Client 页面下载 Basic 和 SDK Instant Client 程序包。这两个 RPM 的总大小大约为 30MB。
  4. 以 root 用户的身份安装 RPM。
    rpm -Uvh oracle-instantclient-basic-10.1.0.3-1.i386.rpm
    rpm -Uvh oracle-instantclient-devel-10.1.0.3-1.i386.rpm

    第一个 RPM 将 Oracle 库置于 /usr/lib/oracle/10.1.0.3/client/lib 中,第二个 RPM 在 /usr/include/oracle/10.1.0.3/client 中创建头 (header)。

  5. 备份此补丁,然后将它应用于 PHP 的 ext/oci8/config.m4。该补丁的行号是基于 PHP 4.3.9 的。如果已修复了PHP 错误 31084(很有可能已在 PHP 4.3.11 和 5.0.4 中修复),则不需要此补丁。如果使用的是 PHP 4.3.9 或 4.3.10,则可以将此补丁保存到一个文件中(如 php_oci8ic_buildpatch),然后使用以下命令安装它:
    patch -u config.m4 php_oci8ic_buildpatch

    此补丁创建一个新的 PHP 配置参数:--with-oci8-instant-client[=DIR].在 Linux 上,默认情况下,它使用从 RPM 中安装的最新版本的 Instant Client。可以指定 Oracle 库所在的目录来使用其他版本。无论在哪种情况下,都将自动使用正确的 SDK 头。

    新参数与现有的--with-oci8参数互斥。

    例如:在非 Linux 平台上,将 Instant Client 程序包解压缩到您所选择的目录中。--with-oci8-instant-client参数将需要明确指定此目录;例如,--with-oci8-instant-client=/home/instantclient10_1。应将 Instant Client SDK 解压缩到与基本程序包相同的目录中,以便修改后的配置脚本可以找到头文件的子目录。

  6. 在顶层 PHP 目录中重新构建“configure”脚本。
    cd php-4.3.9
    rm -rf autom4te.cache config.cache
    ./buildconf --force
  7. 使用新选项运行 configure。此示例使用安装在主目录中的 Apache。
    ./configure \
    --with-oci8-instant-client \
    --prefix=$HOME/php --with-apxs=$HOME/apache/bin/apxs \
    --enable-sigchild --with-config-file-path=$HOME/apache/conf

  8. 重建 PHP。
    make
    make install

  9. 将 PHP 配置复制到--with-config-file-path指定的位置

    cp php.ini-recommended $HOME/apache/conf/php.ini
  10. 将 LD_LIBRARY_PATH 设置为 /usr/lib/oracle/10.1.0.3/client/lib 并重新启动 Apache。如果使用了 tnsnames.ora 文件定义 Oracle Net 服务名称,则将 TNS_ADMIN 设置为包含此文件的目录。

    启动 Apache 之前应设置所有 Oracle 环境变量。以下脚本可以帮助完成此操作:

    #!/bin/sh
    
    APACHEHOME=/home/apache
    
    LD_LIBRARY_PATH=/usr/lib/oracle/10.1.0.3/client/lib:${LD_LIBRARY_PATH}
    TNS_ADMIN=/home
    export LD_LIBRARY_PATH TNS_ADMIN
    
    echo Starting Apache
    $APACHEHOME/apachectl start

要确认是否配置了扩展,请在 web 服务器可以读取的地方创建一个简单的 PHP 脚本。

使用类似“http://localhost:8888/<path>/phpinfo.php”的 URL 将此脚本加载到浏览器中。浏览器页面应包含一个显示“OCI8 Support enabled”的“oci8”部分。

连接到 Oracle

将连接信息传递给 PHP 有多种方法。第一个示例使用 Oracle 10g的 Easy Connect语法连接到 在mymachine上运行的MYDB数据库服务中的HR模式。不需要 tnsnames.ora 或其他 Oracle Network 文件:

有关 Easy Connect 的语法,请参见 Oracle 的使用 Easy Connect 命名方法文档。

或者,如果 /home/tnsnames.ora 包含:

且 TNS_ADMIN 环境变量设置为 /home(在启动 Apache 之前),则连接字符串可以为:

$c = OCILogon('hr', 'hr_password', 'MYDB');

如果环境变量 LOCAL(在 Windows 上)或 TWO_TASK (在 Linux 上)设置为 MYDB,则可以使用以下代码生成与 MYDB 连接:使用 Oracle

$c = OCILogon('hr', 'hr_password');

当基本连接可以使用时,试着运行一个简单的脚本 testoci.php。根据您的数据库修改该连接的详细信息并在浏览器中加载它。此示例列出了用户 HR 拥有的所有表:故障诊断

Windows 帮助

如果 phpinfo.php 脚本没有生成显示“OCI8 Support enabled”的“oci8”部分,则确认在 php.ini 中没有将“extension=php_oci8.dll”设为注释。

如 果 PATH 设置错误,或找不到 Oracle 库,则启动 Apache 将显示警告:“在指定的路径中找不到动态链接库 OCI.dll。”phpinfo() 页面的 Environment 部分将显示 PATH 的值以及 PHP 实际使用的 Oracle 变量。

如果 php.ini 的 extension_dir 指令不正确,则在启动 Apache 将显示警告:“PHP 启动:无法加载动态库 php_oci8.dll。”

Linux 帮助

仔细检查是否正确修复了 config.m4。如果“configure”失败,则检查 config.log 文件。还原 config.m4,删除缓存文件,运行./buildconf --force and configure,验证问题是否与所做的更改相关。

确保“configure”上的时间戳是当前的。删除所有缓存文件,并在必要时重建它。

在启动 Apache 的 shell 中设置所有必要的 Oracle 环境变量。

希望本文对您能有所帮助。您可以在 OTNInstant ClientPHP论坛上发表问题和建议。

结论

<?php 

$conn = OCILogon("hr", "hr_password", '//mymachine.mydomain:port/MYDB);

$query = 'select table_name from user_tables';

$stid = OCIParse($conn, $query);
OCIExecute($stid, OCI_DEFAULT);
while ($succ = OCIFetchInto($stid, $row)) {
foreach ($row as $item) {
echo $item." ";
    }
echo "<br>\n";
 }

OCILogoff($conn);

 ?>

Oracle PHP 故障诊断常见问题解答包含有关连接 Oracle 的有用信息。

可以从Instant Client 页面下载 Oracle 的 SQL*Plus命令行工具来帮助解决环境问题和连接问题。另请参见SQL*Plus Instant Client 版本说明

检查 SQL*Plus 使用的环境是否与 phpinfo.php 显示的环境相同。

MYDB =
(DESCRIPTION=
(ADDRESS = (PROTOCOL = TCP)(HOST = mymachine.mydomain)(PORT = 1521))
(CONNECT_DATA=
(SERVER = DEDICATED)
(SERVICE_NAME = MYDB)
    )
  )
$c = OCILogon('hr', 'hr_password', '//mymachine.mydomain/MYDB');

Oracle 连接信息被传递给 OCILogon() 来创建连接。与 Instant Client 关联的工具通常“远离”任何数据库服务器,因此必须将 Oracle Net 连接标识符与用户名和口令一起使用。对于已建立的 Oracle 数据库,连接信息有可能是众所周知的。对于新系统,此信息由 Oracle 安装程序在安装数据库时提供。此安装程序应配置了 Oracle Net 和创建了一个服务名称。

在新数据库中,可能需要将演示模式(如 HR 用户)解除锁定并向其提供口令。也可通过在 SQL*Plus 中以 SYSTEM 用户身份连接并执行以下语句来完成此操作:

ALTER USER用户名IDENTIFIED BY新口令ACCOUNT UNLOCK;
<?php
phpinfo();
?>
<?php
phpinfo();
?>

网站建设 

产业新闻

2009/11/10

Oracle并购SUN应放松对MySQL控制

Tags: , ,

据美国《商业周刊》报道,在甲骨文并购SUN的交易看似顺利的时候,欧盟很可能将要求甲骨文放弃对SUN开源软件MySQL的控制权。SUN在2008年以10亿美元收购了MySQL。

4月,甲骨文宣布将以74亿美元并购SUN。这次并购是甲骨文成为一家全套服务提供商计划的其中一部分,甲骨文希望未来能够提供公司运营其计算机系统所需的一切,包括芯片、运行系统、数据以及商业项目等。不过,现在,这项庞大的计划需要些小的变动。

为了减轻来自欧盟委员会的压力,甲骨文可能被迫放弃这次交易中重要环节的控制:Sun公司的开源数据软件MySQL。欧盟希望甲骨文确保此次并购不会阻碍数据库市场的竞争。

艰难的处境

很多互联网领域的著名公司都使用免费的MySQL,例如Twitter, Facebook以及谷歌和雅虎。一位不愿透露姓名的证券分析师认为,“甲骨文正处于一种坚难的处境,因为10年时间里很可能出现甲骨文的最大竞争者。”

据媒体报道,自从9月欧盟宣布介入并进行并购调查之后,欧盟正准备拿出正式的证据反对甲骨文对SUN的并购。欧盟委员会可能会要求甲骨文放弃对MySQL新版本的控制,来维持整个市场的竞争。甲骨文拒绝对此进行评价。

由于MySQL基于开源许可协议,让用户免费修改编码。谷歌、亚马逊以及一个叫做Drizzle的软件项目组都和SUN的工作人员一起修改数据库或者共同开发一款商业产品而无需经过SUN的允许。例如,亚马逊在10月27日宣布,消费者可以在网上向亚马逊租用MySQL数据,按其储存数据量付费。谷歌也开发了其自身版本的MySQL。

欧盟的立场

一位熟悉欧盟行为模式的行业管理者表示,“补救方法就在这里。”他认为,控制新版本软件对交易的批准以及MySQL的使用毫无益处。MySQL对运行大型网络在速度和适用性上具有优势。

不管欧盟是否要求甲骨文进行上述妥协,甲骨文的竞争对手都认为此次并购最终将会达成。一位甲骨文的竞争对手表示,“欧盟正在做他们经常做的事情,那就是宣扬煽动性的论调。”分析师认为,欧盟只是希望将自己置于提倡科技开放而非私有的位置。

Gartner数据显示,甲骨文在数据库软件领域拥有领先位置,2008年甲骨文拥有49%市场份额,当年全球数据库软件市场容量为188亿美元。IBM市场份额为22%,微软为16.6%,而MySQL占据0.5%的销售份额,也为SUN贡献一小部分收入。在6月30日结束的2009年财政年度里,MySQL和SUN中间件业务创造了3.13亿美元销售额,SUN公司销售总额为114.4亿美元。

MySQL对于甲骨文意义重大并不是因为其带来的收入。这个数据库让那些不愿使用甲骨文强大的也更加昂贵的11g数据库产品的互联网公司最终成为甲骨文的客户。据SUN估计有1200万个MySQL版本正在使用。ISI Group投资公司技术研究总裁Bill Whyman表示,“对于甲骨文这样级别的公司,MySQL带来的财务价值不值得一提。”

欧盟委员会关心的是,如果甲骨文并购SUN,甲骨文将拥有这个领先的开源数据库。9月3日欧盟第一次提出对SUN并购案的担心,欧盟表示希望确保甲骨文 承诺继续发展MySQL。10月欧盟反垄断委员尼莉·克洛斯(Neelie Kroes)与甲骨文总裁塞弗拉·卡茨(Safra Catz)进行了会面。

Java是甲骨文商业应用Fusion重要的一部分。欧盟反对这项交易是因为甲骨文在过去5年里花300亿美元收购了近60家公司。而美国公平交易部门已经同意了这项并购交易。

甲骨文不能等到欧盟宣布反对并购交易后再作出反应。1月19日前欧盟会做出最终决定,是否同意此次交易。

然而这项交易的延长影响了对SUN的价值判断。SUN截至9月27日为止的第一季度财政收入下降了25%至22.4亿美元。SUN股价当天下降了 1.6%,每股8.10美元,而甲骨文4月提出收购申请时SUN股价为9.5美元。甲骨文总裁拉里·埃里森(Larry Ellison)9月表示,交易延长的1个月期间,SUN损失了1亿美元。

10月20日,SUN表示计划在未来一年内裁员3000人,占全部员工的10%。拉里·埃里森表示“并购拖延的时间越长,SUN损失的钱越多。”埃里森不同意分拆MySQL。

这不是欧盟第一次试图阻止美国公司的并购。2001年通用电气(GE)提出以420亿美元并购霍尼韦尔(Honeywell)公司,欧盟裁定并购交易将阻碍航空业的竞争。

此前,埃里森已经成功说服了交易阻碍者,他态度坚决,希望完成交易。竞购与公平交易部门的长时间斗争后,2005年甲骨文成功收购了商业软件制造商PeopleSoft。2008年甲骨文又收购了BEA公司,并成功地压制了BEA股东提出的高昂价格。

php

2009/10/22

Oracle的上下文开发,自己定义的Context开发

Tags:

不知道有没有用过userevn和sys_context这两个方法

userenv在我们查看当前用户的session的字符集的时候是经常用到的。

比如
select userenv(’language’) from dual;

sys_context的用户和这个userenv类似,是一个保持了和session有关的session级别的上下文。这个上下文是一个session里的都可以访问到的地方,所以如果我们于需要在这里放入一些session级别的自己的信息,就可以使用这个 sys_context的上下文了。

那么我们如何把自己的信息放到session里了。Oracle提供了一个DBMS_session的强大的有关session操作的包。文档可见http://download.oracle.com/docs/cd/E11882_01/appdev.112/e10577/d_sessio.htm#i1010942

我们可以使用里面的set_context的方法

引用:

DBMS_SESSION.SET_CONTEXT (
namespace VARCHAR2,
attribute VARCHAR2,
value VARCHAR2,
username VARCHAR2,
client_id VARCHAR2 );

这里的几个参数的含义
namespace 这个context的命名空间
attribute 属性值,即为key值
value 值
username 用户名 默认null
client_id 指定的clientid 默认null这里1-3函数是一定要的,后面两个是可选值,

我们赶快到sqlplus试试把

引用:

SQL> exec dbms_session.set_context(”, ‘name’, ‘liuyong’);
BEGIN dbms_session.set_context(”, ‘name’, ‘liuyong’); END;

*
ERROR at line 1:
ORA-28106: input value for argument #1 is not valid
ORA-06512: at “SYS.DBMS_SESSION”, line 78
ORA-06512: at line 1

这里出错了。主要是我们的用法不对,在Oracle的文档里已经描述了,这里的context只能是建立create来使用,那么我们应该如何来用了

首先建立package来包装这个context
create or replace package my_context_p as
procedure put(key varchar2, value varchar2);
end;
/
create or replace package body my_context_p as
procedure put(key varchar2, value varchar2) as
begin
dbms_session.set_context(’my_context‘,key,value);
end;
end;
/

然后建立context
create or replace context my_context using my_context_p ACCESSED GLOBALLY;
注意这里的context的名字要和package里set_context的时候一样。 否则后面调用会告诉你ORA-01031: insufficient privileges。

调用
SQL> exec my_context_p.put(’test’, ‘inthirties’);

检查
SQL> exec dbms_output.put_line(sys_context(’my_context’,'test’));
inthirties

这样我们就实现了我们需要的功功能呀。