|
关于使 Oracle PHP 应用程序全球化的概述
关于使 Oracle PHP 应用程序全球化的概述
引言
当一种应用程序被认为完全全球化时,它就应该能够在许多语言中使用,并已基本适合在世界范围内进行部署。从用户的角度看,这意味着界面的各个方面都已被翻译成了他们的本地语言和惯例。从开发人员的角度看,创建一个全球化的应用程序引起了一系列新的问题,我们需要从早期对这些问题进行仔细地规划。幸运的是,PHP 提供了所有必需的构建程序块,便于开发人员能够实施完全全球化的应用程序。本文介绍了当在 Oracle 应用服务器中进行部署时如何有效地使用这些特性。使您的应用程序全球化的工作可以分为四个不同的阶段。我们将介绍这些阶段的概念,然后讨论实现它们的方法。
1. 数据完整性
确保数据保持其意义可能是数据库驱动的应用程序的最基本但却非常重要的概念。由于数据在用户、中间层和数据库之间流动,而没有显式的处理,所以它必须在应用程序的所有层次之间保持完整性。这与全球化的应用程序尤为相关,因为在全球化的应用程序中可能进行字符集转换和字符串扩展,从而可能导致数据损坏。例如,如果您的数据库被配置为存储 Unicode 数据,而您的中间层应用程序被配置为仅使用西欧数据,那么数据库中的任何非西欧数据在通过字符集转换被提交给终端用户时,都很可能以损坏告终。这里,实现数据完整性的最简单的方法是确保在数据库、PHP 和终端用户浏览器之间,所有的层次在字符集方面都保持一致(字符集是用来表达数据的),并且每个层次都执行任意必要的字符集转换来支持这种一致性。
2. 字符串处理
任何需要处理文本数据的层都需要知道数据编码所使用的字符集,以便结果可以得到正确地解释。举一个简单的例子,假定一个应用程序被配置来接收字符长度不超过 3 个字符的字段。很可能在某个地方应用了一个字符串长度函数来执行这种检验,但不知道数据的字符集格式,该函数将不能正确地确定有多少个字符,因此不能正确地执行检验;认为字节长度等于字符长度是不正确的。字符串处理函数一般包含了字符串长度、子字符串、搜索、替换、和正则表达式操作,所有这些在许多中间层应用程序中都占据核心地位。
3. 文本翻译
应用程序将被翻译成所有的目标语言。这个水平上的翻译意味着将基础文本转换成在目标语言中有等价意义的文本,它包括翻译用户界面、翻译相关的文档和在线帮助,并可能翻译任意的可用于电子邮件或日志的模板文本。构建一个容易翻译的产品的第一个规则是要从源代码中将所有需要翻译的文本外部化。这样一来,您可以将逻辑从数据表示中分离出来,从而使得翻译的处理远没有在其它方式下那么复杂。使消息外部化意味着翻译程序可以处理和提交文件,而无需关于您的应用程序的专门知识,并且可以在运行期内配置您的应用程序来选择适当的语言集。开发环境必须提供一种将文本外部化到消息目录中的方法,以及一种通过识别来实现预期翻译的方法。
4. 数据表示
运行期数据可能需要一些特殊的准备,以便根据它们需要的语言正确地将它们提交给终端用户。例如,'12th October 2003' 可能以 '10/12/03' 的形式提交给一位美国用户,而以 '12/10/03' 的形式提交给一位英国用户。要求应用程序逻辑必须识别和提供支持,因为不正确的格式化可能导致数据的错误理解。
处理列表(如一个 to-do 列表、一个电子邮件标题列表、一个产品列表等)的应用程序可能希望将这些数据提交给根据他们的语言规则进行了分类的用户。必须提供能够获取这样一个列表并相应地对其进行归类的 API。
有时候还需要以特殊的格式来提交文本,以供另一种应用程序或协议使用。一些应用程序可能需要诸如 Base-64 之类的编码格式,有些应用程序可能需要诸如 UTF-8 之类的特殊编码。
使用 PHP
PHP 最初被设计为仅使用西欧数据,并且与 C 或 PERL 非常类似,不直接支持我们迄今为止谈到过的概念中的任何一个。不过,这些语言的确常常提供一系列让人印象深刻的构建程序块,以使开发人员能够创建一个完全全球化的应用程序。由于它的开放源代码特性,PHP 能够自由地使用可用的资料库来执行全球化任务,并且与 PERL 非常类似,借用了许多 C API 来执行这些任务。您还应该知道,在 PHP 内部,只在某些组件中提供了全球化支持,并且在那些组件中甚至只提供了某种程度的全球化支持,因此,在决定使用它们之前,值得了解一下它们的支持水平。例如,PHP 将只允许把单字节西欧名称用作变量。
本文以下内容旨在说明您如何能够在 PHP (4.2.x 或更高版本)和作为后端存储的 Oracle (9.2.x 或更高版本)中,利用在 PHP 手册的 LXXIII. Oracle 8 函数一章中提供了文档说明的 Oracle 连接函数来构建完全全球化的应用程序。
设立环境
为您的 PHP 应用程序提供正确运行的环境是至关重要的,并且当这个步骤正确完成时,将保证跨所有层的数据完整性。首先,我们应当集中考虑 PHP 引擎和 Oracle 之间的连通性,以确保我们能够插入和选择数据。
大多数基于互联网的标准支持 Unicode 作为字符编码。Unicode 是能够表示世界上大多数语言的一种编码,因此确保您的应用程序也支持 Unicode,以便它能够以许多语言进行部署是很有意义的。考虑到最新的发展现状,我们将认为您的应用程序实际上完全基于 Unicode,因为当您使用的整个技术堆栈都能够支持 Unicode 时,几乎没理由去支持传统的编码。
就 Oracle 连通性而言,PHP 基本上是一种 OCI 应用程序,这意味着所有适用于 OCI 的规则也适用于 PHP。OCI 应用程序利用环境变量来控制客户端的运行期的某些方面,对于我们而言,我们最关心字符集。这个设置是通过 NLS_LANG 环境变量来实现的,该变量具有以下形式:
NLS_LANG=<language>_<territory>.<character
set>
如果我们的应用程序被设计为在 Unicode 下为一个日本用户工作,那么我们将把该变量设为:
NLS_LANG=JAPANESE_JAPAN.AL32UTF8
这个环境变量只影响客户端,而对于字符集部分的基本前提是从服务器中选出的数据将以指定的字符集格式提交,更重要的是,客户端提交给服务器的数据必须是这种字符集格式的。一个常见的错误是认为 NLS_LANG 中的字符集必须设为被连接的数据库的字符集。如果客户端运行的字符集和数据库字符集不匹配,那么这可能将是一个代价很大的错误。对于基于 web 的应用程序(如我们正试图构建的应用程序),我们将假定提交给数据库的所有数据都已是 Unicode 格式的了。重要注意事项:虽然该设置的 language 和 territory 部分能够在运行期内进行修改,但 character set 部分不能修改,因此它是这里唯一一个真正必要的设置。通过省略可选的设置,我们能够将我们的应用程序配置为一个 Unicode 应用程序,方法如下:
NLS_LANG=.AL32UTF8
其中,AL32UTF8 是 Oracle 针对 UTF-8 — Unicode 的一种常见的多字节编码方式 — 的命名惯例。为了测试的目的,您可以在您的 shell 中将其设置为一个环境变量,并从命令行来运行 PHP。在您的运行期应用程序中,您将需要为您的运行期应用程序内部的 Apache 用户设置这个变量(和您的 ORACLE_HOME 和 ORACLE_SID 设置一起)。设置这个变量将为 Oracle 指出客户端提供的数据将是哪一种字符集格式的,并且意味着如果需要,Oracle 能够为输入和输出的数据执行字符集转换。为了避免字符集转换的开销,并使数据损失最小化,请确保您的数据库字符集也是 AL32UTF8。对于您的数据库字符集是 Unicode 的一个子集(例如西欧)的情况,这个子集外的数据将不能被正确存储。
现在在 PHP 和 Oracle 之间,数据完整性将得到保持,因此剩下要做的全部就是确保在 PHP 和终端用户的浏览器之间,数据完整性能够得到保持。浏览器依靠一个 HTTP 标题(即 Content-Type)来确定如何最好地显示内容,不仅如此,更重要的是确定使用什么字符集编码来将用户提供的表单输入发送回 PHP。我们希望这里每个地方都使用 Unicode,因此我们需要找到一种将所有页面标记为 Unicode 编码的方式。虽然 Oracle 使用 'AL32UTF8' 来引用 Unicode,但互联网标准使用 'UTF-8'。在 PHP 中存在许多方法来提供这种标记,但最简便的方法是在您的 php.ini 文件中设置 default_charset 配置变量,如下:
default_charset = UTF-8
这确保下面的 HTTP 标题将在所有的 PHP 页面中被设置并随后被发送给浏览器。
Content-Type:text/html; charset=UTF-8
虽然浏览器将保证只将完全形成的 UTF-8 提供给服务器,但是必须由应用程序开发人员来负责确保服务器生成的页面是真正用 UTF-8 编码的。这个设置并不表示将对输出页面进行任何转换。
这时您可以对您的应用程序进行一次数据完整性测试驱动。试着在 PHP 中构建一个用于输入的表单,将变量作为输入,并将它们插入到 Oracle 中的一个 VARCHAR2 列中。通过选择数据返回到一个报表中来证明它工作正常。通过使用在西欧字符集中不常见的数据,您可以确信它的确工作正常,尝试从一个阿拉伯文或中文网站中剪切和粘贴内容,并比较输出。如果您需要进一步确认设置工作正常,那么请测试存储在数据库中的数据 — 使用 DUMP() 函数来检查字节序列,并确保它是有效的 UTF-8。还请注意,PHP 能够正确地解码以 URL 转换格式编码的表单输入,然后提交它们作为变量用于您的 PHP 应用程序中。下面是一个非常简单的例子,它将一个用户提供的字符串插入到数据库中,并选出所有的行。假定有一个拥有单个字符列的表:
<FORM METHOD=GET>
<INPUT TYPE=TEXT NAME=invar VALUE="">
<INPUT TYPE=SUBMIT VALUE="GO">
</FORM>
<?
if (isset($invar))
{
$conn = ocilogon("scott", "tiger");
$pars = ociparse ($conn, "INSERT INTO t1
VALUES ('$invar')");
ociexecute($pars);
$pars = ociparse ($conn, "SELECT c1 FROM t1");
ociexecute($pars);
ocifetchstatement($pars, $res);
for ($i = 0; $i < $nrow; $i++)
{
$var = $res["C1"][$i];
echo "value: ($var)<BR>";
}
}
?>
您在您的脚本中显式编码的任何文本都以 Unicode 格式编码是很重要的。例如,如果您在 Oracle 中有一个表名称超出了 ASCII 字符集的范围,那么您必须在 SQL 语句中使用 UTF-8 编码,否则 Oracle 将丢出一个错误。确保您的编辑器能够以 UTF-8 格式保存。
处理数据
一旦建立了正确的数据流,识别出理解 Unicode 并且能够帮助我们处理和格式化数据的可用函数就很重要。正如之前所提到的,PHP 最初被设计为本地使用西欧数据(特别是 ISO-8859-1 字符集),并且当处理其它字符集,特别是那些不将每个字符编码在一个字节中的字符集时,通常不会返回预期的结果。为了解决这个局限性,在 PHP 4.0.6 中提供了一组函数(在 PHP 手册的多字节字符串函数一节中提供了文档说明),这些函数提供了支持许多字符集的字符串处理功能。我们特别感兴趣的是,PHP 也支持 Unicode。
要使用多字节字符串特性,请确保在 PHP 中启用了它 — 通过用 --enable-mbstring 配置选项进行编译来启用。您可以通过在一个脚本中检查 phpinfo() 的输出或者通过在命令行中运行 ‘php -m’ 来检查支持是否已进行了编译。如果您没有对您的 PHP 安装的控制权,那么您真不走运;请要求您的管理员来包含它。要开始部署 Unicode 格式的应用程序,只需在 php.ini 文件中进行以下设置:
mbstring.internal_encoding=UTF-8
如此设定后,所有的 mbstring 函数都将假定您提供给它们的文本是 UTF-8 格式的,并且当我们设立我们的环境时已确定会是这种情况,那么我们就准备好开始编码了。为了证明运行正常,将上一例子中的 FOR 循环修改为如下形式:
for ($i = 0; $i < $nrow; $i++)
{
$var = $res["C1"][$i];
echo "value:($var) ";
echo "strlen:" . strlen($var) ." ";
echo "mb_strlen:" . mb_strlen($var) ." ";
}
在字符串 "résumé" 作为输入的前提下,观察以下输出:
value: (résumé)
strlen: 8
mb_strlen: 6
注意,普通的字符串函数返回 8,这个数字实际上是一个字节计数,因为在 UTF-8 中,每个字符被编码为 2 个字节。实际的字符串长度由 mb_strlen 正确地计算出来,因为它知道两字节的组成部分只是单个字符。
还可能利用以下配置来重载标准字符串处理函数(如 strlen())的行为,以便它们能够根据 UTF-8 进行工作:
mbstring.func_overload=7
我们的示例中的两个函数都知道数据是 Unicode 格式的,输出于是将变为:
value:(résumé)
strlen: 6
mb_strlen: 6
如果您真的决定使标准函数超载,小心它将改变文档说明的基础字符串函数的行为,并且可能返回无法预料的结果。例如,如果您使用 strlen() 来获取用户提供的字符串的字节长度,以确定它是否将适合您的数据库中的一个 CHAR(20),那么您可能不希望使 strlen() 超载来根据字符工作。将您的应该根据字符工作的函数调用移植到它们的 mbstring 的等价调用上而不管 mbstring.func_overload,会是个很好的想法。
应当注意,在 PHP 中提供的许多字符串处理特性在 Oracle 中也提供了。Oracle 拥有的额外的好处是它完全全球化了,所有的函数调用都根据当前的数据库字符集来工作,而无需额外的配置。并且还提供了许多排列顺序,当利用一个 ORDER BY 子句从一个表中选出数据时,可以通过设置 NLS_SORT 来配置这些排列顺序。在数据库中执行这些例程,以使您的中间层逻辑可以专注于表示细节,这样做通常能够给您带来优势。
还值得一提的是正则表达式,因为它们可能是处理和搜索文本数据的最有用的方式。目前,正则表达式的 mbstring 实施正在测试中,这意味着不鼓励您在生产代码中使用它们。不过,如果您在 Oracle Database 10g 上运行,那么在 SQL REGEXP_LIKE、REGEXP_SUBSTR、REGEXP_INSTR 和 REGEXP_REPLACE 函数中,正则表达式是可用的。
还应当注意,虽然 PHP 字符数据类型与 Oracle 的 CHAR、VARCHAR2、LONG 和 CLOB 字符数据类型是完全可互操作的,但目前还没有为访问 NCHAR、NVARCHAR2 和 NCLOB 提供完全的支持。当选择数据或将数据插入 NCHAR 数据类型时,将执行到它们对应的 CHAR 数据类型的隐式转换。这意味着除非您的 NCHAR 数据类型的字符集(由国家字符集定义)是您的 CHAR 数据类型的字符集的一个扩展集(由数据库字符集定义),否则将出现数据损失。
外化文本
正如之前所提到的,使您的应用程序全球化的关键领域之一是它要被翻译成所有需要的语言。作为翻译用户界面文本的一种方式,我们必须从将此类文本从应用程序逻辑中外化出来开始,并且必须按每种语言把它放在某个能够容易地被检索到的地方。
在 PHP 中,没有一种合适的方法来完成这个操作,不过为您提供了许多选项。提供了 GNU gettext 函数,这些函数允许您外化文本,以便进行翻译,并且将一种平面文件格式用作消息。这些函数的详细信息有完整的文档说明,您可以在 GNU 网站上读到更多的信息。外化文本的另一种好办法是依靠一种基于模板的解决方案,如果您有大量的静态 HTML 文本想要翻译,这将非常理想。其它潜在的解决方案包括使用 Oracle 表和 SQL 查询来存储和检索文本。
根据要翻译的数据类型(无论它是在线帮助页面、UI 标签、错误消息或者其它文本),您可能会发现上述方法的一个组合是理想的解决办法。
将所有这些集中起来
现在我们让数据能够以 Unicode 格式来回流动,并且我们将我们所有的文本外部化并翻译成了我们打算支持的不同的语言,我们需要将所有这些集中起来,以达到我们能够部署我们的多语言应用程序的程度。
第一个要考虑的是您打算如何来确定用户的语言,以便您可以用他们的语言将文本提交给他们。根据您的应用程序的模型,存在许多不同的方法来实现这个目的。您可以要求用户登录并维护用户首选项,用户可以在这些首选项中指定他们的本地语言。作为另一种选择,您可以提供应用程序本身的一个界面,任意用户都可以在其中指定他们的语言,并且这种首选项将保存在 cookie 中,而无需要求用户登录。解决这个问题的最简单的方法也许是执行浏览器根据每一次请求发送的接受语言的首选项。在它的最简单的形式下,标题看起来像这样:
Accept-Language:language[-territory]
地域是可选的,这对定义语言的变量非常有用,例如,'fr-CA' 表示加拿大法语或者 'zh-TW' 表示台湾中文。这个值存储在 'HTTP_ACCEPT_LANGUAGE' Apache 环境变量中,您将需要确定如何最好地分析这个值。不过,这种方法的确有几个缺点:通常用户不在浏览器中设置他们的语言首选项,并且在许多情况下,不允许用户进行这些设置(例如,一个机场休息室的网吧对浏览器的限制)。提供一个应用程序级的用户语言和地域首选项始终是个不错的主意。
一旦您确定了首选语言,获取翻译过的资源将是一项简单的任务(无论它是一个外化的平面文件、PHP 数组元素或来自数据库的列值),这里主要的挑战是,确保您有一种方法将用于指示语言首选项的标记转换成用于标记翻译过的文件的标记,因此在这个领域中有一些需要考虑的问题。
Oracle 还提供了几种特性,这些特性能够在语言首选项已知时改善数据的表示。首先,用以下命令设置语言:
ALTER SESSION SET NLS_LANGUAGE=
应当注意的是,语言标记要求符合 Oracle 惯例。设置语言将返回翻译过的 Oracle 错误消息,返回翻译过的数据格式(如日和月的名称),并且将设定一个用于该语言的默认排列顺序。注意,PHP 本身不是一种全球化的应用程序,因此错误文本或日志数据将始终是英文的。接下来设置地域:
ALTER SESSION SET NLS_TERRITORY=
这将分配一种默认的数据格式,并且将为本地惯例格式化数字值(如用一个逗号或一个句点作为成千上万的分隔符)。对这些参数的深入讨论超出了本文的范围,因此关于您的翻译的问题,请参考 Oracle 全球化指南。
结论
本文是一个简短的介绍,便于应用程序开发人员在 PHP 和 Oracle 中创建完全全球化的应用程序时面对将遇到的一些问题。我们并没有打算把它写成一个完整的指南,几个主要的问题(例如,如何设计一个能够提交适用于双向语言的 HTML 页面的应用程序)超出了本文的范围,因为它们与 PHP 和 Oracle 没有直接关系。
还存在其它的与字符集相关的微妙问题(例如,您的应用程序需要用哪一个字符集来发送邮件),这些问题没有详细地讨论,但值得一提的是,PHP 为这个特定的问题提供了应用程序接口。大多数浏览器都得到 Unicode 的支持,但电子邮件早先就需要它的支持,尽管仍然有许多相当流行的电子邮件客户端不能提交 Unicode 文本。像大多数现代语言一样,PHP 是完全动态的,并且常常随着新的特性和程序包的增加而变化。始终跟上变化非常重要,特别是在每个版本通常都会进行改进的全球化支持领域中。
|