| 开发人员:PHP
PHP 大会教程:Oracle 数据库 10g 特别版和 Zend Core for Oracle
作者:Christopher Jones 和 Alison Holloway
一个介绍如何安装 Oracle 数据库特别版和 Zend Core for Oracle 以及如何使用 PHP OCI8 扩展的分步指南
2006 年 3 月发布
安装 Oracle 数据库特别版和 Zend Core for Oracle
本部分介绍了如何安装:
我们已经在下列系统上对安装和配置进行了测试:
- 512MB 内存
- 5GB 硬盘空间
- Red Hat Enterprise Linux (RHEL) 3
- RHEL 4
- Novell SUSE Linux Enterprise Server (SLES) 9
Oracle 数据库特别版
Oracle 数据库特别版是一款小型入门级数据库,可以免费下载、开发和部署,并且可以自由地与应用程序一起分发,不需要支付数据库许可费!
Oracle 数据库特别版是使用与 Oracle 数据库 10g 第 2 版产品(标准版和企业版)相同的基本代码构建的,可以在 32 位 Windows 平台和 Linux 平台上使用。
Oracle 数据库特别版比较适用于:
- 致力于 PHP、Java、.NET 以及其他需要数据库的应用程序的开发人员
- 需要免费入门级数据库来进行培训和部署的 DBA
- 需要在其应用程序或产品中嵌入 Oracle 数据库的独立软件供应商 (ISV)
- 需要免费的入门级数据库的教育机构和学生
Oracle 数据库特别版包含下列编程接口:
- SQL、PL/SQL
- Java、C 和 PHP
- Windows .NET
- Oracle Application Express
- C++、ODBC、OLE DB
Oracle 数据库特别版具有下列限制:
- 用户数据最大为 4GB
- 单数据库实例
- 使用一个 CPU
- 使用 1GB 内存
Oracle 数据库特别版提供了基于浏览器的管理界面,即 Oracle Application Express。
同行和产品专家通过 OTN 论坛提供支持。
安装 Oracle 数据库特别版
如果您的 libaio 版本不高于 0.3.96,则在安装 Oracle 数据库特别版之前必须安装该库。
Oracle Database Developer CD for Linux x86 上的安装过程与下面的过程略有不同。如果从此 CD 进行安装,请按照文档 Start_Here.html(位于此 CD 的根目录下)中的说明执行操作。
要安装 Oracle 数据库特别版,执行下列操作:
- 以 root 用户身份登录或执行 su 命令
su
Password:
- 将 oracle-xe-10.2.0.1-1.0.i386.rpm 复制到 /tmp,然后将目录更改为 /tmp。
cp oracle-xe-10.2.0.1-1.0.i386.rpm /tmp
cd /tmp
- 安装 RPM
rpm -ivh oracle-xe-10.2.0.1-1.0.i386.rpm
安装了 Oracle 数据库特别版。
- 配置数据库。
/etc/init.d/oracle-xe configure
- 对 Application Express 使用默认端口 8080,并对数据库监听器使用 1521。
- 输入并确认默认用户的密码。
- 输入 Y 或 N,指定是否希望数据库在系统重新启动时自动启动。
至此,已经配置并启动了数据库和数据库监听器。
测试 Oracle 数据库特别版安装
要测试 Oracle 数据库特别版安装,执行下列操作:
- 打开 web 浏览器并输入:
http://127.0.0.1:8080/apex
- 以用户 system 的身份使用安装过程中输入的密码登录。
您应登录了 Oracle。
Zend Core for Oracle
2005 年,Oracle 与 Zend Technologies 已经开始合作开发了一个集成的解决方案,旨在帮助开发人员基于常用的 PHP 脚本语言创建并部署数据库驱动的 Web 应用程序。
2005 年 10 月,Oracle 与 Zend 宣布了 Zend Core for Oracle (ZCO) 的通用版本。ZCO 提供了一个经过充分测试和完全受支持的 PHP 5 发行版本,其中包含与 Oracle 数据库 10g 客户端库的集成,并使开发人员能够在几分钟内启动并运行 PHP 和 Oracle。
ZCO 是为 PHP 开发人员提供的一个预建产品系列,它使开发人员能够更容易地在一个强健、可伸缩且可靠的基础架构中开发和部署应用程序。该产品包括与 Oracle 数据库客户端库的紧密集成,以及对 XML 和 Web 服务的原生支持,同时还支持应用日益广泛的面向服务体系结构 (SOA)。它为数据库驱动的应用程序提供了一个快速开发和部署的基础。
Oracle 还在 Oracle 技术网上推出了 PHP 开发人员中心,致力于与 Oracle 相关的 PHP 开发。
Oracle 与 Zend 之间的协作加强了 Oracle 对开放源代码 PHP 社区的贡献。Oracle 新引入并优化的用于 PHP 的 OCI-8 扩展将交回 PHP 社区并集成到 ZCO 中。
这一协作通过帮助开发人员更有效地创建并部署数据库驱动的应用程序,扩展了 Oracle 在开发源代码上的投入。
Zend 支持 ZCO 和 PHP。
安装 Zend Core for Oracle
要安装 ZCO,执行下列操作:
- 以 root 用户身份登录或执行 su 命令(如果您尚未执行该操作)。
su
Password:
- 将 ZendCoreForOracle-v1.3.1-Linux-x86.tar.gz 复制到 /tmp,然后将目录更改为 /tmp。
cp ZendCoreForOracle-v1.3.1-Linux-x86.tar.gz /tmp
cd /tmp
- 解压缩已下载的 Zend Core for Oracle 软件:
tar -zxf ZendCoreForOracle-v1.3.1-Linux-x86.tar.gz
文件解压缩到一个名为 ZendCoreForOracle-v1.3.1-Linux-x86 的子目录。
- 将目录更改为 ZendCoreForOracle-v1.3.1-Linux-x8,然后启动 Zend Core for Oracle 安装:
cd ZendCoreForOracle-v1.3.1-Linux-x86
./install
- 在最初的 Zend Core for Oracle Installation 页面中,单击 OK。
- 在 Zend Core for Oracle V.1 页面中,单击 Exit。
- 当系统提示您接受许可条款时,单击 Yes。
- 如果已经安装了 PHP,则系统提示您备份并覆盖现有的 php.ini 文件。单击 Yes。
- 当系统提示您指定 Zend Core for Oracle 的安装位置时,接受默认位置(或输入您想要的位置),然后单击 OK。安装程序开始解压缩安装所需的文件。
- 当进度窗口指示已安装所有软件时,系统将提示您“Please enter the GUI password”。在 Password 域中,输入要在访问 Zend Core Console 时使用的密码,然后单击 OK。
- 当系统提示您“Verify the password”时,输入上一步中指定的密码,然后单击 OK。
- 在 Zend Core 支持页面中,您可以有选择地输入 Zend 网络用户 ID 和密码,以便能够使用 Zend Core Console 跟踪对 Zend Core 和 PHP 组件的更新何时可用。如果您尚未注册,或不希望跟踪更新,则单击 No。
- 下一页提示您为 Zend Core 安装选择 Web 服务器。选择与 Linux 一起安装的默认的 Apache。单击 OK。
- 在确认 Web 服务器选择页面中,在“Do you wish to proceed?”的提示下,单击 Yes。
- 在下一个安装页面中,系统将提示您“Please select an installation method for Apache 2.0.52”。选择 Apache 模块作为方法,然后单击 OK。
- 在下一个安装页面中,当系统提示您“Please select a virtual server for the Zend Core GUI”时,选择 Main Server,然后单击 OK。
- 在下一个安装页面中,在“Would you like to restart the Web Server”提示下,单击 Yes。
- 在下一个安装页面中,将显示一个指示 apachectl 脚本已更新的通知。单击 OK。
- 下一个安装页面(包含“Thank you for installing Zend Core for Oracle”)列出了有用的配置命令和一个 Zend Core 引擎管理网页。记下此信息,然后单击 Exit。
- 将显示一个最后确认页面。单击 OK 完成安装。
Zend Core for Oracle 安装现已完成。
配置 Zend Core for Oracle
在该部分中,您将配置控制网页中默认错误报告的环境变量和 Zend Core 指令。
- 在 Web 浏览器中输入以下 URL,访问 Zend Core Administration 页面:
http://127.0.0.1/ZendCore
- 输入您在 Zend Core for Oracle 安装过程中提供的 GUI 密码。单击 login >>> 图标。
- 单击 Configuration 选项卡显示配置选项。
- 单击 + 图标展开 Error Handling 和 Logging 配置条目。
- 将 display_errors 指令设置为 On,以便开发过程中在 HTML 脚本输出中显示错误。
- 由于有未保存的更改,因此页面标题下将显示“Unsaved configuration”消息。单击 Save Settings 保存配置更改。
- 由于您已经进行了配置更改,因此必须重新启动 Apache Web 服务器。在页面标题下,注意提示您执行该操作的“Please Restart Apache”消息。单击 Restart Server 重新启动 Apache 服务器。
- 单击 Logout 退出 Zend Core for Oracle Administration 页面。
测试 Zend Core for Oracle 安装
要测试 ZCO 安装,执行下列操作:
将有关创建虚拟目录的信息添加到用户的 home 目录中。
- 创建一个公共虚拟目录 public_html。编辑 APACHE_HOME/conf/httpd.conf,删除以下行中的“#”:
#UserDir public_html
- 以普通用户(非 root 用户)的身份在 home 目录中创建一个名为 public_html 的目录,将目录更改为这个新创建的目录,在命令窗口中输入下列命令:
cd $HOME
mkdir public_html
cd public_html
- 创建一个名为 hello.php 的文件,在其中添加以下 PHP 代码:
<?php
echo "Hello world!";
?>
- 打开 Web 浏览器,输入以下 URL:
http://127.0.0.1/~<username>/hello.php
浏览器中将显示行“Hello world!”。
PHP OCI8 扩展
PHP oci8 扩展提供了用于访问 Oracle 数据库的 PHP 函数。Zend 和 Oracle 最近针对稳定性和性能对该扩展进行了重构。可以通过多种方法安装这个新版本。该版本包含在易于安装的 Zend Core for Oracle 包中,并包含在 PHP 源代码和 Windows 二进制文件中。PHP 扩展社区库 (PECL) 的源代码中也有它,并且 http://pecl4win.php.net/ext.php/php_oci8.dll 还提供 Windows 版的下载。
这个新扩展即可用于 PHP 4,也可用于 PHP 5。
查询 Oracle 只需获取并显示数据。
<?php
$conn = oci_connect('hr', 'hrpw', '//localhost/XE');
$stid = oci_parse($conn, 'select city from locations');
oci_execute($stid);
oci_fetch_all($stid, $res);
oci_close($conn);
print '<table border="1">';
while ($row = oci_fetch_array($stid, OCI_RETURN_NULLS)) {
print '<tr>';
foreach ($row as $item) {
print '<td>'.($item?htmlentities($item):'').'</td>';
}
print '</tr>';
}
print '</table>';
?>
该示例以 Oracle 的演示用户 HR(Human Resources 的缩写)的身份连接,并在 LOCATIONS 表中查找所有城市。
PHP 5 已经对 oci8 函数名进行了标准化。像 OCILogin() 这样的 PHP 4 名称已变为 oci_connect(),而旧名称仍然可以使用。这将产生一个负面影响,即用户在 PHP 手册中为同一功能编写的注释将出现在两个位置。这些注释将分别出现在旧语法和新语法的页面上。当函数存在同义词时,需要同时检查这两个手册页以获取某些好的、最新的用户提示。
连接到 Oracle
要连接到 Oracle,可以使用 PHP 的 oci_connect() 调用:
$c = oci_connect($username, $password, $dbname)
每个 oci_connect() 连接均存储在高速缓存中。当同一脚本中出现第二个 oci_connect() 时,将返回前一个高速缓存的连接。脚本完成时将清除该高速缓存。
Oci_new_connect() 提供了一个完全独立的连接。连接之间相互独立。这使您可以同时执行多个数据库事务:
$c = oci_new_connect($username, $password, $dbname)
持久连接在 PHP 脚本结束时不会自动关闭。它们仍保持打开状态,以便在其他脚本中重用:
$c = oci_pconnect($username, $password, $dbname)
当打开连接的开销很大时,使用持久连接很有好处。开销是否大取决于应用程序要求以及实现问题(如 web 服务器和数据库是否位于同一主机中)。
持久连接的缺点是始终使用 Oracle 资源,即使在无人访问应用程序或数据库的情况下也是如此。如果 Apache 创建了多个服务器进程,则每个进程将分别与数据库建立一组连接。可以使用 php.ini 参数调节持久连接的资源使用。
- oci8.max_persistent:该参数限制高速缓存的持久连接数。达到该限制时,所有 oci_pconnect() 调用均被视为 oci_connect() 调用。将该参数设置为 -1(默认值)表示没有限制。
- oci8.persistent_timeout:Apache 进程保持空闲持久连接的时间(以秒为单位)。每当 PHP 脚本完成时,无论脚本是否调用 oci8 函数,都将执行到期检查。将该参数设置为 -1(默认值)表示没有超时。如果连接已过期,oci_pconnect() 将创建一个新连接。
- oci8.ping_interval:oci8 在执行 oci_pconnect() 过程中执行 ping 操作之前经过的秒数。如果该参数设置为 0,则 PHP 将在每次调用 oci_pconnect() 时对数据库执行 ping 操作。要禁用 ping,将该值设置为 -1。默认值为 60 秒。如果 ping 确定连接不可用,则将创建一个新连接。
良好的应用程序设计将透明地恢复大多数故障。尽管像 Oracle 这样的系统比较稳定,但任何应用程序均存在一些潜在的故障点,其中包括网络、硬件以及用户操作(如关闭数据库)。Oracle 本身可以经过配置来关闭空闲连接。DBA 可能已经使用 CREATE PROFILE IDLE_TIMEOUT 安装了用户配置文件。或者,Oracle Net 层可能已经使网络超时。
Oci_pconnect() 将始终检查 Oracle 客户端设置,以便根据上次从服务器收到的任何响应判断服务器是否可用。这是一个快速操作。另外设置 oci8.ping_interval 将对服务器执行物理 ping 操作,这将导致所谓的网络“往返”,并且将对可伸缩性带来不利影响。由于在连接检查之间以及当您实际使用连接时,数据库或某些数据仍有可能不可用,因此为了实现最高的可用性和可伸缩性,通常建议您不要使用 oci8.ping_interval,而是在应用程序代码中进行错误恢复。
当然,越来越易于使用的 ping 功能还是为许多小型应用程序带来了很多好处。
确保您了解应用程序连接的生存期。虽然应尽可能重用连接,但您也完全可以根据需要创建新连接以及关闭连接。每个连接都将占用一些 Oracle 内存,因此关闭空闲连接可以减少总负载。
连接字符串
最让人感到困惑的当数连接字符串。常见的 Oracle 错误“ORA-12514 TNS:listener does not currently know of service requested in connect descriptor”即使花费很长时间仍难以解决。
DB 名称可以为:
- 简单连接字符串
- 完整连接字符串
- tnsnames.ora 别名
简单连接字符串
简单连接字符串类似于 JDBC。它指定主机名、端口号和 Oracle 数据库服务名称:
//hostname:port/service_name
对于使用默认端口 1521 的 Oracle 数据库特别版,只需使用“//localhost/XE”。
$c = oci_connect('hr', 'hrpw', '//localhost/XE');
必须具有 Oracle 10g 客户端库才能使用简单连接语法。ZCO 具有相应的 Oracle 库。
完整的硬编码连接字符串
完整 Oracle Net 连接字符串提供了足够的连接灵活性。
$db = MYDB2 = (DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)
(HOST = mymachine.mydomain)(PORT = 1521))
(CONNECT_DATA=
(SERVER = DEDICATED)
(SERVICE_NAME = MYDB.AU.ORACLE.COM)))';
$c = oci_connect($un, $pw, $db);
如果存在疑问,请复制其他 Oracle 工具和用户使用的连接字符串。
通过使用完整语法,可以实现像负载平衡这样的 Oracle Net 特性,并可以调整包大小。简单连接语法不具备这样的灵活性。
Tnsnames.ora 文件中的网络别名
可以将完整的连接字符串存储在一个名为 tnsnames.ora 的文件中,并通过别名在 PHP 中引用它
# tnsnames.ora
MYDB2 = (DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)
(HOST = mymachine.mydomain)(PORT = 1521))
(CONNECT_DATA=
(SERVER = DEDICATED)
(SERVICE_NAME = MYDB.AU.ORACLE.COM)))
在 PHP 中,可以使用以下代码进行连接:
$c = oci_connect($un, $pw, 'MYDB2');
PHP 需要能找到 tnsnames.ora 文件来解析别名“MYDB2”。这是一个常见问题。
在“ORACLE_HOME”样式的安装(例如,XE)中,默认的 tnsnames.ora 位于以下位置:
$ORACLE_HOME/network/admin/tnsnames.ora
即
/usr/lib/oracle/xe/app/oracle/product/10.2.0/server/network/admin/tnsnames.ora
无论是使用默认位置还是其他位置,Apache 必须能够在启动时找到该目录。通常情况下(包括在 ZCO 中),这通过设置 TNS_ADMIN 环境变量实现。
Oracle 环境变量
oci8 扩展始终需要查找 Oracle 库和消息文件。找不到正确的文件将导致 PHP 返回 Oracle“ORA-12705:Cannot access NLS data files or invalid environment specified”。如果存在多个 Oracle 安装,则可能发生冲突。ZCO 包含它自己的 Oracle 即时客户端本地副本,该副本包含 Oracle 库和消息,因此您不会看到该错误。
NLS_LANG(全球化)和 TNS_ADMIN(网络)变量通常是 ZCO 所必需的。
必须设置启动 Apache 的环境中的环境变量,以便 oci8 扩展在首次加载时可以访问这些值。在 PHP 脚本中不要使用 putenv() 设置环境变量。所有 oci8 函数的反映并不一致。它肯定不可移植。
ZCO 修改了 apachectl,添加了 LD_LIBRARY_PATH。(如果 Oracle 提供了以不同方式链接的即时客户端,则未来版本的 ZCO 可能不需要该操作)。这使得可以重用 ZCO GUI 控制台来启动 Apache。
您可能需要使用 ZCO 的 TNS_ADMIN 和 NLS_LANG 执行类似操作,或者如果您手动启动 Apache,则在调用脚本中设置环境:
#!/bin/sh
TNS_ADMIN=/usr/local/apache/conf
export TNS_ADMIN
echo Starting Apache
#export > /tmp/envvars
/usr/local/apache/bin/apachectl start
该示例假设 /usr/local/apache/conf/tnsnames.ora 存在。TNS_ADMIN 指向包含 tnsnames.ora 文件的目录。
关闭连接
在每个脚本结束时,将自动关闭使用 oci_connect() 或 oci_new_connect() 打开的连接。您也可以调用以下代码来显式关闭连接:
oci_close($c);
将回滚任何未提交的数据。
如果长时间运行的脚本只用少量时间与数据库交互,则您可能要关闭连接以将资源释放给其他数据库用户使用。
oci_close() 无法关闭使用 oci_pconnect() 打开的连接。这与持久资源在其他 PHP 扩展中的工作方式类似。
Oci_close() 通过引用计数起作用。仅当完成所有连接引用时,才会实际关闭连接。在本示例中,$c1 和 $c2 是一个连接,但仅当脚本结束时,才会关闭数据库连接。
$c1 = oci_connect('hr', 'hrpw', '//localhost/XE');
$c2 = oci_connect('hr', 'hrpw', '//localhost/XE');
do_query($c1, 'select user from dual');
oci_close($c1);
do_query($c1, 'select user from dual');
do_query($c2, 'select user from dual');
oci_close($c2);
在重构 oci8 之前,oci_close() 函数是一个无操作函数。即使在需要的情况下,您也无法显式关闭连接。现在,这种情况发生了变化,但您可以在必要时通过在 php.ini 中设置 oci8.old_oci_close_semantics 来恢复为旧行为。将 oci8.old_oci_close_semantics 设置为 1 可以使 oci_close() 再次成为无操作函数。默认值 0 表示 oci_close() 将关闭连接。
执行语句
oci8 中的查询遵循 Oracle 中常用的模型:分析、执行和获取。类似 CREATE 和 INSERT 这样的语句只需分析和执行。如今,分析实际上只是一个“准备”步骤,这是因为 Oracle 的实际分析操作会在执行阶段执行。
您可以像在字符串中使用“%s”打印格式指示符那样选择将局部变量“绑定”到语句中。这样做可以提高性能和安全性。
您也可以“定义”将结果存储到的位置。大多数用户让 oci8 fetch 函数执行该操作。
可能的步骤包括:
- 分析 - 准备一个要执行的语句
- 绑定 - 可以有选择地让您绑定数据值(例如,在 WHERE 子句中)以便提高性能和安全性
- 定义 - 一个可选步骤,使您可以指定哪些 PHP 变量将保存结果。(不常见?)
- 执行 - 数据库处理该命令并缓冲结果。
- 获取 - 从数据库中取回查询结果。Oci8 提供了一些可供选择的获取语句。
没有哪个 PHP 函数调用可以一次性执行以上所有操作,但在应用程序中创建一个这样的函数也不难,它还允许您添加自定义的错误处理要求。
查询
以下是 oci8 中的一个基本查询:
$stid = oci_parse($c, 'select * from locations');
oci_execute($stid, OCI_DEFAULT);
oci_fetch_all($stid, $res);
确保在必要时使用双引号:
$stid = oci_parse($c, "select * from locations where city = 'Sydney'");
oci8 包含一些 fetch 函数,PHP oci8 参考手册对其进行了详细说明。
- oci_fetch_all():一次性获取所有结果
- oci_fetch_array():以您选择的数组形式获取下一行
- oci_fetch_assoc():以关联数组的形式获取下一行
- oci_fetch_object():以对象形式获取新行
- oci_fetch_row():以整数索引的数组形式获取下一行
- oci_fetch():与 oci_result()(返回给定字段的结果)一起使用
需要重复调用获取单个行的函数:
$stid = oci_parse($c, "select city from locations");
oci_execute($stid, OCI_DEFAULT);
while ($res = oci_fetch_row($stid)) {
echo $res[0] ."<br>\n";
}
某些函数包含可更改其行为的可选参数,例如,oci_fetch_array() 提供了一些选项,用于指定是以关联数组形式还是以数字索引数组形式(或同时以关联数组和数字索引数组形式)返回结果。关联数组使用大写列名命名。
由于一些未知的历史原因,某些 PHP 4 fetch 函数在默认情况下不返回 NULL 数据。对于这些函数,请指定您需要返回 NULL,否则结果可能不包含您选择的所有列。
可以使用两个 php.ini 参数调整 PHP 的总体查询性能:
- oci8.default_prefetch:当执行每个数据库获取操作时 Oracle 返回的记录数。默认值为 10。调整该设置可以显著提高返回大量行的查询的性能。它每次返回尽可能多的数据,从而最大限度地降低了数据库服务器的“往返”次数。Oracle 将数据高速缓冲在它的客户端缓冲区中,并只向 PHP 提供 PHP 本身请求的行。
- oci8.statement_cache_size:支持 OCI 客户端语句高速缓存。默认值为 20 个语句。可以通过将该值设置为 0 来禁用高速缓存。客户端语句高速缓存意味着甚至不需要将语句文本传输给数据库,从而进一步减少了网络通信量和服务器负载。高速缓存是一个纯 Oracle 会话,因此当使用持久连接时,该特性通常很有用。
Insert/Update/Delete/Create/Drop
执行 CREATE 和 INSERT 等语句只需要进行分析和执行:
$s = oci_parse($c1, "create table i1test (col1 number)");
$r = oci_execute($s, OCI_DEFAULT);
$s = oci_parse($c1, "insert into i1test values (1)");
$r = oci_execute($s, OCI_DEFAULT);
只有一次性的应用程序配置部分才应使用 CREATE TABLE。某些用户认为应用程序需要创建临时表,而 Oracle 与其他数据库的限制并不相同。
在应用程序运行之前创建一次临时表。Tom Kyte 在 asktom.oracle.com 中介绍了全局临时表。
在 Oracle 数据库中,创建和删除表将自动提交所有未提交的数据。这种情况无法改变。
事务
就像任何其他关系应用程序一样,使用事务保护数据完整性对于 PHP 同样重要。但在极个别情况下,您需要提交所有数据,或根本不提交数据。
在前面的示例中,我们在执行代码中指定了 OCI_EXECUTE 标志。
$r = oci_execute($s, OCI_DEFAULT);
oci_execute() 的默认模式为 OCI_COMMIT_ON_SUCCESS(其名称显示了它的功能)。而不必要的提交会影响数据库性能,这是因为它将导致不必要的网络通信量以及导致数据库文件 I/O 的浪费。这就是为什么首选 OCI_DEFAULT 的原因。
oci_execute() 的 PHP 手册对此进行了简要介绍:
使用 OCI_DEFAULT 模式时,您将创建一个事务。当您关闭连接或当脚本结束(以最先结束的为准)时,事务将自动回滚。您需要显式调用 oci_commit() 提交事务,或调用 oci_rollback() 终止它。
在该示例中,如果不显式提交,则在 PHP 脚本结束时将回滚第二行。要准确插入这两行,就不应提交第一行,而是应在第二行之后提交 - 与该示例正好相反。
$s = oci_parse($c, "insert into i2test values ('row 1')");
$r = oci_execute($s);
$s = oci_parse($c, "insert into i2test values ('row 2')");
$r = oci_execute($s, OCI_DEFAULT);
错误处理
任何可靠的应用程序的错误处理均增加会复杂性并需要仔细的设计。期待着意外情况的出现。检查所有返回值。
要获取 Oracle 错误消息,必须将连接资源传递给 oci_error():
$c = oci_connect("hr", "hr", "//localhost/XE");
if (!$c) {
$e = oci_error(); // no resource passed
var_dump($e);
}
$stid = oci_parse($c, "select city from locations");
if (!$stid) {
$e = oci_error($c); // connection resource passed
var_dump($e);
}
$rc = oci_execute($stid, OCI_DEFAULT);
if (!$rc) {
$e = oci_error($stid); // statement resource passed
var_dump($e);
}
$rc = oci_fetch_all($stid, $results);
if (!$rc) {
$e = oci_error($stid); // statement resource passed
var_dump($e);
}
绑定变量
绑定变量类似于“%s”打印格式指定符。通过它们可以使用不同的变量值重新执行语句,从而获得不同的结果。强烈建议您使用绑定。
它们可以提高数据库总吞吐量。Oracle 可以对语句重用任何高速缓存的执行计划,即使其他人最初执行了该计划。
此外,绑定变量也是一个可以阻止 SQL 注入安全攻击的重要方法。用户数据通常被视为数据而非 SQL 语句的一部分。
$stid = oci_parse($c,
"select last_name from employees where employee_id = :eidbv");
$myeid = 101;
oci_bind_by_name($stid, ":EIDBV", $myeid);
oci_execute($stid, OCI_DEFAULT);
oci_fetch_all($stid, $res);
echo "Last name is:". $res['LAST_NAME'][0] ."\n";
// No need to re-parse
$myeid = 102;
oci_execute($stid, OCI_DEFAULT);
oci_fetch_all($stid, $res);
echo "Last name is:". $res['LAST_NAME'][0] ."\n";
绑定数据在调用 oci_execute() 时应可以访问。在 sub 函数中使用局部变量可能会导致作用域问题。
除了向 Oracle 中传入数据的“IN”绑定以外,还存在返回值的“OUT”绑定。它们通常用于从 PL/SQL 过程和函数返回值。
oci_bind_by_name() 函数使用可选的大小和数据类型参数。
绑定后的 PHP 数字与字符串之间进行相互转换。这意味着在绑定变量中返回数字数据值时通常必须向 OCIBindByName() 提供 length 参数。该长度是将返回的位数。
在某种情况下,您可能决定不使用绑定变量。当语句包含绑定变量时,优化器不知道您最终要使用的值。如果数据很固定,则您可能要对值进行硬编码。但如果数据是由用户输入,确保对其进行处理。
很多旧文档在对 oci_bind_by_name() 的调用中使用“&”。不要这样做。由于最新的 PHP 按引用调用清除了该语法,因此不建议使用该语法。我还发现该语法会导致一些问题。
PL/SQL 存储过程
PL/SQL 是 Oracle 的过程语言。它最初以 ADA 为模型,是一种完善的强大语言。您可以直接在其中嵌入 SQL 语句。它在数据库服务器中执行,这对 I/O 有一定的要求。
当决定是在客户端中编写 PHP 还是在服务器中编写 PL/SQL 时,请考虑您的语言技能、通过网络传输数据的成本以及代码的可重用性。如果用 PL/SQL 编写,则用任何工具或客户端语言编写的 Oracle 应用程序均可以重用此功能。某些功能将仅位于数据库中,如事件触发器。在 Oracle 中,可以创建在数据插入或用户登录等事件发生时引发的功能。
预先提供了一些 PL/SQL 程序包来简化编码以及一些实用程序(如 DBMS_JOB)来进行调度。您可以创建存储过程、函数和程序包,以便可以无限制地扩展应用程序。通过 PL/SQL,您可以创建功能强大的应用程序。
要调用以前创建的 PL/SQL 过程,使用“匿名块”。这只是一个内部包含 PL/SQL 语句的 BEGIN/END 对。在该示例中,只是一个过程调用。可以在该块中使用任何所需 PL/SQL 语句。
$stid = oci_parse($c, "begin myproc('mydata', 123); end;");
oci_execute($stid, OCI_DEFAULT);
注意,“end”之后有一个分号,这与 SQL 语句的编写方式不同。
PL/SQL REF CURSOR
REF CURSOR 是 PL/SQL 的另一个重要特性。您可以在一个 REF CURSOR 类型的变量中存储一组查询结果,并像传递普通变量那样传递它。在 PHP 中,可以将 OCI_B_CURSOR 变量绑定到 PL/SQL 过程调用,并在普通的 fetch 循环中检索结果集的行。
作为示例,我们创建的 PL/SQL 程序包将包含一个接受一个工作标识符、然后查询执行该工作的员工的过程。该过程返回一个包含员工标识符和薪水的 REF CURSOR。
该程序包规范中提供了一个 ref cursor 类型以及过程签名。程序包主体包含该功能的代码。
CREATE OR REPLACE PACKAGE cv_types AS
TYPE EmpInfoTyp is REF CURSOR;
PROCEDURE EmpInfoRpt (jid IN VARCHAR, emp_cv IN OUT EmpInfoTyp);
END cv_types;
/
CREATE OR REPLACE PACKAGE BODY cv_types AS
PROCEDURE EmpInfoRpt (jid IN VARCHAR, emp_cv IN OUT EmpInfoTyp) AS
BEGIN
OPEN emp_cv FOR SELECT EMPLOYEE_ID, SALARY
FROM EMP_DETAILS_VIEW WHERE JOB_ID=jid;
END;
END;
/
在 PHP 中,我们可以按如下所示使用此 PL/SQL 过程:
$job_id = 'SA_MAN';
$stmt = "BEGIN cv_types.EmpInfoRpt(:jid, :rc); END;";
$stid = oci_parse($c, $stmt);
oci_bind_by_name($stid, ':jid', $job_id);
$refcur = oci_new_cursor($c); // pass the connection
oci_bind_by_name($stid, ':rc', $refcur, -1, OCI_B_CURSOR);
// Execute the call to EmpInfoRpt()
oci_execute($stid);
// Execute and fetch from the cursor
oci_execute($refcur);
echo '<table border="1">';
while($row = oci_fetch_assoc($refcur)) {
echo '<tr>';
foreach ($row as $c) {
echo "<td>$c</td>";
}
echo '</tr>';
}
echo '</table>';
大型对象 (LOB)
Oracle 字符大对象 (CLOB) 和二进制大对象 (BLOB) 列(以及 PL/SQL 变量)可以包含大量的数据。有很多方法可以创建这些对象来优化 Oracle 存储。此外,还预先提供了一个程序包 DBMS_LOB,通过它可以轻松地在 PL/SQL 中操作这些对象。
与使用 REF CURSOR 相似,我们告诉 PHP 我们需要一个 LOB 类型的变量,然后将其绑定到 SQL 或 PL/SQL 语句中。然后,我们有一些可以用于访问该数据的特殊 oci8 函数。
将 LOB 数据上载到表中:
$myv = 'a very large amount of binary data';
$lob = oci_new_descriptor($c, OCI_D_LOB);
$stid = oci_parse($c,
'INSERT INTO mybtab (blobid, blobdata) '
. 'VALUES(123, EMPTY_BLOB()) RETURNING blobdata INTO :blobdata');
oci_bind_by_name($stid, ':BLOBDATA', $lob, -1, OCI_B_BLOB);
oci_execute($stid, OCI_DEFAULT);
$lob->save($myv);
我们通过 PHP 描述符操作 LOB。这些 LOB 映射到 Oracle LOB 定位器。
如果通过 Web 表单上载了 lob,则可以使用 $lob->savefile($filename) 直接从上载的目录文件中上载它。可以使用 upload_max_filesize 在 php.ini 中设置所允许的上载文件的最大大小。
获取 LOB 时,oci8 将返回 LOB 描述符,并通过使用 load() 或 read() 方法检索数据:
$query = 'SELECT blobdata FROM mybtab WHERE blobid = 123';
$stmt = oci_parse ($conn, $query);
oci_execute($stmt, OCI_DEFAULT);
$arr = oci_fetch_assoc($stmt);
$result = $arr['BLOBDATA']->load();
LOB 描述符中的一些其他方法允许定位到指定偏移量、将数据直接导出到文件、清除数据以及复制或比较 LOB。
以下代码片段显示了定位到结果描述符中的第 10 个位置,然后将后面的 50 个字节存储在 $result 中
$arr['BLOBDATA']->seek(10, OCI_SEEK_SET);
$result = $arr['BLOBDATA']->read(50);
CLOB 的用法与 BLOB 的用法基本相同。绑定类型变成了 OCI_B_CLOB,且表必须明确包含一个 CLOB 列。
Oracle 集合
Oracle 手册中提到:“集合是一个同一类型元素的有序组。”它们是有效的数组。
与 LOB 相似,集合由使用 oci_new_collection() 分配的集合资源上的方法操作。
在一个简单的电子邮件地址簿演示(由 Oracle 的 Charles Poulsen 创建)中,创建了两个 VARRAY,一个用于用户名称数组,另一个用于电子邮件地址数组。VARRAY(根据 Oracle 手册的描述,它是可变大小数组 (variable-size array) 的缩写)使用序列化数字作为访问一组固定数量元素的下标。
SQL> drop table emails;
SQL> create table emails (
user_id varchar2(10),
friend_name varchar2(20),
email_address varchar2(20));
SQL> create or replace type email_array as
varray(100) of varchar2(20);
/
SQL> create or replace type friend_array as
varray(100) of varchar2(20);
/
SQL> create or replace procedure update_address_book(
p_user_id in varchar2,
p_friend_name friend_array,
p_email_addresses email_array)
is
begin
delete from emails where user_id = p_user_id;
for i in 1 .. p_email_addresses.count loop
insert into emails (user_id, friend_name, email_address)
values (p_user_id, p_friend_name(i),
p_email_addresses(i));
end loop;
end update_address_book;
/
update_address_book() 过程遍历地址集合的所有元素并插入每个元素。
在 PHP 中,我们创建集合变量并使用 append() 方法向每个数组中添加元素。通过绑定为 OCI_B_NTY(“有名类型”),我们可以向 PL/SQL 过程参数中传递集合。
$user_name = 'cjones';
$friends_names = array('alison', 'aslam');
$friends_emails = array('alison@example.com', 'aslam@example.com');
$friend_coll = oci_new_collection($c, 'FRIEND_ARRAY');
$email_coll = oci_new_collection($c, 'EMAIL_ARRAY');
for ($i=0; $i < count($friends_names); $i++) {
$friend_coll->append($friends_names[$i]);
$email_coll->append($friends_emails[$i]);
}
$stid = oci_parse($c,
"begin update_address_book(:name, :friends, :emails); end;");
oci_bind_by_name($stid, ':name', $user_name);
oci_bind_by_name($stid, ':friends', $friend_coll, -1, OCI_B_NTY);
oci_bind_by_name($stid, ':emails', $email_coll, -1, OCI_B_NTY);
oci_execute($stid);
其他 PHP 集合方法允许访问或复制集合中的数据。
PHP 5.1.2 通过一个新函数 oci_bind_array_by_name() 增强了对集合的支持。该函数与 helper PL/SQL 函数一起使用时对插入非常有效。我们可以绑定一个包含所有数据的 PHP 数组并使用一个 oci_execute() 将其发送给数据库。
SQL> drop table mytab;
SQL> create table mytab(name varchar2(20));
SQL> create or replace package mypkg as
type arrtype is table of varchar2(20) index by binary_integer;
procedure myproc(p1 in out arrtype);
end mypkg;
/
SQL> create or replace package body mypkg as
cursor cur is select name from mytab;
procedure myproc(p1 in out arrtype) is
begin
for i in 1 .. p1.count loop
insert into mytab values (p1(i));
end loop;
end myproc;
end mypkg;
/
要将 PHP 数组插入到 MYTAB 中,使用:
$s = "BEGIN mypkg.myproc(:c1); END;";
$stid = oci_parse($c, $s);
$array = array("abc", "def", "ghi", "jkl", "mno");
oci_bind_array_by_name($stid, ":c1", $array, 5, -1, SQLT_CHR);
oci_execute($stid, OCI_DEFAULT);
oci_commit();
oci_bind_array_by_name() 函数与 oci_bind_by_name() 相似。它不但接收数据长度上限,而且还接收数组中的元素个数。在本示例中,元素数量为 5,数据长度为 -1(即使用字符数据的实际长度)。
插入的数据为:
SQL> select * from mytab;
NAME
--------------------
abc
def
ghi
jkl
mno
可以绑定一些其他 Oracle 类型。PHP 手册镜像只获取 oci_bind_array_by_name() 文档。通过读取 CVS 中的 oci8 树下的自动测试获取信息源。这些测试也与 PHP 源代码打包在一起。
全球化
Web 应用程序全球化需要仔细的规划。必须使用正确的字符集表示数据,必须正确转换和编码 HTML 页面,并应留意用户期望的惯例。Oracle 数据库特别版两日速成以及 PHP 开发人员指南对全球化进行了全面的概述。
Oracle 数据库是使用指定字符集创建的。PHP 可以通过设置 NLS_LANG 环境变量选择它自己的字符集。Oracle 客户端(即 PHP)全球化设置还指定了默认值,如区域设置的正确日期格式、Oracle 错误消息使用的语言以及语言排列顺序。应在启动 Web 服务器的环境中设置 NLS_LANG。它的格式为:
<language>_<territory>.<character set>
例如,德国一位运行使用 Unicode 的应用程序的德语用户应将 NLS_LANG 设置为:
GERMAN_GERMANY.AL32UTF8
还可以使用 oci_connect() 可选的第四个参数按连接设置字符集。该字符集是一个包含 Oracle 字符集名称的字符集(例如,“ja16euc”)。
$c = oci_connect("hr", "hr", "//localhost/XE", 'ja16euc');
当未指定或为 NULL 时,则使用 NLS_LANG 环境变量设置。
客户端字符集决定了 Oracle 如何对从数据库传输到 PHP 的数据进行转换。如果字符集不等价,则可能错误地转换某些数据。
应由您的应用程序来正确处理返回的数据(通常使用 PHP 的 mb_string 功能)。
使用 XML
请您自行研究 SQL 和 PL/SQL。最大限度地重用已经存在的功能。Tom Kyte 广受欢迎的 asktom.oracle.com 提供了大量有用的信息。
Oracle 的一般指导原则是让数据库管理数据,并通过网络传输最少量的信息。不要将数据从数据库传递到 PHP,从而避免不必要的后期处理。数据是企业的核心资产。所有应用程序均应一致地处理数据。在应用程序层与数据库之间维护一个瘦接口也是一个不错的编程实践。
正则表达式、XML 函数、分析函数、自动事务以及空间功能也是有用的数据库特性的一些例证。
Oracle 和 PHP 5 均提供了出色的 XML 功能(允许使用大量作用域处理信息)。Oracle 的所有版本均包含所谓的 Oracle XML DB(或 XDB),即数据库的 XML 功能。
创建表后,可以使用线性 LOB 格式或根据 XML 模式的结构存储 XML。
一个有用的特性是可以自动将关系 SQL 表检索为 XML:
$query =
'SELECT XMLELEMENT("Employees",
XMLELEMENT("Name", employees.last_name), XMLELEMENT("Id", employees.employee_id)) as result
FROM employees
WHERE employee_id > 200';
$stid = oci_parse($c, $query);
oci_execute($stid, OCI_DEFAULT);
while ($row = oci_fetch_row($stid))
foreach ($row as $item)
echo htmlentities($item)." ";
注意 XML 查询的引用。
PL/SQL 程序包 DBMS_XMLGEN() 是另一种基于关系数据创建 XML 的方法。使用 DBMS_XMLGEN() 的查询返回一个 CLOB 列,因此需要将初始结果视为 LOB 描述符:
$query = "select dbms_xmlgen.getxml('
select first_name
from employees
where department_id = 30') xml
from dual";
$stid = oci_parse($c, $query);
oci_execute($stid, OCI_DEFAULT);
$res = oci_fetch_row($stid);
$mylob = $res[0]->load(); // treat result as a LOB descriptor
$mylob 中的值为:
<?xml version="1.0"?>
<ROWSET>
<ROW>
<FIRST_NAME>Den</FIRST_NAME>
</ROW>
<ROW>
<FIRST_NAME>Alexander</FIRST_NAME>
</ROW>
<ROW>
<FIRST_NAME>Shelli</FIRST_NAME>
</ROW>
<ROW>
<FIRST_NAME>Sigal</FIRST_NAME>
</ROW>
<ROW>
<FIRST_NAME>Guy</FIRST_NAME>
</ROW>
<ROW>
<FIRST_NAME>Karen</FIRST_NAME>
</ROW>
</ROWSET>
精益求精
感谢您阅读本指南,衷心希望它能给您带来帮助!
Christopher Jones 和 Alison Holloway 是 Oracle PHP 开发小组的成员。您可以在他们的网志中发表对本指南的意见。
|