手机浏览 RSS 2.0 订阅 膘叔的简单人生 , 腾讯云RDS购买 | 超便宜的Vultr , 注册 | 登陆
浏览模式: 标准 | 列表2009年05月8日的文章

在 Oracle 中完成 PHP5 对象的持久

在数据库驱动的 Web 应用程序中实现 PHP5 对象的持久,朝着完全面向对象的 Web 应用程序开发迈进重要的一步。

许多开发人员都认同 PHP5 的面向对象实现的简洁和高效,并且同样被世界上最著名的数据库 — Oracle — 的强大功能和强健性所吸引。然而,从逻辑上更进一步 — 将 PHP 对象存储在数据库中以进行 Web 应用程序开发 — 可能将非常困难。

在这篇方法文档中,您将了解到如何通过一个(在 OracleDatabaseObject 程序包(下载)中提供的)对象持久层来存储和检索作为 Oracle 数据库中的对象的 PHP5 对象。利用这一技巧,任何 Oracle 开发人员都能够在他们的 Web 应用程序中最终实现完全面向对象 — 从而专注于应用程序逻辑,而不是麻烦的 SQL 和编码。

什么是对象持久层?

在解释如何在 Oracle 数据库中实现 PHP 对象的持久的机制之前,大家需要先了解支持它们的原理:对象持久层的概念。

世界上最受欢迎的脚本语言 PHP 只是在最近才为我们提供了面向对象的一个完整和合理的实现(在 PHP5 中)。这为希望效仿面向对象方法的严格性和灵活性的开发人员展现了一个激动人心的前景,面向对象方法在“传统的”编程环境中已经存在了很多年。然而,由于 关系数据库在 Web 的发展中扮演了如此关键的角色,希望充分利用这种方法的开发人员将面临一个的两难境地:对象技术明显不适合关系数据库技术。数据库需要表,而不是类;表需 要行,而不是对象。教科书上把这种现象称为“阻抗失谐”。

早于对象技术出现的关系数据库可以对作为简单类型集中在一起的简单数据进行适当地建模;例如,Person 表可以包含姓名 (varchar2)、身高 (INT) 和出生日期 (DATE) 列。相比而言,对象技术支持复杂数据类型的概念。例如,Person 对象可以包含 name 和 height 的基本类型,但也可以包含类型为 Person 的 partner、类型为 Job 的 job,每一种类型都有它们自己的复杂的数据结构。

因此,想收获面向对象方法的好处并存储复杂数据类型(PHP 对象)的 Web 开发人员只有两种选择:模拟或使用对象数据库,或者更可能的是,以专门的方式简单地存储数据对象。后一种做法(虽然很流行)是完全不可接受的,因为它抛弃 了面向对象的简洁性、可扩展性和可重用性,而这些是面向对象的主要好处。

幸运的是,Oracle 提供了对象类型、PL/SQL 和 REF。这些特性结合在一起形成了 Oracle 自己的面向对象实现;虽然这种实现从技术上看是不完整的,但它的确使开发人员能够容易地将对象存储为数据库对象,而不是以固定类型存储在表的行中。

然而,只是因为 PHP 和 Oracle “使用”对象技术并不意味着可以将 PHP 对象存储为 Oracle 对象(反之亦然)。因此,需要对象持久层服务来使 PHP 开发人员能够简单、高效地在 Oracle 数据库中存储和检索对象。

可以通过许多方式来执行这一操作,但所有方法都必须满足同样的需求:

  1. 必须通过简单、高效的服务来存储和检索对象。
  2. 存储在数据库中的对象必须能够包含对其他对象(复杂数据类型)的引用。
  3. 每一个对象必须有一个唯一的标识符。
  4. 当保存对象时,还必须能够保存所有其他相关的对象状态。
  5. 当检索对象时,还必须能够检索所有其他相关的对象状态。

OracleDatabaseObject 程序包是我对这些需求的实现。它反映了现有的最简洁的方法:使用抽象类和数据类型来对通用 的 Oracle 数据库对象建模。该方法允许任意类简单地继承这个通用类,从而继承持久所需的所有服务 — 实质上将其自身封装在一个可以和 Oracle 数据库接口的层中(参见下图)。

 

大小: 6.64 K
尺寸: 401 x 236
浏览: 1426 次
点击打开新窗口浏览全图

对象持久层将动态提供在数据库中实现对象持久所需的一切。无论何时当调用了方法 save() 来将 PHP5 对象存储到数据库中时,持久层的第一个任务是测试在此之前是否存储过这种类型的对象。如果没有,则将检查对象的状态(属性类型和值)。考虑以下对象:

 

大小: 5.07 K
尺寸: 362 x 207
浏览: 1462 次
点击打开新窗口浏览全图

如果在 Person1 上调用了 save() 方法,并且这是要保存在数据库中的第一个类型为 Person 的对象,那么将检查状态和它们的类型 — 在这种情况下,属性 Name、Age 和 Partner 分别有 PHP5 类型 String、Integer 和 Person。然后,要使这个对象能够持久,持久层将创建一个 Oracle 对象类型(也称为 Person),它的结构基于它对应的 PHP5 对象的状态。该层将在 Oracle 中复制 PHP5 类型 — Name (PHP5 string) 将变为 Oracle VARCHAR2;Age (PHP5 integer) 将变为 Oracle Number;PHP5 Partner(类型为 Person)将变为类型为 Person 的 Oracle REF。(关于这些转换的概述,请查看表 1。)

 

PHP5 类型 Oracle 对象类型
Numeric Number
String VARCHAR2
Array VARCHAR2 *
Object Object
Associated Object REF to Object

* 在撰写本文时,出于性能的目的,数组作为序列化的字符串以 VARCHAR2 类型存储在 Oracle 中。
然而,在未来,它们可能由 Oracle VARRAY 来实现。

一旦创建了 Oracle 对象类型结构来模拟它对应的 PHP5 类,属性值就将被保存到新创建的对象类型的表中。按照上述的第 4 点需求,当保存一个对象及其关联的对象时(如以上示例所示),还必须保存所有的关联关系。只需通过确保在检查和保存当前对象的内部状态时调用相关对象的 save() 方法来实现这一需求。

了解这些实现细节对于判断持久层具体在做些什么非常有用,但这种方法的强大之处在于您不需要了解这些。作为开发人员,您需要做的全部是利用交互对象来开发 和实现类 — 完全可以忽略所涉及的编程工作。利用这一层的服务,您可以在数据库中存储和检索对象,而永远无需设置表或处理 SQL,从而使您只需考虑应用程序自身的逻辑。

现在让我们看一个例子。

何时使对象持久

利用类和对象进行开发的实践是现在 PHP5 开发人员的日常工作。但并非所有这些对象都将在 Oracle 数据库中持久。因此,第一步是要决定是否需要为指定类的对象添加持久服务。

随着开发人员倾向于对 Web 全部使用面向对象,我们将共同创建并能够访问成千上万个类 — 但我们必须记住它们并非全部需要持久。相反,您必须考虑您的类代表了什么抽象概念,以及将如何在应用程序中使用类的对象。作为一条通用的基本原则:

如果在脚本完成之后必须存储对象的状态,并且在脚本运行时必须能够访问和/或更新对象的状态,那么就必须持久对象。

例如,大部分面向对象的 Web 开发人员会在应用程序中使用表单对象 — 但很少需要将它们存储在数据库中。例如,您不需要保存表单对象(包括其元素、目标、名称等),因为它在被创建之后就不会改变;所以无需在脚本中设置对象属 性并将它们存储在数据库中。相比而言,如果您在开发一个电子日历应用程序,那么您可能想使用条目对象作为每一天的对象,这种对象将在添加和编辑它们时改 变。当添加或修改了一个条目时,对象的状态也将改变;当一个条目变化时,您将把更新的版本保存在数据库中。

因此让我们利用这种应用程序的示例来浏览一下使用在下载(在撰写本文时提供了测试版本,计划在 2005 年底之前推出产品版本)中提供的 OracleDatabaseObject 程序包 (OracleDatabaseObject.class.php) 将持久服务添加到一个简单的 TestEntry 类 (TestEntry.class.php) 中的过程。

将 Oracle 数据库持久添加到类中

原始的 TestEntry 类如下所示:

PHP代码
  1. class TestEntry   
  2. {  
  3.   
  4. public $title;  
  5. public $body;  
  6. public $author;  
  7. public $dayName;  
  8.   
  9.   
  10. function display()  
  11. {  
  12.   
  13. Echo $this->title ."<BR>" .$this->body ."<BR>" .$this->author;  
  14. }  
  15.   
  16. }  

传统上将这个类的对象存储在 Oracle 数据库中将非常复杂。这个过程将包含创建一个与类的内部状态类似的表,然后利用关系 SQL 编写一系列的过程函数来插入/更新/删除类的各个属性。这是一个非常复杂、低效的任务 — 当对象变得更复杂时(可能包含数组或其他对象),它将变得几乎不可能完成。

OracleDatabaseObject 类使得您在其他方式下将手动完成的一切 — 例如存储类的内部状态(包括所有属性的类型)和创建 Oracle 对象类型 — 都变为动态完成,从而使得这个过程变得非常简单。最终的结果是您永远无需担心创建或修改 Oracle 数据库中的表或类型;您所需做的一切是专注于应用程序的逻辑。

因此,下面将创建 TestEntry 对象,并在数据库中存储和检索它们,让您的类继承 OracleDatabaseObject 类,该类已经内置了您所需的所有服务(参见表 2)。

 

表 2
方法 参数 目的 用途示例
对象
save() 将当前对象保存为数据库中的对象 $anObject->save()
load()

利用当前的 objectName 从数据库中加载对象

$anObject->load()
addDatabaseUser ($username ) 包含用户名的字符串,该字符串将用于存储类对象的数据库
为存储类的对象的 Oracle 服务器设置用户名、口令和数据库名称,使得能够利用各自的用户名和口令在各个数据库中存储类。
ClassName::setUserName(‘scott’)
addDatabasePass ($password) 包含口令的字符串,该字符串将用于存储类对象的数据库
ClassName::setPassword(‘tiger’)
 


addDatabaseName ($databasename ) 包含数据库名称的字符串,该字符串将用于存储类对象的数据库 ClassName::setDatabase(‘orcl’)

将这些服务提供给 TestEntry 类的过程为:

PHP代码
  1. <?php  
  2. include_once 'DatabaseObject.class.php';   
  3. include_once 'OracleDatabaseObject.class.php';   
  4.   
  5. class TestEntry extends OracleDatabaseObject   
  6. {  
  7.   
  8.     public $title;  
  9.     public $body;  
  10.     public $author;  
  11.     public $dayName;  
  12.   
  13.  function display()  
  14.  {  
  15.       echo $this->title ."<BR>" .$this->body ."<BR>" .$this->author;  
  16.  }  
  17.   
  18. }  
下面您将通过使用 include_once 关键字(如第 1 行和第 2 行所示)来包含对您所需的类的访问权(OracleDatabaseObject 是一个继承 DatabaseObject 的抽象类)。然后可以使用 PHP5 继承关键字 extends(第 4 行)来继承 OracleDatabaseObject 类。继承的工作方式是获取父类的所有特性(方法)和属性(属性)并将它们提供给子类。这是一个简单但强大的想法(如下图所示):最初的 TestEntry 类继承 OracleDatabaseObject 类,产生一个结合了两者的特性的新的 TestEntry 类。

 

大小: 6.76 K
尺寸: 302 x 268
浏览: 1412 次
点击打开新窗口浏览全图

(左边显示的继承将产生右边显示的 Entry 类的一个新的版本。)

接下来,您将创建 TestEntry 类的对象,并使用服务层在 Oracle 数据库中保存和检索对象。

创建和使用 Oracle 持久对象

现在您的类拥有了所需的服务,将对象保存至数据库的过程将非常简单,如下所示 (ExampleA.php):

PHP代码
  1. <?php  
  2.  include_once "DatabaseObject.class.php";   
  3.  include_once "OracleDatabaseObject.class.php";   
  4.  include_once "TestEntry.class.php";   
  5.   
  6.  TestEntry::addDatabaseUser('scott');   
  7.  TestEntry::addDatabasePass('tiger');   
  8.  TestEntry::addDatabaseName('orcl');   
  9.   
  10.  $anEntry = new TestEntry();   
  11.  $anEntry->title = 'Meeting with boss - 9am';   
  12.  $anEntry->body = 'Meeting with boss scheduled to discuss raise';   
  13.  $anEntry->author = 'Joe Bloggs';   
  14.  $anEntry->dayName = 'Tuesday';   
  15.  $anEntry->objectName = 'Tuesday';   
  16.  $anEntry->display();   
  17.  $anEntry->save();   

在继承 OracleDatabaseObject 之后,您必须通过使用表 2 中所示的服务来将用户名、口令和数据库名称与类绑定(如第 5-7 行所示的方式)。这个操作将使得能够通过单独的用户名、口令和数据库名称来获取各个类(从而获取该类的所有对象) — 顺便说一句,这将为您的对象提供非常细粒化的安全性。

不过,您可能希望为对象提供特定的名称,例如 Tuesday。这种方式将使您能够通过调用 load() 方法从数据库中快速容易地检索对象(如下面的 ExampleB.php 所示)。

您应当注意的最后但可能最重要的一件事是第 14 行中的方法 save()。这个方法也继承了 OracleDatabaseObject 类,它只是将对象保存到数据库中。

加载 Oracle 对象

您已经看到了在 Oracle 中保存对象有多容易。您甚至不需要接触数据库!自然,下一步是检索它 — 这将一样简单。

使用 ExampleB.php 示例,您将把数据库对象加载到一个新的 PHP5 对象中(如下所示):

PHP代码
  1. <?php  
  2. include_once "DatabaseObject.class.php";   
  3. include_once "OracleDatabaseObject.class.php";   
  4. include_once "TestEntry.class.php";   
  5.   
  6. TestEntry::addDatabaseUser('scott');   
  7. TestEntry::addDatabasePass('tiger');   
  8. TestEntry::addDatabaseName('orcl');   
  9.   
  10. $anEntry = new TestEntry();   
  11. $anEntry->objectName = "Tuesday";   
  12. $anEntry->load();   
  13. $anEntry->display();   

这将产生和 ExampleA 相同的输出:只需从存储在数据库中的对象中重新加载对象的状态,无需任何复杂的调用和查询,并且只需通过调用 display() 方法来显示对象的状态。

SQL代码
  1. $sql = "SELECT * FROM: 
  2.         ( 
  3. SELECT 
  4. r.*, ROWNUM as row_number  
  5. FROM: 
  6. ( $select ) r 
  7.         ) 
  8. WHERE row_number BETWEEN :start_row AND :end_row";  

关于更广泛的演示,请查看提供下载的完整的示例电子日历应用程序。它包含了类的源代码以及 UML 图,从而使您能够更彻底地研究这一技巧。

使用 Oracle/PHP5 对象

将对象存储在数据库中不只是一个小花招;相反,它是满足“完全在面向对象范例中进行开发”的需求的最后一步。它使您能够无缝地使用在传统的大型面向对象编 程中流行的所有技巧 — 使用诸如业务对象、UML 和契约式设计等方法 — 来进行分析、设计、开发和测试。这是一种激进的想法,但 Web 开发领域正朝这个方向努力。


Barry McKay 是一个居住在苏格兰 Glasgow 的兼职 Web 开发人员和面向对象方法的拥护者。他还是 International PHP Magazine 的撰稿人。