PHP:超文本处理器(PHP:Hypertext Processor),是一种创建 Web 内容的、功能强大且越来越受欢迎的服务器端脚本语言。可移植性是 PHP 受到欢迎的主要原因之一:
- 可以在 Microsoft Windows、Linux、BSD、Macintosh OS X 和 UNIX 服务器上编译和运行 PHP。
- PHP 可以与包括 Apache 和 Microsoft IIS 在内的大多数普及 Web 服务器紧密集成,也可以用作独立的 CGI 解释器。
- PHP 的源代码可以免费获取,您可以自由编写和分发 PHP 应用程序,将其用于商业和非商业用途。
开放源码社区积极支持围绕 PHP 语言进行的开发,并在这方面做出了热心贡献。作为他们努力的结果,PHP 语言提供了一个令人印象深刻的扩展集合来提供各种功能,从 XML 转换、动态生成图像和 Adobe 可移植文档格式(Portable Document Format,PDF)文件,到支持 SOAP 客户机和服务器。
在简要介绍 PHP 脚本编制之后,本文将描述如何将 PHP 编译成支持 DB2 客户机的动态装入的 Apache 模块。文章的最后是几个示例,这几个示例将演示如何使用 PHP 连接 IBM Cloudscape 或 IBM DB2 Universal Database 服务器,如何向这些服务器中插入数据,以及如何从中选择数据。本文更新了我的前一篇文章,将介绍堆栈各个方面的主要版本改进:从 PHP 4 到 PHP 5、从 Apache 1.3.x 到 2.0.x、从 DB2 UDB Version 7 到 Version 8,以及从基于 2.4 内核到基于 2.6 内核的 Linux 发行版本。
PHP 基本上是一种服务器端脚本编制语言,它允许您将应用程序逻辑嵌入 HTML 页面,或者用 PHP 函数创建整个 HTML 页面。当 Web 服务器收到对某个 PHP 页面的请求时,它就将控制权转交给 PHP 引擎。PHP 引擎装入 PHP 页面,执行该页面内所有的 PHP 函数,然后将生成的 HTML 返回给 Web 服务器。
要在 Web 页面内调用 PHP 函数,只需将 PHP 函数嵌入到常规的 HTML 源代码内即可。PHP 函数被嵌入到以 ?php
开始并以 ?
结尾的 HTML 样式的标签内。例如,
演示了对 PHP 函数 echo()
的调用。要在这个 PHP 标签内包含多个函数,可以使用分号(;)字符将这些函数分隔开。
当然,可以在一个 PHP 脚本内包含许多 PHP 标签。为了演示使用 PHP 编写 Web 页面是多么的容易,下面给出了一段简短的脚本,它将输出从任意值开始到任意值结束的一系列数字。
在前一篇文章,我向读者介绍了一些有问题的 PHP 编码实践。因为我认为在那个时候它们是处于主导地位的实践,但在最近的 4 年中,人们的 PHP 开发和安全性意识已经获得了显著的提高。第一个约定是:虽然 PHP 解释器允许您将 PHP 代码转义序列直接放在 HTML 内部,但是现在许多开发人员都在一个 块中编写一个 PHP 文件,并将 HTML 生成为代码流的一部分。 |
if ($start > $stop) { return(FALSE); } elseif ($increment <= 0) { return(FALSE); } else { for ($i = $start; $i < $stop; $i = $i + $increment) { print "$i "; } } return(TRUE); } ?>
Counting from x to y
Counting from x to y
Counting from 1 to 10 by 1:
Counting from 2 to 20 by 2:
|
相比之下,目前许多 PHP 应用程序都将其代码分隔成一些函数,并从这些函数中生成 HTML。该方法使得理清用来生成最终 PHP 程序输出的调用顺序变得容易得多。在下面的例子中,我将前面的原始 HTML 移入它自己命名的 print_html()
函数中,这个函数是 PHP 程序中惟一调用的执行语句。
function print_sequence ($start, $stop, $increment) { if ($start > $stop) { return(FALSE); } elseif ($increment <= 0) { return(FALSE); } $numbers = "Counting from $start to $stop by $increment: "; for ($i = $start; $i < $stop; $i = $i + $increment) { $numbers .= "$i "; } $numbers .= " n"; return($numbers); }
function print_html() { $html = ' Counting from x to y Counting from x to y' . print_sequence(1, 10, 1) . print_sequence(2, 20, 2) . ' '; return($html); }
echo print_html();
?>
|
该方法与将 PHP 函数收集到模块中的方法相结合的另一好处就是,您可以容易地将 PHP 页面的标题和脚注标准化为一个单独模块。对于整个 PHP 站点外观的修改可以通过在一个模块中修改标准标题和脚注函数来完成,无需更改站点上的每一个 PHP 页面。没有绝对正确或错误的方法:将 PHP 输出封装在函数中和将 PHP 代码插入 HTML 模板中是两种处理方法,找到两者之间的适当平衡是一种个人选择。您只需认识每种方法的优点,并选择最能满足您需求的方法即可。
本文假定您在 Network Server 模式下运行 DB2 Universal Database 服务器或 IBM Cloudscape 数据库。为了连接 DB2 或 Cloudscape 数据库服务器,PHP 的统一 ODBC 扩展需要依赖 DB2 客户机的调用级接口(Call Level Interface,CLI)层。必须在同一台计算机上安装 DB2 客户机作为 PHP 解释器。首先,要从 下载 DB2 Runtime Client。确保您为操作系统和‘位’级(32 位还是 64 位)选择了合适的客户机。
- 安装 DB2 客户机,确保选择了用于应用程序开发的头文件和库文件。
- 在创建 DB2 实例时,选择适合您需求的用户名和密码。出于本文的目的,我们将假定默认的用户是 db2inst1,密码为 ibmdb2。
- 通过确保 /home/db2inst1/sqllib/include/ 中包含诸如 sqlcli1.h 的文件,来确认您已经正确安装了该应用程序开发组件。
为 Windows 操作系统提供了已预编译的二进制文件。这些二进制文件包括一个已经在 ODBC Driver Manager 上编译了的统一 ODBC 版本。这让我们可以权衡在安装无需进行编译就支持 DB2 和 Cloudscape 的 PHP 时需要考虑的一些性能因素。安装用于 Windows 的 DB2 客户机,并继续下一步,即建立到数据库的连接。
- (只适用于 Linux):通过下列命令在 shell 会话中继承 DB2 实例环境:
bash$ source /home/db2inst1/sqllib/db2profile
|
- 编目要连接的节点和数据库。例如,要在运行在端口 1527 上的名为‘db2air.toronto.ibm.com’的服务器上对数据库‘SAMPLE’进行编目,需要发出下列命令:
bash$ db2 CATALOG TCPIP NODE airnode REMOTE db2air.toronto.ibm.com PORT 1527 bash$ db2 CATALOG DB SAMPLE AT NODE airnode
|
- (只适用于 Windows):将数据库编目为系统 ODBC 数据源:
bash$ db2 CATALOG SYSTEM ODBC DATA SOURCE SAMPLE
|
- 连接该数据库,以确保该连接已正确编目:
bash$ db2 CONNECT TO SAMPLE USER username USING password
|
PHP 社区声称,PHP 可以在生产环境中与 Apache 2 一起使用,只要您使用 prefork MPM 来避免线程安全性问题。这是一条好消息,因为最新的 Linux 发行版本仅配送 Apache 2!
PHP 项目没有提供可在 Linux 上安装的二进制文件,因此为了获得最新的统一 ODBC 支持,您必须下载并编译 PHP 源代码。然而,最新的 Linux 发行版本配送了 Apache 2,因此,这些使用说明针对的是 Apache 2 Web 服务器。要编译 PHP,Linux 发行版本中至少必须包含 apache2-devel(Red Hat Enterprise Linux 4 上的 httpd-devel)、autoconf、automake、bison、flex、gcc 和 libxml2-devel 包。
- 要连接 DB2 或 Cloudscape 数据库,Apache 2 Web 服务器必须继承 DB2 客户机实例环境。编辑系统上的 /etc/sysconfig/apache2、/etc/sysconfig/httpd、/etc/init.d/apache2 或 /etc/init.d/httpd,以包含下列行:
source /home/db2inst1/sqllib/db2profile
|
- 下载最新 PHP 版本的。本文中用于测试和开发的版本是 PHP 5.0.3。
- 从 tarball 提取下列文件:
bash$ tar xjf php-5.0.3.tar.bz2
|
- 配置 PHP 源代码以使用 IBM DB2,并指定将为 Apache 2 Web 服务器创建的 PHP 版本:
bash$ cd php-5.0.3 && ./configure --with-ibm-db2=/home/db2inst1/sqllib --with-apxs2=/usr/sbin/apxs
|
- 构建并安装 PHP:
bash$ make && su -c 'make install'
|
如果一切都顺利,会要求您输入 root 密码,新的 PHP 二进制文件安装在 /usr/local/lib/php/ 目录中。
如果在构建该二进制文件时收到错误消息,那么可能是 Linux 发行版本中缺少必要的开发包,或是指定了错误的 DB2 实例位置。
如果在安装该二进制文件时收到错误消息,那么可能是 PHP 没有理解您所选择发行版本上的 Apache 配置文件。要解决这个问题,则需要修改 Makefile 中的下列代码行,将 install-sapi 移至列表结尾处:
install_targets = install-sapi install-cli install-pear install-build install-headers install-programs
|
install_targets = install-cli install-pear install-build install-headers install-programs install-sapi
|
PHP make install 命令试图代表您更新 Apache 2 配置文件。然而,某些 Linux 发行版本大量修改了这些配置文件,在这种情况下,就需要您的干预。例如,在 SuSE Linux Professional 9.2 上,您必须在 /etc/apache2/conf.d/ 目录中创建一个名为 php5.conf 的新文件,并添加下列行:
LoadModule php5_module /usr/lib/apache2/libphp5.so AddType application/x-httpd-php php
|
现在可以根据您的 Linux 发行版本,通过发出命令 /etc/init.d/apache2 restart
或 /etc/init.d/httpd restart
重新启动 Apache Web 服务器。如果您的 Linux 发行版本提供 SELinux 支持,就需要修改 SELinux 策略,或禁用 SELinux 支持,以便允许 Apache 用户继承实例用户的环境。例如,在 Red Hat Enterprise Linux 4 上,Apache Web 服务器以 apache 用户的身份运行,默认的 SELinux 策略阻止 apache 用户读取 /home/db2inst1/sqllib/db2profile 环境脚本。发出命令 setenforce 0
可以暂时禁用 SELinux,并且允许您重新启动 Apache Web 服务器。
PHP 项目通过提供带完整扩展集的立即部署(ready-to-deploy)的二进制文件,简化了 Windows 开发人员的工作。更妙的是,统一 ODBC 支持被内建在了 PHP 二进制文件中。要在默认安装 Apache 2 的 Windows 上安装 PHP,请执行以下操作:
- 下载最新 PHP 版本的 。本文中用于测试和开发的版本是 PHP 5.0.3。请选择 ZIP 文件,而非 Windows installer,因为 ZIP 文件中包含了统一 ODBC 扩展。
- 解压这些文件。出于本文的目的,我们假定您将这些文件解压至 C:php 目录。
- 将 C:phpphp.ini-recommended 文件复制到 C:php 目录中,将它作为名为 php.ini 的新文件。推荐配置为您的 PHP 环境提供了一套相对安全的默认参数。
- 编辑 C:Program FilesApache GroupApache2confhttpd.conf 文件,以便添加下列行:
LoadModule php5_module 'c:/php/php5apache2.dll' AddType application/x-httpd-php .php PHPIniDir 'c:/php'
|
- 重新启动 Apache。
为了演示一些可通过 PHP 和 DB2 执行的基本操作,我们将创建一组 Web 页面来帮助您管理数据库表,该表包含与一组作家有关的数据。首先,我们将创建一个保存作者数据的表,然后编写 PHP 脚本,以便能够浏览现有的作者。最后,我们将编写脚本,使用户可以向该表添加他们自己的作者数据。
为了使用该数据库,我们需要向该表插入一些数据。我们可以发出一些数据操作语言(Data Manipulation Language,DML)语句,但是因为安装了 PHP,所以我们将创建并使用一个简单的 PHP 表单,用它在数据库中插入新记录。下列所有 PHP 脚本都使用了 PHP 文档中所描述的统一 ODBC 函数。
在可以插入数据之前,必须在 PHP 脚本中创建数据库连接。一旦通过返回我们用户名的表列表来确认连接成功,就可以重用我们编写的其余脚本中的连接函数。
使用 PHP 连接数据库的语法如下:
int odbc_connect() (string dsn, string user, string password [, int cursor_type]);
|
其中:
- dsn:DB2 目录中注册的数据库名称。
- user:连接该数据库的用户名。
- password:用户密码。
- cursor_type:用于指定游标行为的可选参p。
AUTHOR 表包含 4 列:LAST_NAME、FIRST_NAME、MIDDLE_INITIAL 和 AUTHOR_ID(数据库服务器生成的惟一性标识符,充当该表的主键)。下列 PHP 脚本所包含的数据定义语言(Data Definition Language,DDL)语句将创建该表:
出于两个理由,我们将 SQL 语句定义为字符串变量:
- 在文件开始部分定义所有 SQL 语句,使读取代码和修改扭曲应用程序的元素变得更容易。对于包含 JOIN 操作或者包含可能跨越多行的复杂 WHERE 子句的长 SQL 语句,这样做特别有帮助。
- 在许多数据库语言程序中,我们需要反复发出相同的语句,有时仅使用稍微不同的值。在开发应用程序时,将语句声明为字符串变量将使之更易于转换成生产应用程序。
|
// connect to the database $conn = odbc_connect('SAMPLE', 'db2inst1', 'ibmdb2');
// define our SQL $sql = 'CREATE TABLE author (last_name VARCHAR(32) NOT NULL, first_name VARCHAR(32) NOT NULL, middle_initial VARCHAR(1), author_id INTEGER GENERATED ALWAYS AS IDENTITY, PRIMARY KEY (author_id))';
// issue our SQL statement directly odbc_exec($conn, $sql);
// close the database connection odbc_close($conn); ?>
|
为了创建该表,要将 PHP 代码复制到一个名为 create_table.php 的文件中。可以继承 DB2 实例环境,从命令行通过发出下列命令来运行该程序:
bash$ source /home/db2inst1/sqllib/db2profile bash$ php create_table.php
|
正如您可以看到的,PHP 不仅仅是一种 Web 脚本编制语言 —— 您还可以编写方便的命令行脚本。注意,脚本中暴露了用户名和密码。这不仅是一个相当不安全的实践(如果必须调试脚本,您可能会不经意地就与另一开发人员共享您的密码),而且在必须修改每个脚本中的密码时,这会带来极大的不便。取而代之的是,我们可以用一个公用函数创建模块 db2lib.php,通过用户名‘db2inst1’和密码‘ibmdb2’连接样本数据库 SAMPLE。然后,通过包括 db2lib.php,可以在脚本中使用以下函数:
function dbconnect($verbose=FALSE) { $dbname = "SAMPLE"; $username = "db2inst1"; $password = "ibmdb2";
// odbc_connect returns 0 if the connection attempt fails; // otherwise it returns a connection ID used by other ODBC functions $dbconn = odbc_connect($dbname, $username, $password);
if (($verbose == TRUE) && ($dbconn == 0)) { echo("Connection to database failed."); $sqlerror = odbc_errormsg($dbconn); echo($sqlerror); }
return($dbconn); }
function my_header($title) { $html = " $title $title "; return $html; } ?>
|
注意,如果没有为 dbconnect()
的 $verbose
参数指定任何值,则使用默认值 FALSE。如果该脚本成功地连接到数据库,那么只需向调用者返回数据库连接句柄。
如果连接失败,并且已经要求了 verbose 输出,那么脚本会告诉您它无法连接到数据库,并调用 odbc_errormsg()
函数来输出数据库所返回的错误消息。
在名为 db2lib.php 的文件中保存代码,以便能够在其余脚本中包含该函数。
在大多数情况下,应该像建立到数据库的连接中所描述的那样编目数据库连接,以便可以从命令行测试该连接。不过,也可以使用 IBM DB2 ODBC 驱动程序连接字符串来连接未编目的 DB2 或 Cloudscape 数据库。为了使用驱动程序连接字符串进行连接,需要将下列字符串传递给 odbc_connect() 方法的 DSN 参数,并用正确的主机名、数据库名和端口号进行替换:
DRIVER={IBM DB2 ODBC DRIVER};HOSTNAME=localhost;DATABASE=dbname;PROTOCOL=TCPIP;PORT=1527;
|
一旦成功连接到数据库,就可以开始进行一些有趣的工作,例如插入、更新和检索数据。可以使用 odbc_exec()
函数发出简单的 SQL 语句(没有包含用于变量输出的参数标志的语句)。下列脚本将向 AUTHOR 插入新的行:
// include the dbconnect() and my_header() functions include_once("db2lib.php");
echo(my_header"INSERT data into AUTHOR table");
$author_insert = "INSERT INTO author" . "(last_name, first_name, middle_initial)" . "VALUES('Scott', 'Daniel', 'B')";
$verbose = TRUE; $dbconn = dbconnect($verbose);
if ($dbconn != 0) { // odbc_exec returns 0 if the statement fails; otherwise // it returns a result set ID $result = odbc_exec($dbconn, $author_insert);
if ($result == 0) { echo("INSERT statement failed."); $sqlerror = odbc_errormsg($dbconn); echo($sqlerror); } else { echo("Successfully inserted one row."); } } else { echo("Connection failed. "); } echo(""); ?>
|
第二个过错就是假定将打开 register_globals php.ini 指令。该指令所带来的结果是在给定 PHP 脚本的全局范围内自动将 CGI 参数变成具有相同名称的 PHP 变量。虽然这是一个很方便的功能,但它也是极其不安全的,在我的文章发表不久之后,PHP 开发人员就明智地选择默认关闭 register_globals 。当然,这就破坏了那篇文章中的许多示例。我保证这篇 文章中的示例是可以工作的! |
当然在现实世界中,您可能不会通过编写脚本来向数据库重复插入同一行。您可能编写一个脚本,提供简化数据输入工作(或 Web 站点用户的数据输入工作)的 HTML 表单。幸运的是,PHP 使处理 HTML 表单输入变得更加容易。当 PHP 脚本是某个 HTML
元素的目标动作时,CGI 参数就自动转换成可在 PHP 脚本内使用的 $_GET[]
或 $_POST[]
superglobal 数组的成员。保存 CGI 参数的数组是由
元素中 action 属性的值决定的。superglobal 数组将根据表单中各个输入字段的 NAME 属性,来包含带有指定 CGI 参数值的字段。
例如,由
组成的表单将在 $_POST[]
superglobal 中创建 submit.php 脚本中名为‘myparm’和值为‘BLUE’的字段。您可以使用 $_POST['myparm']
访问该值。
如果需要多次发出同一语句,odbc_exec()
函数是一种代价高昂的方法,因为它每次都要准备和执行这条语句。您可以通过使用 odbc_prepare()
在单独的步骤中准备这条语句,并通过多次调用 odbc_execute()
执行准备好的语句,从而提高应用程序的性能。
在下列例子中,我们创建另一个 PHP 模块和脚本,用它们显示 HTML 表单,帮助我们向 AUTHOR 表插入数据。该模块演示了如何使用 odbc_prepare()
函数准备包含参数标志的 INSERT 语句。我们使用 PHP 的功能强大的 HTML 表单解析来确定是否可以使用任意 CGI 变量值调用脚本;如果不是,则只返回这个 HTML 表单。如果可以用 CGI 变量值调用脚本,则解析表单字段的内容,并将参数作为数组传递给 odbc_execute()
函数,以便向该表插入数据。
function insert_data( $dbconn, $last_name, $first_name, $middle_initial) { $insert_stmt = "INSERT INTO author " . "(last_name, first_name, middle_initial) VALUES(?, ?, ?)";
// odbc_exec returns 0 if the statement fails; otherwise // it returns a result set ID $result = odbc_prepare($dbconn, $insert_stmt);
if ($result == 0) { echo("odbc_prepare() failed."); return(FALSE); } else { odbc_execute($result, array($last_name, $first_name, $middle_initial)); echo("Inserted $last_name and $first_name and $middle_initial"); } return(TRUE); }
function author_form($action) { // $form is a HERE string that spans multiple lines $form = <<
HERE;
return($form); } ?>
|
db2form.php 是我们的第二个 PHP 模块,包含 insert_data()
和 author_form()
函数。将这些函数置于一个模块中有助于我们在其他脚本中重用它们。这正是脚本 prepinsert.php 所起的作用;它包含 db2lib.php,以便能够利用标准数据库连接函数,此外还包含 db2form.php,以便能够利用标准的 INSERT 和表单提交函数。该脚本返回一个 Web 页面,页面中包含允许我们在数据库中插入新行的简单表单,实际上这个脚本就是一个调用模块函数的包装器。
最后一个错误,也是最严重的错误就是没有提醒开发人员净化输入和输出数据的重要性。PHP Security Consortium 提供了许多关于该主题和许多更高级安全性主题的免费高质量参考资料。让我们这样说好了,如果没有确保脚本中所使用的数据是安全的,您的服务器成为恶意团体的牺牲品,不必对此感到惊讶。 |
该脚本的一个重要方面就是用来返回净化后 CGI 输入数据的 preg_replace()
调用集合。滥用 Web 应用程序的常见方法就是蓄意向表单变量传递恶意输入;如果 Web 应用程序简单地使用原始输入、将数据传递给 SQL 语句、用恶意输入作为文件名,或者不信任输入数据,那么可能发生意想不到的结果。在这个脚本中,在将输入字符串值插入数据库之前,我们使用 Perl 兼容的正则表达式函数 preg_replace()
来确保该输入字符串只包含字母字符。
/* The form within this PHP script calls itself as the action-handler using the $_SERVER['PHP_SELF'] superglobal variable.
If the script is called without CGI parameter values, only the form is displayed on the page.
If the script is called with CGI parameter values, we also call the custom function insert_data(). */
// include our custom function libraries include_once("db2lib.php"); include_once("db2form.php");
echo(my_header("INSERT using odbc_prepare"));
$first_name = ''; $last_name = ''; $middle_initial = null;
// cleanse CGI variable input; allow names with alphabetic chars only $name_pattern = '/^s*([a-z]+)s*$/i'; $name_replacement = '$1';
if (array_key_exists('first', $_POST) && array_key_exists('last', $_POST)) { $first_name = preg_replace($name_pattern, $name_replacement, $_POST['first']); $last_name = preg_replace($name_pattern, $name_replacement, $_POST['last']); $middle_initial = preg_replace($name_pattern, $name_replacement, $_POST['middle']); }
if (($last_name != '') && ($first_name != '')) {
if ($middle_initial == '') { // insert a true NULL value $middle_initial = null; } $verbose = TRUE; $dbconn = dbconnect($verbose);
if ($dbconn != 0) { insert_data($dbconn, $last_name, $first_name, $middle_initial); // always close your database connection odbc_close($dbconn); } }
// $_SERVER['PHP_SELF'] is a superglobal variable that contains the name of this script echo(author_form($_SERVER['PHP_SELF'])); echo('
|