test
|
结合使用 PHP 5 和 Oracle XML DB 将面向对象的 PHP 和 Oracle XML DB 相结合 作为一种编程语言,PHP 具有许多优势,包括易于使用、开发时间短和高性能。通过一种全新的对象模型,PHP 5 利用了面向对象编程的能力和灵活性。 该 PHP 的第五版把更多完全面向对象的功能引进到了该语言中(包括接口;抽象类;私有、公有和保护访问修饰符;以及静态成员和方法)。PHP 5 现在还通过引用而不是通过值来传递对象。 PHP 提供了几个内置的类库,您可以轻松地对其进行扩展,从而将重用的承诺马上变成现实。本文介绍了如何扩展来自 PHP 的文档对象模型 (DOM) 库的类,以创建与 Oracle XML DB 交互的子类。本文分析了 PHP 类的一些面向对象的特性,并讨论了对示例子类的一些修改,这些修改试验了从 PHP 中使用 Oracle XML DB 的各种方式。 利用 Oracle XML DB 演示 PHP 5 的继承特性 在 PHP 5 中,一个类将通过使用 extends 关键字来继承一个现有的基类的功能。正如在 Java 和其他面向对象的编程语言中一样,您可以在 PHP 5 中扩展用户定义的和预先定义的 PHP 类。DOM 函数库是在 PHP 5 中自动提供的预先定义的 PHP 类之一 它提供了用于创建、填充以及用其他方式处理 XML DOM 结构(文档节点、元素等)的功能;它是尽可能遵循 DOM Level 2 的一个面向对象的类库(PHP 5 DOM 库替代了 PHP 4 的 DOM XML 扩展)。 扩展内置的类 在列表 1 中,MyDomDocument.php 中的 MyDomDocument 类代码演示了您如何能够利用一个公有方法来扩展预先定义的 DomDocument 类,该方法旨在简化创建 XML 文档的过程。 代码列表 1: MyDomDocument 类扩展 DomDocument
<?php
//File:MyDomDocument.php
class MyDomDocument extends DomDocument{
public function __construct($ver = "1.0", $encode = "UTF-8")
{
parent::__construct($ver, $encode);
}
public function addElement($node, $elemName, $elemVal="")
{
if(!$child = $this->createElement($elemName, $elemVal))
throw new Exception('Could not create a new node ');
if(!$child = $node->appendChild($child))
throw new Exception('Could not append a new node ');
return $child;
}
}
?>
在 MyDomDocument.php 代码中,您在覆盖的构造函数内部显式调用了父构造函数。与其他面向对象的语言不同,PHP 在子类调用其构造函数时不隐式调用父构造函数,这意味着如果您在一个子类中定义了构造函数,那么您必须从新的子构造函数内部显式调用父类的构造函数。同样要注意,正如 Java 的情况一样,只有在父类中被定义为 public 或 protected 的成员和方法是可以从子类内部来访问的。 无论子类是否覆盖了公有或保护的父方法或成员,您仍然可以在子类内部通过使用 parent:: 前缀来访问它。 对客户端代码而言,默认情况下,可以使用子类的一个实例来访问在父类中被定义为 public 的任何方法或成员,除非该子类覆盖了该父方法或成员。(在子类中不能覆盖在父类中被定义为 final 的方法。) 现在,让我们来使用 MyDomDocument 类。在列表 2 中显示的 dom.php 脚本创建了 MyDomDocument 的一个实例,并填充了一个 XML DOM 结构。如果您在您的 Web 服务器的 /htdocs(或其他相应的目录)中加载了 dom.php 并在 Web 浏览器中打开了该文件,那么您会在查看源文件时看到以下输出:
<?xml version="1.0" encoding="UTF-8" ?> <EMPLOYEE id="1"> <EMPNO>212</EMPNO> <ENAME>John Jamison</ENAME> <TITLE>Programmer</TITLE> </EMPLOYEE>
代码列表 2: dom.php 创建了一个 XML 文档
<?php
//File:dom.php
require_once 'MyDomDocument.php';
try {
//first, create an instance of MyDomDocument
$dom = new MyDomDocument();
//now, create the root of the XML document
$root = $dom->addElement($dom, 'EMPLOYEE');
//you can always add an attribute to the node if necessary
$root->setAttribute('id', '1');
//once the root element is created, add nested nodes as needed
$emplno = $dom->addElement($root, 'EMPNO', '212');
$ename = $dom->addElement($root, 'ENAME', 'John Jamison');
$title = $dom->addElement($root, 'TITLE', 'Programmer');
//finally, output the XML document
echo $dom->saveXML();
}
catch(Exception $e) {
print $e->getMessage();
}
?>
与这个简单的示例及其在测试脚本中的硬编码数据不同,实际的脚本最可能将从文件或数据库中检索数据(如列表 3 中的 domXML.php 中所示)。 代码列表 3: domXML.php 从数据库中获取元素信息
<?php
//File:domXML.php
require_once 'MyDomDocument.php';
require_once 'dbConn5.php';
require_once 'ScottCred.php';
$i = 0;
try {
$dom = new MyDomDocument();
$root = $dom->addElement($dom, 'EMPLOYEES');
$db = new dbConn5($user, $pswd, $conn);
$sql="SELECT EMPNO, ENAME, JOB FROM EMP";
$db->query($sql);
while ($row = $db->fetch()) {
$empl = $dom->addElement($root, 'EMPLOYEE');
$empl->setAttribute('id', ++$i);
$empno = $dom->addElement($empl, 'EMPNO', oci_result($row,'EMPNO'));
$ename = $dom->addElement($empl, 'ENAME', oci_result($row,'ENAME'));
$title = $dom->addElement($empl, 'TITLE', oci_result($row,'JOB'));
}
print $dom->saveXML();
}
catch(Exception $e) {
print $e->getMessage();
}
?>
注意列表 3 中的 domXML.php 包含了两个其他的脚本 ScottCred.php(列表 4)脚本和 dbConn5.php(列表 5)脚本。ScottCred.php 包含了 Oracle 数据库的登录证书和连接字符串信息,dbConn5.php 包含一个符合 PHP 5 规范的 dbConn5 连接字符串类。如果您要运行本文的示例代码,那么您将希望针对您的特定环境修改 ScottCred.php 文件的特定信息($user、$pswd 和连接字符串信息),但您可以将 dbConn5.php 保留原样。(关于设置您的环境来运行本文中的示例代码的信息,请参阅“示例的基本设置”部分。) 代码列表 4: ScottCred.php 包含登录和连接信息
<?php
//File:ScottCred.php
$user="scott";
$pswd="tiger";
$conn="(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))
)
(CONNECT_DATA=(SID=orcl)(SERVER=DEDICATED))
)";
?>
代码列表 5: dbConn5 连接类
<?php
//File:dbConn5.php
class dbConn5 {
private $user;
private $pswd;
private $db;
private $conn;
private $query;
const CONNECTION_ERROR = 1;
const SQLEXECUTION_ERROR = 2;
function __construct($user, $pswd, $db)
{
$this->user = $user;
$this->pswd = $pswd;
$this->db = $db;
$this->ConnToDb();
}
function ConnToDb()
{
if(!$this->conn = oci_connect($this->user, $this->pswd, $this->db))
{
$err = oci_error();
throw new Exception('Could not establish ...: ' . $err['message'], self::CONNECTION_ERROR);
}
}
function query($sql)
{
if(!$this->query = oci_parse($this->conn, $sql)) {
$err = oci_error($this->conn);
throw new Exception('Failed to execute SQL ...: ' . $err['message'], self::SQLEXECUTION_ERROR);
}
else if(!oci_execute($this->query)) {
$err = oci_error($this->query);
throw new Exception('Failed to execute SQL ...: ' . $err['message'], self::SQLEXECUTION_ERROR);
}
return true;
}
function fetch()
{
if(oci_fetch($this->query)){
return $this->query;
}
else {
return false;
}
}
}
public function fetchAll()
{
if(oci_fetch_all($this->query, $results)){
return $results;
}
else {
return false;
}
}
?>
列表 3 中的 domXML.php 脚本还扩展了 MyDomDocument 类,但首先它将从数据库中选择关系数据。 或者,您可以使用 SQL XML 函数来实现相同的一般结果,在这种情况下,您将希望使用列表 6 中的 domTaggedXML.php 脚本来生成 XML 数据。 代码列表 6: domTaggedXML.php domXML.php 的 SQL XML 的替代方式
<?php
//File:domTaggedXML.php
require_once 'MyDomDocument.php';
require_once 'dbConn5.php';
require_once 'ScottCred.php';
try {
$dom = new MyDomDocument();
$db = new dbConn5($user, $pswd, $conn);
$sql="SELECT (XMLELEMENT(\"EMPLOYEE\", XMLATTRIBUTES(rownum AS \"id\"), XMLELEMENT(\"EMPNO\",
empno), XMLELEMENT(\"ENAME\", ename)
[[[remove this line break to use code]]], XMLELEMENT(\"TITLE\", job))).getClobVal()
as MYXML FROM EMP";
$db->query($sql);
$rows=$db->fetchAll();
foreach ($rows['MYXML'] as $row) {
$rslt=$rslt.$row;
}
$dom->loadXML("<EMPLOYEES>$rslt</EMPLOYEES>");
print $dom->saveXML();
}
catch(Exception $e) {
print $e->getMessage();
}
?>
不过,在任意一种情况下 使用 domXML.php 或 domTaggedXML.php 结果是相同的。假定您与拥有默认的 SCOTT 数据库模式(它包含 EMP 表)的 Oracle 数据库连接在一起,那么 domXML.php 或 domTaggedXML.php 将生成这种 XML 输出:
<?xml version="1.0" encoding="UTF-8" ?> <EMPLOYEES> <EMPLOYEE id="1"> <EMPNO>7369</EMPNO> <ENAME>SMITH</ENAME> <TITLE>CLERK</TITLE> </EMPLOYEE> ... </EMPLOYEES>
这种 XML 输出是临时的 它仅暂时在 Web 浏览器的上下文中存在。让我们更进一步,利用 Oracle XML DB 将 XML 内容(存储在 MyDomDocument 类对象的内部 XML 树中)保存到 Oracle 数据库中。 使用数据库来保存 XML 继续进行该示例,您可以更进一步,将存储在 MyDomDocument 例程的内部 XML 树中的 XML 内容保存到 Oracle 数据库中。例如,您可能想将 employee XML 元素从 XML 树中提取出来,并将它们作为各个单独的 employee XML 文档保存在数据库中。 为此,您必须首先创建一个数据库模式来在其中存储 employee XML 文档,并利用 XML Schema 定义来定义一种 XMLType 用于存储 employee XML 文档。(请参见“示例的基本设置”部分,了解关于模式设置的信息。)一旦您设置了 XML 存储,您就可以在 MyDomDocument 类中添加一个公有方法,该方法将为您把基于 XML Schema 的数据插入到 Oracle 数据库中(如列表 7 所示)。 代码列表 7: insertSchemaBasedXML() 函数
public function insertSchemaBasedXML($tagName, $db, $table, $schema)
{
try {
$items = parent::getElementsByTagName($tagName);
for ($i = 0; $i < $items->length; $i++) {
$nodeXML = parent::saveXML($items->item($i)) ."\n";
$db->query("INSERT INTO " .$table ." VALUES( XMLType('" .$nodeXML ."').createSchemaBasedXML('"
."$schema" . "'))");
}
}
catch (Exception $e) {
throw $e;
}
print "Data have been submitted";
}
正如列表 7 中的代码所示,您将使用 parent 关键字来调用父类的方法。(或者,您可以根据名称来显式指定父类 例如, DomDocument 而不是使用 parent 关键字。) 列表 7 演示了多个 PHP 对象的交互 您可以通过把对一个对象的引用作为参数传递给另一个对象的方法来动态地将两个对象关联起来。在这个示例中, insertSchemaBasedXML() 方法将 dbConn5 的一个实例作为参数,然后调用 query() 方法来对数据库执行 INSERT 操作。 通过准备好 insertSchemaBasedXML() 方法,您不需要组装个别的 INSERT 语句来将基于模式的 XML 数据插入到 Oracle 数据库中: insertSchemaBasedXML() 将为您执行这一操作。 正如列表 7 中的 insertSchemaBasedXML() 方法签名所示,您必须指定以下参数:
因而,将生成的 employee XML 文档插入数据库的工作被简化为以下操作:
$dom->insertSchemaBasedXML( 'EMPLOYEE', $db2,'employee', 'http://localhost:8080/public/emp.xsd');
列表 8 中的 domXML_db.php 脚本将执行该函数调用。 代码列表 8: domXML_db.php 客户端
<?php
//File:domXML_db.php
require_once 'MyDomDocument.php';
require_once 'dbConn5.php';
require_once 'ScottCred.php';
try {
$dom = new MyDomDocument();
$root = $dom->addElement($dom, 'EMPLOYEES');
$db = new dbConn5($user, $pswd, $conn);
$sql="SELECT EMPNO, ENAME, JOB FROM EMP";
$db->query($sql);
while ($row = $db->fetch()) {
$empl = $dom->addElement($root, 'EMPLOYEE');
$emplno = $dom->addElement($empl, 'EMPNO', oci_result($row,'EMPNO'));
$ename = $dom->addElement($empl, 'ENAME', oci_result($row,'ENAME'));
$title = $dom->addElement($empl, 'TITLE', oci_result($row,'JOB'));
}
$db2 = new dbConn5('usr', 'pswd', $conn);
$dom->insertSchemaBasedXML('EMPLOYEE', $db2,'employee',
'http://localhost:8080/public/emp.xsd');
}
catch(Exception $e) {
print $e->getMessage();
}
?>
在运行 domXML_db.php 之后,您可以从命令行中使用 SQL*Plus 来确认结果(在作为 PHPUSR 登录之后),如下所示:
SELECT COUNT(*) FROM employee;
COUNT(*)
----------------
14
结果显示了 14 行被成功地从 SCOTT 演示模式下的 emp 表中转移到了 PHPUSR 数据库模式下的 employee XMLType 表中。如果您试图多次运行 domXML_db.php 脚本,那么您将获得以下错误消息:
Failed to execute SQL query:ORA-00001:unique constraint (PHPUSR.EMPNO_IS_PRIMARYKEY) violated
insertSchemaBasedXML() 方法使用了一条 try-catch 语句来检测和响应在您将 XML 内容插入到数据库中的 XMLType 表时可能出现的错误。虽然这可能看起来很奇怪 类定义一般不包含 try-catch 例程 但它在这个例子中是有意义的,因为交互是在两个类之间进行的:try 块中的代码将调用 dbConn5 类中可能引发异常的 query() 方法。如果 query() 方法不能对数据库执行查询,那么它将引发异常,因此如果您在 insertSchemaBasedXML() 方法中遗漏了 try-catch 语句,那么在 query() 方法中引发的异常将只在调用脚本中被捕获。 这意味着 insertSchemaBasedXML() 方法在 query() 将一行(一个 XML 文档)插入到指定的 XMLType 表中失败时将不会终止。相反,它将尝试插入下面的行,直到它到达生成的 XML 文档的列表的末尾。 为了防止这种行为,您只需将调用 query() 方法的代码包装在 try 块中并定义一个 catch 块,后者将响应异常引发一个新的异常,因此在第一次发生插入行失败时终止 insertSchemaBasedXML() 的运行。 不过,如果您想 insertSchemaBasedXML() 方法在发生插入失败时继续执行,从而尝试插入下面的行,那么您可以简单地用输出警告消息的一个代码行来替换在 catch 块中引发异常的代码行。在这种情况下, insertSchemaBasedXML() 方法中的 catch 块将看起来像这样:
catch (Exception $e) {
print 'Could not insert
a row into the ' .$table .
' table:' . $e->getMessage();
}
转移 XML 数据的其他途径 上一部分演示了您如何能够利用 PHP 通过标准 SQL 转移 Oracle 数据库中的 XML 数据。实际上,Oracle XML DB 为您提供了将 XML 内容转入和转出 Oracle 数据库的不同策略。 例如,您可以使用行业标准的互联网协议(例如 FTP、HTTP 或 WebDAV)来将 XML 内容插入到数据库中。在这种情况下,您只需将包含基于模式的 XML 内容的文件放到 Oracle XML DB 信息库中的一个文件夹里。然后 Oracle XML DB 将隐式地把相应的行插入到被指定为 XML 模式的默认表的表中。 例如,为了增加一个以编程的方式通过 FTP 插入 XML 的函数,您可以向列表 1 的 MyDomDocument 类中添加一个 insertXMLByFTP() 公有方法(如列表 9 所示)。 代码列表 9: insertXMLByFTP SQL 的互联网协议替代方式
public function insertXMLByFTP($tagName, $table, $user, $pswd, $ftp_dir, $host, $port=2100,
$timeout=30)
{
try {
$items = parent::getElementsByTagName($tagName);
$con_id = ftp_connect($host, $port, $timeout);
$login = ftp_login($con_id, $user, $pswd);
ftp_chdir($con_id, $ftp_dir);
for ($i = 0; $i < $items->length; $i++) {
$nodeXML = parent::saveXML($items->item($i)) ."\n";
$dest_file = $tagName .$i .'.xml';
$temp = tmpfile();
fwrite($temp, $nodeXML);
fseek($temp, 0);
if(!ftp_fput($con_id, $dest_file, $temp, FTP_ASCII))
throw new Exception("Cannot put $dest_file");
fclose($temp);
}
}
catch (Exception $e) {
throw $e;
}
print "Data have been submitted";
}
列表 10 中的 domXMLFTP_db.php 脚本显示了工作中的 insertXMLByFTP() 方法。(在试图运行 domXMLFTP_db.php 之前,您必须删除由于之前的脚本运行而被插入到 employee 表中的所有行,以避免违反完整性约束。) 代码列表 10: domXMLFTP_db.php 执行 insertXMLByFTP 函数
<?php
//File:domXMLFTP_db.php
require_once 'MyDomDocument.php';
require_once 'dbConn5.php';
require_once 'ScottCred.php';
try {
$dom = new MyDomDocument();
$root = $dom->addElement($dom, 'EMPLOYEES');
$db = new dbConn5($user, $pswd, $conn);
$sql="SELECT EMPNO, ENAME, JOB FROM EMP";
$db->query($sql);
while ($row = $db->fetch()) {
$empl = $dom->addElement($root, 'EMPLOYEE');
$empl->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
$empl->setAttribute('xsi:noNamespaceSchemaLocation', 'http://localhost:8080/public/emp.xsd');
$emplno = $dom->addElement($empl, 'EMPNO', oci_result($row,'EMPNO'));
$ename = $dom->addElement($empl, 'ENAME', oci_result($row,'ENAME'));
$title = $dom->addElement($empl, 'TITLE', oci_result($row,'JOB'));
}
$dom->insertXMLByFTP('EMPLOYEE', 'employee', 'usr', 'pswd', '/public', 'localhost');
}
catch(Exception $e) {
print $e->getMessage();
}
?>
列表 10 中的 domXMLFTP_db.php 脚本为存储在 SCOTT/TIGER 演示模式下的 emp 表中的每一个行创建了相应的基于模式的 employee XML 文档,然后将 XML 文档作为一个文件放到 Oracle XML DB 信息库的 /public 目录中。Oracle 数据库自动依次地将相应的行插入到 PHPUSR 数据库模式中的 XMLType 的 employee 表中。您可以通过作为 PHPUSR 从 SQL*Plus 登录到数据库中并获取行数来确认 INSERT 操作是否成功(如下所示):
SELECT COUNT(*) FROM employee;
COUNT(*)
------------------
14
支持事务 默认情况下, oci_execute() 函数(在我们的基于 SQL 的示例中到处使用)是不支持事务的 默认的执行模式值是 OCI_COMMIT_ON_SUCCESS 。不过,您可以通过将 OCI_DEFAULT 执行模式指定为 oci_execute() 调用中的第二个参数来轻松地修改默认的行为,以显式地从 PHP 脚本中控制事务(如列表 11 所示)。 代码列表 11: 从 PHP 中控制事务
public function query($sql, $mode = OCI_COMMIT_ON_SUCCESS)
{
if(!$this->query = oci_parse($this->conn, $sql)) {
$err = oci_error($this->conn);
throw new Exception('Failed to execute SQL query:' . $err['message']);
}
else if(!oci_execute($this->query, $mode)) {
$err = oci_error($this->query);
throw new Exception('Failed to execute SQL query:' . $err['message']);
}
return true;
}
接下来,将以下公有方法添加到 dbConn5 类中:
public function commit()
{
if(!oci_commit($this->conn)) {
return false;
}
return true;
}
public function rollback()
{
if(!oci_rollback($this->conn)) {
return false;
}
return true;
}
当具备了前述的 dbConn5 类方法后,您可能想将一个新的 insertSchemaBasedXML_trans() 公有方法添加到 MyDomDocument 类中(如列表 12 所示)。 代码列表 12: insertSchemaBasedXML_trans 函数插入或回滚
public function insertSchemaBasedXML_trans($tagName, $db, $table, $schema)
{
try {
$items = parent::getElementsByTagName($tagName);
for ($i = 0; $i < $items->length; $i++) {
$nodeXML = parent::saveXML($items->item($i)) ."\n";
$db->query("INSERT INTO " .$table ." VALUES( XMLType('" .$nodeXML ."').createSchemaBasedXML('"
."$schema" ."'))", OCI_DEFAULT);
}
if(!$db->commit())
print "Failed to commit the transaction";
}
catch (Exception $e) {
if($db->rollback())
print "Failed to rollback the transaction";;
throw $e;
}
print "Data have been submitted";
}
现在您可能想用 domXML_db.php 脚本中的 insertSchemaBasedXML_trans() 来替换 insertSchemaBasedXML() 方法,以保证所有生成的 XML 内容都将被插入到 employee 表中。如果该方法至少有一行插入失败,那么整个事务将被回滚。 结论 利用 PHP 5 中面向对象的特性,您能够编写可重用的代码来将 XML 内容转入和转出 Oracle XML DB。利用在 PHP 5 中提供的多种预先定义的类 您可以根据需要重用或扩展它们,以及通过 SQL、FTP、HTTP 和 WebDAV 进行访问 — 您可以用最少的工作量来设计甚至更加复杂的 PHP/Oracle XML DB 应用程序。
Yuli Vasiliev ( jvyul@yahoo.com ) 是一位主要致力于 Oracle 对象和 Oracle XML 技术的软件开发人员、自由作者和顾问。
|
热门下载 | ||