登录 |
  • 注册
  • [转] PHPV6的新特性

    2009年06月28日 下午 36:49 | 作者:pangyt

    初步了解 PHP V6 中的新特性

    级别: 中级

    Deepak Vohra, Web 开发人员, 独立顾问

    2009 年 6 月 04 日

    简介

    “PHP 的特性” 简单介绍了 PHP V6 中的新特性。在本文中,了解这些让 PHP V6 更加容易使用、更加安全和更加适合国际化的新特性。本文探索改进的 Unicode 支持,删除了几个函数、改进的扩展、引擎添加内容、改进的面向对象函数和 PHP V6 中的一些扩展。

    回页首

    增强对 Unicode 的支持

    PHP V6 中的主要特性是增强对 Unicode 的支持。目前,PHP 实际上是一个二进制处理器。PHP V5 没有提供原生的 Unicode 支持;它假定所有字符的长度都为 1 字节,这在处理非拉丁字符时会出现问题。您可以转换到 Unicode,但需要使用 mbstring 扩展,而默认的 PHP V5 或外部工具(比如 iconv)都不支持该扩展。

    PHP V5 有时不能正确显示文本,这取决于字符编码。Unicode 是由 Unicode Consortium 开发的行业标准,它能表示所有语言、程序和平台上的每一个字符。各个行业和标准都广泛支持 Unicode,这使得实现国际化多语言应用程序成为可能。Unicode 可以使用多种字符编码表示,最常见的是 UTF-8、UTF-16 和 UTF-32。

    PHP V6 原生地支持 Unicode (UTF-8),因此它的引擎、扩展和 API 都支持 Unicode。尽管已经添加将所有二进制字符串作为不同类型处理的支持,PHP V6 仍然能够将字符串作为 Unicode 处理。在 PHP V6 中,字符串的字面量为 Unicode,允许使用 Unicode 标识符,并且它的函数能够理解 Unicode 文本。PHP V6 能够在需要时支持 Unicode 字符串和其他编码之间的转换,并支持对 UTF-8 文件进行读写。下面给出一个从 UTF-8 文件读取内容的例子:

    $input = fopen(‘inputfile.txt’, ‘rt’);
    $str = fread($input, 100); // returns 100 Unicode characters

    向 UTF-8 文本文件写一个 Unicode 字符串 $uni:

    $output = fopen(‘outputfile.txt’, ‘wt’);
    fwrite($fp, $uni); // writes out data in UTF-8 encoding

    Unicode 模式

    在 PHP V5 中,可以根据需求将 Unicode 模式设置为打开或关闭。这表明非 Unicode 和 Unicode 变体类、方法和函数名必须存储在符号表中,这导致更多的开销。PHP V6 在 php.ini 中提供一个服务器通用的配置设置,以启用或禁用 Unicode 模式。

    Opcode 缓存用于缓存已编译的 PHP 代码。
    服务器通用的配置使引擎的某个部分的实现更加容易,引起的 opcode 缓存问题更少,并且提高运行速度,因为不需要在运行时转换符号名。

    PHP V6 保留了禁用 Unicode 模式的选项,因为一些字符串函数的 Unicode 实现变慢了 300%,这使整个应用程序的速度变慢 25%。PHP V6 在 php.ini 中提供一个运行时配置选项,用于启用或禁用 Unicode 语义。默认设置为 on。

    unicode.semantics = on

    将 unicode.semantics 设置为 off 并不意味着不使用 Unicode。当设置为 off 时,您仍然可用访问 Unicode 特性。当 Unicode 语义设置为 off,字符串的字面量是 8 位的字符串;1 个字符等于 1 个字节。

    unicode.semantics = off
    $str = “Hello, world!”;// ASCII encoding
    echo strlen($str);//result is 13

    如果设置为 unicode.semantics=on,那么字符串字面量使用 Unicode 类型。当 Unicode 语义设置为 on 时,一个字符可能 > 1 个字节。

    unicode.semantics = on
    $str = “Hello, world!”;// Unicode string
    echo strlen($str);// result is 13

    PHP V6 中的 unicode.runtime_encoding 配置选项指定在运行时执行 Unicode 和二进制字符串之间的转换时使用哪种编码。例如,将运行时编码设置为 iso-8859-1。

    unicode.runtime_encoding = iso-8859-1

    当与尚未支持 Unicode 的函数连接时,仍然需要使用运行时编码。PHP 脚本可以采用各种编码方式。PHP V6 提供 unicode.script_encoding 配置选项来指定脚本的编码。不管脚本的编码是什么,生成的字符串字面量都为 Unicode 类型。

    unicode.script_encoding = iso-8859-1
    $uni = “”; // Unicode string
    unicode.script_encoding = utf-8
    $uni = “Atildel”; // also Unicode string

    您还可以将 declare() 语句作为 PHP 脚本的第一个语句,这样也可以设置脚本的编码。declare() 构造器覆盖 php.ini 设置。declare() 设置不会传播到文件。

    unicode.script_encoding = utf-8
    declare(encoding=”iso-8859-1″);
    $uni = “Atildel”;// read as ISO-8859-1 string
    include “inputfile.php”;// file is read as UTF-8

    unicode.output_encoding 配置选项指定标准输出流所使用的编码,包括 echo、print 和 var_dump() 函数。输出流的编码被即时转换。unicode.output_encoding 配置选项不影响二进制字符串。

    unicode.output_encoding = utf-8
    unicode.script_encoding = iso-8859-1
    $unicode = “Atildel”; // Unicode string (from ISO-8859-1)
    echo $unicode; // converts $unicode to UTF-8 encoding
    echo b”Atildel”; // no conversion, raw contents

    如果启用了 Unicode 语义,HTTP 输入必须转换为 Unicode。GET 请求没有编码,并且也很少指定 POST 请求的编码。PHP V6 提供 unicode.http_input_encoding 配置选项来指定将 HTTP 输入转换为哪种 Unicode 编码。例如,将 HTTP 输入转换为 UTF-8:

    unicode.http_input_encoding = utf-8

    PHP 将尝试根据 unicode.http_input_encoding 设置进行解码。如果解码失败,PHP 将使用原始的二进制数据填充请求数组。unicode.filename_encoding 配置选项指定文件系统上文件和目录名的编码。

    unicode.filename_encoding = utf-8

    当输入和输出文件名时,与文件系统相关的函数执行所需的转换。PHP V6 提供 unicode.fallback_encoding 配置选项来指定 fallback 编码。

    unicode.fallback_encoding = iso-8859-1

    当没有给其他编码分配值时,将使用 fallback 编码。fallback 编码的默认值是 UTF-8。在 PHP V6 中,您可以使用不同的字符编码;当启用 unicode.semantics 选项时,PHP V6 将所有字符串字面量转换为 Unicode 字符串。您可以无缝地对输入和输出使用不同的字符。PHP V6 根据 php.ini 中指定的配置选项对 HTTP 输入和输出进行编码。数据库和用户代理程序能够收到所需的字符编码,而不需使用任何转换函数。

    您不一定要使用 Unicode 开发脚本来处理和输出 Unicode,但我们推荐这么做。如前所述,通过 unicode.script_encoding 配置选项指定脚本编码。为了在数据中(比如 MySQL)存储 Unicode 数据,数据库不需要配置为 UTF-8 编码,但这样做比较好。MySQL 数据库被配置为运行不同的字符集,PHP V6 将以 Unicode 编码发送查询,而 MySQL 将尝试转换它。

    两种字符串类型

    PHP V5 的字符串类型实现有很多字符串类型(二进制、字符串和 Unicode),并且有很多内部引擎函数和帮助函数的实现。PHP V6 只有两种字符串类型:二进制和 Unicode。unicode.semantics 转换(switch)决定字符串字面量的默认类型。如果启用了 unicode.semantics,默认的字符串字面量就为 Unicode。如果禁用该语义转换,默认字符串字面量的类型就为二进制。您可以显式地在 Unicode 和二进制字符串类型之间转换,也可以隐式地进行转换。使用 unicode.runtime_encoding 配置选项进行隐式转换。表 1 显示了如何进行显式转换。

    表 1. 转换字符串类型
    类型转换 描述
    (binary) 转换到二进制字符串类型
    (unicode) 转换到 Unicode 字符串类型
    (string) 如果 unicode.semantics 设置为 on,就转换到 Unicode 字符串类型,否则转换到二进制字符串类型

    PHP V6 在内部使用 IS_STRING 表示二进制字符串数据,以及使用 IS_UNICODE 表示 Unicode 字符串数据。

    在扩展中支持 Unicode

    PHP V6 包含了 Unicode/API,扩展可以借助它实现 Unicode 支持。需要向扩展模块结构添加一个标志,以表明它是否支持 Unicode。如果扩展不支持 Unicode,并且在 PHP 中启用了 Unicode 语义,那么在启动时将不加载它。Unicode 支持被添加到 PHP 数据对象中 (PDO)。

    ICU 库

    International Components for Unicode (ICU) 是一组提供 Unicode 和全球化支持的库。PHP V6 需要使用 ICU,但 ICU 是一个大型的库,将增加 PHP 的下载体积。PHP 要求使用 ICU V3.4,并且要求发布版提供 ICU 库。

    文件名编码

    不同的文件名可能使用不同的字符集进行编码。例如,Windows 文件名使用 UTF-16 编码。filename_encoding 设置指定期望的文件名编码。当 read 函数(比如 readdir() 和 readfile())遇到其编码在 filename_encoding 中不存在的文件名时,将返回一个二进制字符串。

    缓存校对器

    校对器(Collator)用于比较字符串。打开和关闭校对器需要引入开销。PHP V6 缓存了校对器,但限制存储在缓存中的对象数量,以防止使用过多的内存。PHP V6 在线程/进程通用缓存中存储最近打开的 3 个校对器。

    基于位置的函数

    对于使用系统位置的 PHP 函数,由于位置名在不同的平台中不一致甚至不可用,因此这是一个问题。PHP V6 仅在合适的地方使用基于位置的函数。基于位置的函数包括 strtoupper/strtolower 和 stristr 等。strtoupper 函数将字母表中的字符都转换为大写的。“字母表的字符顺序” 由当前的位置决定。

    字符集转换错误

    在字符编码之间执行转换可能发生转换错误,因为并不是源字符串中的所有字符都有必要存储在目标字符串中。PHP V6 为字符集转换失败提供一个额外的错误模式,它会在失败时抛出异常。PHP V6 为处理转换错误引入了两个新的 php.ini 配置选项:unicode.from_error_mode 和 unicode.from_error_subst_char。

    unicode.from_error_mode = U_INVALID_SUBSTITUTE
    unicode.from_error_subst_char = 3f

    unicode.from_error_subst_char 选项为无效的字符串指定了代替字符串的十六进制值;它的默认值是 3f。

    unicode.from_error_mode 选项指定错误模式,它在遇到无效字符串时采取行动。表 2 显示了 unicode.from_error_mode 可能使用的值。

    表 2. unicode.from_error_mode 值
    常量 值 说明
    U_INVALID_STOP 0 遇到第一个无效字符时停止
    U_INVALID_SKIP 1 跳过无效字符
    U_INVALID_SUBSTITUTE 2 替换无效字符;默认值
    U_INVALID_ESCAPE 3 对无效字符进行转义

    回页首

    删除了几个函数

    这个小节概述了在 PHP V6 中删除的几个函数。

    删除了 register_globals

    PHP V5 在 php.ini 中使用 register_globals 配置选项从环境和 HTTP 请求参数定义变量。不过,register_globals 生成不安全的代码。因此,在 PHP V6 中删除了 register_globals。

    register_globals 生成不安全代码的方式之一是 authenticate.php,它使用 $auth 变量对用户进行身份验证。因为 $auth 没有被初始化,所以它的值可能通过使用 register_globals 注入变量来定义,比如 GET authenticate.php?auth=1。

    if (authenticated_user()) {
    $auth = true;
    }
    if ($auth) {
    include "/data.php";
    }
    ?>

    register_globals 注入的变量也可能在会话中生成不安全的代码。看看以下代码,当设置了 $username 变量时,将在用户会话中显示一条欢迎消息。但是,当启用 register_globals 时,可能会使用 HTTP 请求参数注入 $username 变量。

    if (isset($_SESSION['username'])) {
    echo "Welcome {$_SESSION['username']}";
    } else {
    echo "Welcome Guest";
    }

    ?>

    所有使用 register_globals 的函数都被删除了,比如 session_register。如果需要导入请求变量,可以使用 import_request_variables。或者使用 $_POST、$_GET、$_COOKIE 和 $_REQUEST 变量。如果设置了 register_globals,PHP V6 将在启动时抛出 E_CORE_ERROR。

    删除了 magic_quotes

    在 PHP V5 中,当启用了 magic_quotes 配置选项时,它会在输入数据中自动将所有单引号(’)、双引号(”)、反斜杠(\)和 NULL 字符转义为带反斜杠的(\)PHP 脚本。Magic Quotes 减少了 SQL 注入(一种能够篡改 SQL 命令的技术)的风险。Magic Quotes 便于将数据插入到数据库中。不 使用 Magic Quotes 的理由是考虑到移植性、性能和方便性。

    您需要知道是否关闭了 Magic Quotes。如果 Magic Quotes 处于 on,而您认为它处于 off,应用程序就会出现额外的斜杠。如果 Magic Quotes 处于 off,而您认为它处于 on,应用程序就可能遭受 SQL 注入攻击。Magic Quotes 会损害性能,因为并不是所有转义数据都被插入到数据库。一个更好的代替办法是在运行时使用转义函数,比如 addslashes。Magic Quotes 可能带来不便,因为并不是所有数据都需要转义。可以使用 stripslashes 函数删除多余的斜杠。

    PHP V6 不再支持 magic_quotes、magic_quotes_sybase 和 magic_quotes_gpc 设置。如果在启动时发现这些设置,PHP 将发出一个 E_CORE_ERROR。此外,还删除了 get_magic_quotes_gpc() 函数。

    删除了 safe_mode

    在 PHP V5 中,safe_mode 配置选项可用于解决与共享服务器有关的安全问题。它确保需要打开或包含的文件的用户必须与执行这些文件的脚本的用户相同。safe_mode 并不是绝对安全的。

    考虑这样一个场景,一个应用程序使用 Web 服务器的 ID 并生成缓存文件或映像。如果该应用程序由客户机用户加载,PHP 脚本将不能打开缓存文件或映像,因为用户 ID 不匹配。并且,safe_mode 可以绕过库。

    在 PHP V6 中删除了 safe_mode 选项。如果检测到 safe_mode 设置,将抛出 E_CORE_ERROR。open_basedir 配置仍然可用,它限制 PHP 可以在特定目录树中打开的文件。

    删除不赞成使用的行为

    在 PHP V6 中删除了一些在早期的 PHP 版本中不赞成使用的行为。allow_call_time_pass_reference 配置指定在运行时通过引用传递参数时是否发出警告,它在 PHP V5 中就不推荐使用。在 PHP V6 中删除了 allow_call_time_pass_reference。

    如果要指定哪个参数是通过引用传递的,更好的方法是使用声明函数。在 PHP V6 中,call-time-pass-by-reference 将抛出一个 E_STRICT 错误。在 PHP V6 中,“var” 已经成为 “public” 的别名,并且删除了 E_STRICT 警告。受 “new lt;object-name>” 的影响,也删除了 return-by-reference。通过引用赋值 “new” 将抛出一个 E_STRICT 错误。例如,下面的语句将抛出 E_STRICT 错误:

    删除了 zend.ze1_compatibility_mode

    zend.ze1_compatibility_mode 配置启用了与 Zend Engine V1 (PHP V4) 的兼容性,它是在 PHP V5 引入的,方便了从 PHP V4 迁移到 PHP V5。

    在 PHP V6 中,已经删除了 zend.ze1_compatibility_mode 特性,如果检测到该设置,将抛出 E_CORE_ERROR。

    删除了 Freetype 1 和 GD 1 库

    FreeType 1 和 GD 1 是老版本的字体呈现和图像处理库。它们不再受到维护,并且已被更新版本的 FreeType 和 GD 代替。在 PHP V6 中,删除了对 FreeType 1 和 GD 1 库的支持。

    默认情况下不启用 dl()

    dl (string $library) 函数用于在运行时加载 PHP 扩展。在 PHP V5 中,可以在 php.ini 配置文件中启用或禁用 dl() 函数:enable_dl = On。

    dl() 函数会给从未加载过的模块带来问题。dl() 函数不能在多线程服务器中正常工作,比如 IIS 或 Zeus,因为在这些服务器中会自动禁用它。不过 dl() 函数在 PHP Command Line Interface (CLI) Server Application Programming Interface (SAPI) 中非常有用。

    在 PHP V6 中,dl() 函数在默认情况下是禁用的,但并没有删除它。SAPI 层可能会显式地注册 dl() 函数。

    FastCGI 模式

    FastCGI 提供很高的性能,如果禁用,将导致杂乱的代码。在 PHP V6 中不能禁用 FastCGI。

    删除了 register_long_arrays

    当启用 register_long_arrays 配置选项时,将注册很长的超级全局变量 — $HTTP_*_VARS 变量。$HTTP_*_VARS 变量是在 PHP V5 中为了向后兼容性而引入的,但不是必要的。使用更短的变量 $_GET、$_POST 和 $_SERVER 代替它会更好。在新的 PHP 版本中,register_long_arrays 设置和 $HTTP_*_VARS 没有得到好评。

    在 PHP V6 中,删除了 register_long_arrays 设置和 $HTTP_*_VARS 全局变量,如果检测到 register_long_arrays,将抛出 E_CORE_ERROR。

    删除 break $var

    动态的 break 操作数不起作用,如以下示例所示:

    for (..) { $var = rand(1, 2); break $var; }

    在 PHP V6 中,删除了动态的 break 操作数。取而代之的是,您可以为 $var 分配一个数字,如下所示:

    for (..) { $var = rand(1, 2); break 1; }

    回页首

    改进扩展

    扩展是 PHP 的主要组件。在 PHP V6 中,有几个组件得到了改进。

    包含在核心发布版中的 XML 扩展

    XMLReader 扩展提供一个 XML 解析器,用于阅读 XML 文件,该解析器在内部基于 SAX 解析。XMLWriter 为写 XML 文件提供一个 API。XMLReader 和 XMLWriter 使 XML 文件的读写更加容易。PHP V5 的核心发布版没有包含 XMLReader 和 XMLWriter。

    PHP V6 将在核心发布版中包含 XMLReader 和 XMLWriter 扩展,并且默认启用这两个扩展。

    正则表达式扩展

    PHP V5.x 为正则表达式提供两个库:ereg 和 Perl Compatible Regular Expression (PCRE)。ereg 库支持 Portable Operating System Interface (POSIX) 正则表达式,而 PCRE 扩展支持与 Perl 兼容的语法。打包的 ereg 库会造成问题,因此被转换成扩展并从核心发布版移动到 PHP Extension Community Library (PECL)。

    PCRE 扩展提供更多的特性,并且比 ereg 扩展快。PCRE 扩展在默认情况下是启用的,并且不可以禁用它。一些核心的 PHP 函数使用 POSIX 正则表达式。因为删除了 ereg 库,所以这些函数将使用 PCRE 表达式重新编写。PCRE 表达式提供一些与 ereg 库等效的函数。

    表 3. PCRE 扩展中的等效函数
    ereg() 函数 等效的 PCRE 函数
    ereg() preg_match()
    ereg_replace() preg_replace()

    MIME 类型检查扩展

    在 PHP V5 中,mime_magic 扩展用于媒体类型(media-type)检测,但不是很可靠。PECL 为 MIME 类型检查提供另一个扩展:Fileinfo。mime_magic 扩展将从核心发布版移动到 PECL。Fileinfo 扩展将添加到核心发布版,并默认启用。

    默认启用 SOAP 扩展

    SOAP 扩展(ext/soap)用于开发使用 Web 服务的 PHP 应用程序,包括 SOAP 服务器和 SOAP 客户机。在 PHP V5 中,SOAP 扩展不是默认启用的;开发人员必须配置 SOAP 扩展。在 PHP V6 中,默认启用 SOAP 扩展。PHP V6 还实现一些安全扩展。

    回页首

    引擎的添加内容

    在 PHP V6,将添加 64 位的整数,同时保留 32 位整数。64 位引擎的转换名为 int64。将使用一个静态标签扩展 break 关键字。将从 ?: 操作符丢弃中间参数。例如,在下面的表达式中,如果 $_GET['var'] 计算为 true,则 $var 设置为 3。如果 $_GET['var'] 计算为 false 或没有设置,则 $var 设置为 $_GET['var']。

    $var = $_GET['var'] ?: 3;

    如果 $_GET['var'] 没有设置,将抛出 E_NOTICE。PHP V6 将规定对多维数组使用 foreach 语法。

    $a = array(array(1, 2), array(3, 4));
    foreach( $a as $k => list($a, $b)) {}
    ?>

    在 PHP V5 中,{} 和 [] 都可用于访问字符串和数组元素中的字符。在 PHP V6 中,将删除 {},并且继续使用 [],而 [] 操作符将支持 substr()/array_slice() 函数。在 PHP V6 中,microtime() 在默认情况下返回一个浮点值。

    回页首

    OO 函数

    在 PHP V6 中对 OO 函数进行了一些改进。添加了关键字 static:: 用于延迟静态绑定,这意味着将执行运行时静态数字计算。

    支持名称空间

    PHP V5 不支持名称空间,而是在一个全局名称空间中定义所有函数和变量。PHP V6 通过关键字 namespace 添加对名称空间的支持。为了定义一个已经定义的类名或函数名,必须使用名称空间将新的类或函数与已经存在的类或函数区分开来。XMLReader 是一个已经存在的类,但是假设您仍然想定义另一个 XMLReader。您应该这样定义 XMLReader 类:

    namespace XmlNamespace { class XMLReader { }}
    $reader = new XmlNamespace\XMLReader();
    $reader2= new XMLReader();
    ?>

    为了在 XmlNamespace 中调用 XMLReader 类,需要使用前缀 XmlNamespace\。名称空间中可以包含类和函数,但不能包含变量。

    支持类型提示返回值

    PHP V5 仅支持类型提示(其中定义了类型)参数。PHP V6 支持类型提示返回值。

    静态和动态方法调用

    在 PHP V5 中,可以对方法进行静态或动态调用,而不管它是否标记为 static。例如,在下面的清单中,进行了静态和动态方法的调用:

    class classA { static function aStatic() { echo "static function\n"; }
    function bDynamic() { echo "dynamic function\n"; }}
    classA::aStatic("static call");
    classA::bDynamic("static call");
    $classA = new classA;
    $classA->aStatic(“dynamic call”);
    $classA->bDynamic(“dynamic call”);
    ?>

    在 PHP V6 中,使用静态调用语法调用动态函数将抛出一个 E_FATAL 错误,反之亦然。

    回页首

    PHP 的添加内容

    opcode 缓存改善了性能。另一个 PHP cache(APC)扩展曾经使用过一段时间,它也将添加到核心 PHP 发布版。默认情况下将不启用 APC 扩展。提供其他安全性的 Hardened PHP 补丁的一些特性已经合并到 PHP。

    现在,许多 PHP 扩展甚至对可修复错误使用 E_ERROR。E_ERROR 终止脚本的运行。因此,在 PHP V6 中,扩展中仅对不可修复的错误使用 E_ERROR。E_STRICT 错误表明这是语言级别的警告或错误,目前还没有包含在 E_ALL 中。在 PHP V6 中,已经将 E_STRICT 添加到 E_ALL。在 PHP V6 中,将为 PHP 添加 MySQL 原生驱动器。并且添加对大于 2 GB 的文件的支持。

    在 PHP V6 中,将删除 ASP 样式的标记 (<% %>)。但会保留 PHP 短标记 ()。

    回页首

    结束语

    本文介绍了 PHP V6 中的新特性和已更改的特性。其中最主要的特性就是对 Unicode 的支持。删除了一些配置选项,比如 register_globals、magic_quotes、safe_mode 和 register_long_arrays。改进了扩展支持和 OO 函数。PHP V5.3 支持 PHP V6 的 50% 的特性。

    来源:http://www.ibm.com/developerworks/cn/opensource/os-php-v6/index.html

    正则表达式元字符

    2009年06月28日 下午 43:10 | 作者:pangyt

    http://www.phpweblog.net/fuyongjie/archive/2009/03/11/6375.html

    [^xyz] 字符的补集,除xyz之外的字符
    ^-?[0-9]*.?[0-9]*$ 所有小树
    通用字符簇:
    [[:alpha:]] 任何字母
    [[:digit:]] 任何数字
    ============
    常用的正则表达式
    1、非负整数:”^\d+$”
    2、正整数:”^[0-9]*[1-9][0-9]*$”
    3、非正整数:”^((-\d+)|(0+))$”
    4、负整数:”^-[0-9]*[1-9][0-9]*$”
    5、整数:”^-?\d+$”
    6、非负浮点数:”^\d+(\.\d+)?$”
    7、正浮点数:”^((0-9)+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$”
    8、非正浮点数:”^((-\d+\.\d+)?)|(0+(\.0+)?))$”
    9、负浮点数:”^(-((正浮点数正则式)))$”
    10、英文字符串:”^[A-Za-z]+$”
    11、英文大写串:”^[A-Z]+$”
    12、英文小写串:”^[a-z]+$”
    13、英文字符数字串:”^[A-Za-z0-9]+$”
    14、英数字加下划线串:”^\w+$”
    15、E-mail地址:”^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$”
    16、URL:”^[a-zA-Z]+://(\w+(-\w+)*)(\.(\w+(-\w+)*))*(\?\s*)?$”
    =============
    Perl和POSIX表达式的写法区别
    preg ereg
    \d+ [0-9]*
    [abc\d+] abc[0-9]* //perl中字符都要加上分隔符[]或()或||
    \w [A-Za-z0-9_]
    [log]$i ereg中模式修正符i,m,s等无效,所以最接近的表达式是: log$
    perl中字符串必须包含在[],()之间或是紧邻^、$
    ====================================
    基本元字符
    元字符 说明
    . 匹配任意单个字符
    | 逻辑或操作符
    [] 定义一个字符集合,匹配该集合中的一个字符
    [^] 对字符集合求非(是对整个集合求非,而不是紧挨着^符号的字符)
    - 在字符集合中定义一个区间。如[A-Za-z]
    \ 对下一个字符转义。比如\n表示换行。

    数量元字符
    元字符 说明
    * 匹配前一个字符(子表达式)零次或多次
    *? *的懒惰型版本(防止正则表达式的“贪婪性”)
    + 匹配前一个字符或子表达式一次或多次
    +? +的懒惰型版本
    ? 匹配前一个字符或子表达式零次或一次
    {n} 匹配前一个字符或子表达式的n次重复,比如[A-Z]{6}表示匹配由六个大写字母组成的字符串。
    {m,n} 匹配至少m次至多n次
    {m,} 匹配至少m次
    {m,}? {m,}的懒惰型版本

    位置元字符
    元字符
    说明
    ^ 行首
    $ 行尾
    \< 单词开头
    \> 单词结尾
    \b 单词边界(单词的开头和结束)
    \B \b的反义

    特殊字符元字符
    元字符 说明
    [\b] 匹配一个退格字符
    \c 匹配一个控制字符
    \d 匹配任意一个数字字符,等价于[0-9]
    \D \d的反义
    \f 换页符
    \n 换行符
    \r 回车符
    \s 匹配一个空白字符
    \S \s的反义
    \t 制表符
    \v 垂直制表符
    \w 匹配任意字母、数字、下划线。等价于[A-Za-z0-9_]
    \W \w的反义
    \x 匹配一个十六进制数字
    \0 匹配一个八进制数字

    回溯引用和前后查找
    元字符 说明
    () 定义一个子表达式
    \1 第一个子表达式,同理\2表示第2个子表达式。\0通常表示整个正则表达式。
    ?= 向前查找
    ?<= 向后查找
    ?! 负向前查找
    ?!= 负向后查找
    ?() 条件(if then)
    ?()| 条件(if then else)

    ssh 和 rsync 的使用笔记

    2009年06月28日 下午 42:45 | 作者:pangyt

    参考资料:
    ssh http://suso.org/docs/shell/ssh.sdf
    rsync
    摘录:
    ssh-keygen -t dsa
    scp ~/.ssh/id_dsa.pub username@arvo.suso.org:.ssh/authorized_keys
    chmod 700 ~/.ssh
    chmod 600 ~/.ssh/authorized_keys
    ps auxw | grep ssh-agent
    ssh-add
    —– tcp端口转发—–
    ssh -L 3306:mysql.suso.org:3306 username@arvo.suso.org
    The -L (which means Local port) takes one argument of
    ::
    ———-
    ssh -R 8022:localhost:22 username@my.home.ip.address
    [转]:ssh的三个强大的端口转发命令:

    QUOTE:
    ssh -C -f -N -g -L listen_port:DST_Host:DST_port user@Tunnel_Host
    ssh -C -f -N -g -R listen_port:DST_Host:DST_port user@Tunnel_Host
    ssh -C -f -N -g -D listen_port user@Tunnel_Host
    -f Fork into background after authentication.
    后台认证用户/密码,通常和-N连用,不用登录到远程主机。

    -p port Connect to this port. Server must be on the same port.
    被登录的ssd服务器的sshd服务端口。

    -L port:host:hostport
    将本地机(客户机)的某个端口转发到远端指定机器的指定端口. 工作原理是这样的, 本地机器上分配了一个 socket 侦听 port 端口, 一旦这个端口上有了连接, 该连接就经过安全通道转发出去, 同时远程主机和 host 的 hostport 端口建立连接. 可以在配置文件中指定端口的转发. 只有 root 才能转发特权端口. IPv6 地址用另一种格式说明: port/host/hostport

    -R port:host:hostport
    将远程主机(服务器)的某个端口转发到本地端指定机器的指定端口. 工作原理是这样的, 远程主机上分配了一个 socket 侦听 port 端口, 一旦这个端口上有了连接, 该连接就经过安全通道转向出去, 同时本地主机和 host 的 hostport 端口建立连接. 可以在配置文件中指定端口的转发. 只有用 root 登录远程主机才能转发特权端口. IPv6 地址用另一种格式说明: port/host/hostport

    -D port
    指定一个本地机器 “动态的’’ 应用程序端口转发. 工作原理是这样的, 本地机器上分配了一个 socket 侦听 port 端口, 一旦这个端口上有了连接, 该连接就经过安全通道转发出去, 根据应用程序的协议可以判断出远程主机将和哪里连接. 目前支持 SOCKS4 协议, 将充当 SOCKS4 服务器. 只有 root 才能转发特权端口. 可以在配置文件中指定动态端口的转发.

    -C Enable compression.
    压缩数据传输。

    -N Do not execute a shell or command.
    不执行脚本或命令,通常与-f连用。

    -g Allow remote hosts to connect to forwarded ports.
    在-L/-R/-D参数中,允许远程主机连接到建立的转发的端口,如果不加这个参数,只允许本地主机建立连接。注:这个参数我在实践中似乎始终不起作用,参见III)

    实例说明:
    一台服务器提供ftp服务,因为ftp传输是明文密码,如果不做ssh端口之前,我们可以通过tcpdump命令很容易的捕捉到明文信息。所以我们要对21端口进行转发:
    (ftp-server)# ssh -CNfg -R 2121:localhost:21 root@10.4.2.50
    然后登录到10.4.2.50机器,我们可以通过netstat -an|grep :2121查看端口已经侦听
    (10.4.2.50)# ftp localhost 21就可以登录到ftp-server了,而且tcpdump无法捕获到有效的信息。
    2121端口任意选择,只要是机器上没有占用的端口就行

    来一个稍微复杂一点的,做网关的例子:
    假如内网有一台提供ftp(linux,port is 2121,称为A机器)的机器,通过网关服务器(linux,port is 8888,称为B机器)进去,现在外网有一台C机器需要访问网关服务器的某个端口(port is 21)来访问内网的ftp服务器。大家可以看到,其实这就像是一个基于ssh的防火墙程序,好,下面我们来具体操作:
    1。login A 机器
    # ssh -CNfg -R 8888:localhost:2121 root@B机器IP
    这样我们就在B机器上开了一个8888->2121的端口转换,但是由于8888端口只能侦听在localhost主机上,因此,虽然我们已经可以在B机器上使用
    # ftp localhost 8888 来访问真正的ftp服务器,但仍然无法提供给外网的机器访问

    2。login B机器
    # ssh -CNfg -L 21:localhost:8888 root@localhost
    这样做,是做本地机器上的21->8888端口转换,可以侦听在任何地址上的请求。
    2(1)。
    如果C机器也是一台linux机器,那也可以这样设置:
    # ssh -CNfg -R 21:localhost:8888 root@C机器IP

    3。使用C机器,可以是linux下的ftp命令,也可以是windows下的客户端软件都可以访问B机器的21端口来连接后台真正的ftp服务器(真正的端口是2121)
    如果是按照2(1)中的设置,则访问的地址为本机IP。

    简单介绍一个在linux下使用的设置ssh端口转换的程序

    http://gstm.sourceforge.net/?page_id=5

    [p_w_upload=1197]

    在windows机器下使用putty也可以建立端口转发
    假设从windows机器上将本地的8888端口转发到B的21端口,可以做如下设置
    Connection->SSH-Tunnels中可以设置putty的端口转发,Source port为listen_port,填8888,Destionation为DST_Host:DST_port,填写B机器IP:21,设置完了点Add.
    注意是local还是remote?
    [p_w_upload=1198]
    设置好后,我们可以在dos下用netstat命令看一下是否开启了本地的转发端口
    [p_w_upload=1199]
    接下来我们访问localhost的8888端口来访问ftp服务器了

    [转]PHP沉思录之五

    2009年06月28日 上午 38:34 | 作者:pangyt

    Session处理是所有的Web应用都必须面对的问题。PHP中对session有效期的处理,和其他的解决方案有着很大的不同,这是和PHP的工作机制相关的。

      在传统的client/server应用中,对于session失效的情况,可以交给网络协议自己来处理。无论是client端主动关闭连接,还是因为网络异常而导致的连接中断,server端都能够得到通知,触发连接中断的事件。只要编程响应这一事件,执行指定的操作即可。但对于web应用来说,情况却完全不一样。HTTP协议本身是无状态的,也就是说,每当client/server完成一次请求/响应的过程后,连接就会被断开。在断开连接以后,server并不知道client是否继续“在线”,还会继续发送下一次请求。换句话说,无论client端的用户已经关闭了浏览器窗口,还是用户仅仅在阅读当前网页并准备在下一秒钟继续浏览,或者用户因为Windows崩溃/停电/硬盘坏掉/网线被拔/地球爆炸而彻底无法再发送下一个请求,server都一无所知。(在HTTP 1.1中,浏览器可以通过keep-alive参数,来通知server不要在响应请求后主动断开连接,从而实现物理上的长连接。但是,这只是为了提高网络传输的性能而采取的措施,HTTP在逻辑上仍然是无状态的。)因此,只能通过某种模拟的方式来判断当前session是否有效。如果某个session 在超过一段时间后没有对server端发出请求,server都会判断用户已经“离线”,当前session失效,并触发连接中断的事件。要做到这一点,server需要运行一个后台线程,定时扫描所有的session信息,判断session是否已经超时。

      PHP处理session的原理也不例外,但是在具体的实现方式上,却与众不同。这是因为,由于PHP的工作机制,它并没有一个后台线程,来定时地扫描session信息并判断其是否失效。它的解决之道是,当一个有效请求发生时,PHP会根据某个概率,来决定是否调用一个GC(Garbage Collector)。GC的工作,就是扫描所有的session信息,用当前时间减去session的最后修改时间(modified date),同配置参数(configuration option)session.gc_maxlifetime的值进行比较,如果生存时间已经超过gc_maxlifetime,就把该session删除。这是很容易理解的,因为如果每次请求都要调用GC代码,那么PHP的效率就会低得令人吃不消了。这个概率取决于配置参数 session.gc_probability/session.gc_divisor的值(可以通过php.ini或者ini_set()函数来修改)。默认情况下,session.gc_probability = 1,session.gc_divisor=100,也就是说有1%的可能性会启动GC。

      这三个参数,session.gc_maxlifetime/session.gc_probability /session.gc_divisor都可以通过php.ini或者ini_set()函数来修改。但要记得,如果使用ini_set()函数的话,必须在每一个页面的开始处都调用ini_set()。

      这又导致了另外一个问题,gc_maxlifetime只能保证session生存的最短时间,并不能够保存在超过这一时间之后session 信息立即会得到删除。因为GC是按概率启动的,可能在某一个长时间内都没有被启动,那么大量的session在超过gc_maxlifetime以后仍然会有效。当然,发生这种情况的概率很小,但是如果你的应用对session的失效期要求很精确的话,这会导致很严重的问题。解决这个问题的一个方法是,把 session.gc_probability/session.gc_divisor的机率提高,如果提到100%,就会彻底解决这个问题,但显然会对性能造成严重的影响。另一个方法是放弃PHP的GC,自己在代码中判断当前session的生存时间,如果超出了 gc_maxlifetime,就清空当前session。

      PHP中的session有效期默认是1440秒(24分钟),也就是说,客户端超过24分钟没有刷新,当前session就会失效。要修改这个默认值,正确的解决办法是修改配置参数session.gc_maxlifetime。

      我曾经在网上搜索过这个问题的解决方式,找到的结果千奇百怪。有的说要设置“session_life_time”,据我知所,PHP中没有这个参数。有的说要调用session_set_cookie_params,或者设置session.cookie_lifetime,这仅仅用于设置 client端cookie的生存时间,换言之,只当client端cookie的生存时间小于server端的session生存期时,修改这个值才有效,并且最长不能超过server端的session生存期,原因很简单,当server端的session已经失效时,client端cookie的生存时间再长也是没有意义的。还有的说要调用 session_cache_expire,这个参数用于通知浏览器和proxy,当前页面的内容应该被缓存多长时间,和session的生存期并没有直接关系。

      听起来,这种解决方案很完美。但是,当你在实际中尝试修改session.gc_maxlifetime的值的时候,你很可能会发现,这个参数基本不起作用,session有效期仍然保持24分钟的默认值。甚至可能出现,在开发环境下工作正常,在服务器上却无效!

      为了彻底解决这个问题,需要对PHP的工作细节进行进一步的分析。

      在默认情况下,PHP 中的session信息会以文本文件的形式,被保存在系统的临时文件目录中。这个路径由配置参数session.save_path指定。在Linux 下,这一路径通常为\tmp,在 Windows下通常为C:\Windows\Temp。当服务器上有多个PHP应用时,它们会把自己的session文件都保存在同一个目录中(因为它们使用同一个session.save_path参数)。同样地,这些PHP应用也会按一定机率启动GC,扫描所有的session文件。

      问题在于,GC在工作时,并不会区分不同站点的session。举例言之,站点A的gc_maxlifetime设置为2小时,站点B的 gc_maxlifetime设置为默认的24分钟。当站点B的GC启动时,它会扫描公用的临时文件目录,把所有超过24分钟的session文件全部删除掉,而不管它们来自于站点A或B。这样,站点A的gc_maxlifetime设置就形同虚设了。

      找到问题所在,解决起来就很简单了。在页面的开始处调用session_save_path()函数,它能够修改 session.save_path参数,把保存session的目录指向一个专用的目录,例如\tmp\myapp\。这样,gc_maxlifetime参数就工作正常了。

      使用公用的session.save_path还会导致安全性问题,因为这意味着,同一台服务器上的其它PHP程序也可以读取你的站点的 session文件,这可能被用于黑客攻击。另一个问题是效率:在一个繁忙的站点中,可能存在成千上万个session文件,而把许多不同网站的 session文件都放在同一个目录下,无论是对单个文件的读写,还是遍历所有文件进行GC,都无疑会导致性能的降低。因此,如果你的PHP应用和别的 PHP应用运行在同一台服务器上的话,强烈建议你使用自己的session.save_path。

      严格地来说,这算是PHP的一个bug。当PHP在进行GC时,它应该区别来自不同站点的session文件,并应用不同的gc_maxlifetime值。目前,最新的PHP 5.2.X仍然存在这个问题。

      上文说到,在一个繁忙的站点中,可能存在成千上万个session文件,即使区分了不同站点的session.save_path目录,单个站点的session文件数目仍然可能导致效率问题。为了解决这一问题,可行的几种方法有:

    * 如果PHP运行在Linux系统下,使用ReiserFS文件系统取代默认的ext2/ext3文件系统。ReiserFS对于大量小文件的存取性能,比ext2/ext3有极大的提高。
    * 将session.save_path指向一个内存路径。这意味着,session文件的读写只在内存中进行,而不执行磁盘操作。
    * session.save_path 接受一个额外的N参数,用于指定目录的级数。例如,“5;/tmp” 将导致创建类似这样的session文件:/tmp/4/b/1/e/3 /sess_4b1e384ad74619bd212e236e52a5a174If。具体的说明,请参见:http://cn.php.net/manual/en/session.configuration.php#ini.session.save-path
    * 终极的解决方案,是放弃PHP的session处理机制,自己编码接管所有的session处理操作,通过 session_set_save_handler()函数来实现。通过自己接管session处理,可以将所有的session保存在专门的数据库(往往使用内存表)中,从而彻底解决session文件带来的问题,并且可以方便地实现session的共享和复制。这也是大型的PHP应用一般会使用的方式。关于session_set_save_handler()函数的使用,网上和相关图书都有详细的说明,这里不再赘述。值得一提的是,即使在这种方式下,启动GC的概率仍然取决于session.gc_probability/session.gc_divisor。

    [转]PHP沉思录之六 drupal

    2009年06月28日 上午 34:34 | 作者:pangyt

    Drupal是一个基于PHP的开源CMS系统,也是我认为技术上实现得最好的一个PHP应用。Drupal的架构非常优秀,通过微内核+plugin的方式,实现了极佳的扩展性,从而使Drupal远远超出一般的CMS这一范畴。从这个意义上来说,把Drupal称为Web OS似乎更加合适一些。关于Drupal,有太多的话可以说,也许我会在以后的时间里写一篇文章对它进行专门的讨论。但是在本文中,我想讨论的,是 Drupal社区中的每一个人都会面对,但不是每一个人都对其有清晰认识的问题,即Drupal的性能问题。

    因为客户需求,我曾经对Drupal做过比较全面的测试。当时的环境是双服务器(DB server+Web Server),硬件配置都是单CPU+4G。数据库里面有几千条Node记录。用JMeter对各种情况下(开/关各种cache模块,logged user/anonymous user)不同页面的读取和写入操作都进行过测试。

    测试的结果可能和很多人印象中不一样。两个主要的结果如下:

    1. Logged user和anonymous user的性能差距非常大。同一个页面,logged user的RPS(Requests per second)一般不超过20,而启用了cache的anonymous user的RPS在100多,当使用了file-based cache以后,甚至能超过300。

    2. 数据库压力相对较小。由于Drupal把大量可配置的内容都放在数据库中,因此往往容易产生这样一种印象,即Drupal对数据库要求应该是很高的。但事实上,无论是cache还是非cache模式,DB server的压力都相当小(CPU在10%以下),而Web Server的CPU在80%以上。跟踪所有的db query的执行时间后,也证明了这一点(全部db query的执行时间只占页面生成时间的一小部分)。

    经过反复的测试和思考,我得出了一些结论。很明显,Drupal在大量logged user并发情况下的瓶颈,在于执行Drupal代码的CPU时间,而不是在于数据库或者其他地方。之所以出现这样的情况,和PHP本身的执行机制和 Drupal的实现方式有关。Drupal在生成一个非cached的页面时,不管这个页面多么简单,都要执行一个完整的bootstrap过程,即使只启用了最少的模块,这个过程也要调用几十个PHP文件,执行成千上万行PHP代码。而PHP的机制又决定了没有任何PHP代码或者对象能够驻留内存,每次响应请求都必须执行完整的初始化工作。而anonymous user之所以快,是因为Drupal在执行cached page的时候,不会执行完整的bootstrap过程,它先检查是否cached page,是的话就读取缓存,然后结束工作。这样当然就快了。
    以这个结论为前提,可以解释一些事情:

    1. 为什么Drupal的性能在各种环境下相差并不多。无论是双服务器,单服务器,甚至内存非常小的虚拟机,logged user的RPS值往往总是在10~20之间。数据库里面有几百条,或者几十万条记录,影响也不大。因为瓶颈并不在于DB或者内存,而是在于执行代码的过程。

    2. 为什么使用APC/XCache这样的代码优化程序,能够得到极大的性能提升。在我自己的虚拟机环境上,RPS从3~4提升到了12。因为它提升的是PHP代码的执行时间。

    从这个结论出发,列出一些对优化Drupal的logged user性能有明显作用和没有明显作用的措施:

    I. 没有明显作用的:
    1. 加内存。在并发数只有10+的时候,即使每个请求占20M内存,也只有200M+内存而已。
    2. DB server和Web server分开,或者增强DB server的配置。一台中等性能的mysql服务器,应付200~300的并发是很轻松的事情,在并发数只有10+的时候,db server实际上是很空闲的。
    3. 基础软件的优化,例如从Windows转移到Linux,从apache转移到Lighttpd,从MySQL迁移到其他数据库,除了从Windows转移到 Linux会有比较明显的提升以外(因为PHP在Linux上的效率比在Windows上要好),其它的措施可能会快一些,但不会有大幅度的提高,因为瓶颈不在那里。

    II. 有明显作用的:

    1. 使用APC/XCache这样的代码优化程序,速度会有几倍的提升。估计大家都已经这样做过了。
    2. 增加web server的CPU数量。双核的肯定比单核的快,4个CPU肯定比2个CPU快得多。
    3. 使用多web server+单db server的配置,把代码执行的压力分散到不同的web server上。上文说到,单台db server可以轻松应付200+的并发,这意味着理论上可以支持10台以上的web server。
    4. 使用Quercus这样的引擎,把PHP代码编译成Java,再在Java VM中运行,理论上会有很大的提高。原因是,第一,Java的运行效率比PHP高,第二,Java代码是可以cache的,不需要每次都重新加载。这里有个测试结果:http://www.workhabit.org/resin-backed-php-drives-4x-performance- improvements-drupal 。Drupal在Quercus下有4倍的性能提高,但是这个数字跟Drupal在打开APC/eAccelerator下的提升差不多,所以可能没有太大的实用价值。

    另外一种思路是代码本身的优化。
    使用cache API基本上是没有意义的,因为对于logger user,Drupal不会调用cache API。Drupal.org上有人提出,即使是logged user,有很多页面也是不用定制化的,这意味着可以cache它们。但是Drupal没有提供这样一种机制。只要是logged user,Drupal就会执行完整的bootstrap过程,即使只打印出一个hello world,因此实际上你没有办法在logged user状态下cache单个页面。

    到目前最新版本的Drupal(Drupal 6.4)为止,对于logger user,Drupal只提供了一种cache功能,就是可以将部分block设置为可cache的。在block占用大量服务器时间的情况下,block cache能够有效地提高效率。但是,由于block cache对于bootstrap过程并无影响,因此当瓶颈在于bootstrap本身时,Block cache是无能为力的。
    在Drupal.org的社区,关于logger user的cache问题,一直处于热烈的讨论之中。基本的结论是,由于Drupal的架构就是这样,目前没有很好的解决方案,只能期待Drupal在以后的版本中进行改进了。

    我研究了一下Drupal的bootstrap过程,发现也许这样是可行的:实现hook_boot函数,这是bootstrap中执行最靠前的一个函数,它被调用时,bootstrap的大部分过程还没有执行。在hook_boot中,检查当前页面是否需要cache,如果是,直接读 cache生成页面,然后调用exit()强行结束。这在理论上是可行的,但太过hack了一点。
    Drupal的情况是这样,那么其他的PHP框架,尤其是半官方的Zend Framework,性能如何呢?通过搜索,我在网上找到了一份PHP framework comparison benchmarks,网址见:http://www.avnetlabs.com/php/php-framework-comparison- benchmarks。根据这份报告的数据,Zend Framework的性能只有原生PHP的10%,如果没有用APC,连3%都不到。当然,这份报告的数据不一定详尽,Zend Framework在不同环境下的表现应该也会有出入。但是,Zend Framework的性能大幅度落后于Baseline PHP,应该是确凿无疑的。

    为什么PHP主流框架的性能都存在着这样的问题呢?其实这也不难理解。回顾PHP沉思录系列第一篇中对于PHP工作模型的讨论,由于PHP没有驻留内存的进程,所以每一个request发生时,都必须初始化所有的对象,这导致大量的时间被耗费在进程代码的执行过程中。当PHP程序仅仅是简单的脚本时,这无关紧要,但是在结构复杂的架构中,由于每次处理request都要重复调用成千上万行代码,这一问题就变得非常突出了。而且,除非PHP以后的版本对这一机制进行改进,否则这个问题无法得到彻底的解决。

    那么,这是否意味着,PHP只能适用于小型的网站,而无法在高并发量的大型网站施展拳脚呢?当然不是这样。事实上,在Yahoo和其他许多知名的巨型网站上,都大量地使用了PHP。原因在于,PHP仅仅被用作一个内容生成器,生成的内容会被转化为静态文本,绝大多数用户浏览的都是被cache的静态文本。这就和PHP程序的性能毫无关系了。但是,当用户并不是仅仅进行浏览,而是需要频繁地和网站进行互动时,PHP的性能不但无法比拟C和Java,甚至无法与同为脚本语言的Python和Ruby相比。也就是说,PHP更适合于新闻门户这样的内容发布站点,而不是web 2.0应用的首选。

    在本系列文章告一段落的时候,我们看到的是PHP的局限性。热爱PHP的人们可能会对此觉得沮丧。但是,这并无损于PHP作为一门优秀语言的声誉。尺有所短,寸有所长,对于我们熟悉和喜爱的工具,我们更应该了解它们的局限,这也有利于我们更有效地使用它们。

    [转]PHP沉思录之三

    2009年06月28日 上午 32:33 | 作者:pangyt

    左轻候:
    在任何Web应用中,如何将程序代码和界面设计,或者说,将逻辑层和表现层分离开来,都会是一个问题。对于PHP这种类型的嵌入网页的脚本语言,这一问题尤其突出。在新手编写的代码中,把访问数据库的代码和操纵HTML元素的代码写在同一个页面里,是很常见的情况。为了避免这一问题,开发者倾向于将涉及业务逻辑的代码封装在某些单独的库文件中,再在负责显示界面的文件中将它们include进来。但是,这仍然无法避免在显示界面的文件中包含大量的PHP代码。究其所以然,是因为除了涉及业务逻辑的代码以外,即使仅仅在显示层,也往往涉及到复杂的显示逻辑。在一个典型的显示页面中,程序需要先包含所有必需的库文件,初始化上下文环境,创建相关业务逻辑对象(假如数据库访问代码已经被业务逻辑对象封装,可以节省数据库相关的代码),最后在HTML的空隙中把对象格式化为HTML元素进行显示。于是我们看到了无数这样的页面,在第一行HTML开始之前,就已经包含了数十行甚至更多的PHP代码,在HTML的内部,仍然充满了各种各样的PHP代码。因此,PHP代码和HTML代码搅和在一块的问题仍然无法解决,对HTML的修改仍然可能导致整个PHP程序崩溃。更加麻烦的是,这种不清晰的结构妨碍了PHP应用在规模上的进一步扩张。

    为了解决这一问题,模板(template)技术应运而生。模板技术的基本原理是,通过一个解析器(parser),读取指定的模板文件(包含了某些特定标签的HTML文件),将这些标签替换为相关的PHP变量,再输出为标准的HTML。通过这种方式,不但分离了业务逻辑层和表现层,而且也尽可能地分离了显示逻辑和HTML代码。通过替换不同的模板文件,可以方便地生成各种格式的输出,例如HTML,XML,WML,后期稍加处理甚至可以生成PDF和 Flash。早期较为著名的PHP模板引擎有PHPLib中的Template和FastTemplate。

    但是,模板技术也有其先天的缺陷。
      
    无法彻底分离逻辑。显示逻辑和HTML代码很难通过简单的标签替换,实现彻底的分离。例如,遍历并显示一个数组,在PHP中可以用简单的foreach语句实现,但是使用模板时,就需要进行对整个模板文件进行多次替换操作,造成效率的极大降低;或者根据不同的数据值显示不同的格式,如果模板文件完全不包含 PHP代码,那么将很难做到这一点。

    解析导致的性能损失。由于每次PHP页面被访问时,解析器都必须对模板文件进行替换操作,无疑会降低PHP应用的性能。尤其在多次的替换操作时更是如此。因此,不使用模板比使用模板往往更加快速,这也是许多PHP程序员摒弃模板技术的原因之一。
      
    在经过数年的发展之后,“编译型”的模板技术渐渐占据了主流。所谓“编译型”,是指解析器读取模板文件以后,并不直接生成静态HTML,而是 “编译”成一个新的PHP文件,并将它保存起来。以后访问该页面时,模板引擎会直接执行“编译”后的PHP文件。Smarty是这种模板引擎的代表。

    针对以上的两个问题,Smarty作了如下处理:
      
    独立语法。Smarty实现了一套自己的语法,这套语法不但支持变量替换和简单的判断,而且支持循环,修饰符(modifier),内置了很多功能强大的函数,而且还支持自定义函数。这套系统保证Smarty能够完全独立地处理显示输出,无须再和PHP有什么瓜葛。事实上,在Smarty模板中,是不可以直接使用PHP代码的(通过显式定义可以使用),这也是一种强制分离逻辑层和表现层的方式。(理论上来说,Smarty的模板文件也可以应用于其它语言。)但是,这种解决方式也受到了指责,因为Smarty的语法过于强大,几乎变成了一门新的语言,指责者认为,这反而增加了复杂性。但是,根据作者的实际经验,Smarty的语法不但非常简单直观,而且只需要掌握一些最初级的语法,就足可以应付绝大多数的应用。即使是不懂编程的网页设计师,也很容易就能够掌握。

    编译机制。Smarty的“编译”机制,节省了用于反复解析模板文件的时间,极大地提高了速度。由于“编译”后生成的是标准的PHP文件,因此从理论上来说,执行的速度不会低于没有模板的PHP应用的速度。在一些和解析型模板引擎进行的对比测试中,Smarty在第一次访问时落后,但是在以后的访问中速度远远超出竞争对手。而且,这种编译过程是智能的,在模板文件的内容被改变后,Smarty会自动重新编译,因此对于开发者来说,编译过程完全无需人工干预。另外,如果你愿意的话,生成的PHP文件还可以方便地应用于Zend Accelerator这样的工具,进行二次编译。
      
    除此之外,Smarty还拥有其他一些优秀的特性:
      
    缓存机制。由于实现了编译机制,在接收到对某个模板文件的访问请求时,Smarty会自动将它重定向到编译后的PHP文件。但是,这也意味着,Smarty也可以将它重定向到任何其他的文件——例如静态的HTML文件。在此基础之上,Smarty实现了自己的基于页面的缓存机制。 Smarty能够将编译后的PHP文件产生的结果——静态HTML——保存起来,将重复发送的请求直接重定向给它,这意味着对于第一次之后的请求,不需要执行任何PHP代码(Smarty本身的代码当然除外)。对于不需要频繁更新的页面(我们知道这种网页往往在整个网站中占大多数),通过这种缓存机制获取的性能提升是惊人的。而且,由于它是在页面级实现的,因此完全无须涉及到复杂的对象级缓存问题,保持了逻辑上的简单性。

    可配置性。Smarty在开发之初就将高度的可配置性作为自己的一个设计目标。它本身以100%的PHP编写,以源代码的方式发行,只需要将Smarty 简单地拷贝到你的文件路径中,就可以使用了。Smarty的各项配置变量,都可以通过修改config文件或者手动编码进行定制。例如,Smarty默认的定界符是花括号({}),但是这往往和Javascript以及CSS中的花括号冲突。为了解决这一问题,可以简单地将默认定界符修改为其他的字符(例如ASP风格的“<%”和“%>”)。

    可扩展性。Smart的实现基于面向对象的架构,并且提供了插件机制,非常便于用户修改和扩展其默认的认为。当然,你也可以直接修改它的源代码来达到目的。(对于基于脚本语言的开源应用来说,这是非常惬意的,因为你甚至不需要重新编译。)
      
    让我们来看一个最简单的Smarty应用。这个应用包括两个文件:
      
    TestSmarty.php 调用Smarty类库,初始化变量,并解析相应的模板文件
    TestSmarty.tpl 模板文件,其实就是包含了Smarty标签的HTML,放在指定的模板目录下,默认是./templates
      
    TestSmarty.php的内容如下:
    Html代码
      
         include_once("./smarty/Smarty.class.php");
      
      $Smarty = new Smarty();
      $Smarty->assign(“HelloStr”, “Hello, world”);
      $Smarty->display(“TestSmarty.tpl”);
      ?>

      
         include_once("./smarty/Smarty.class.php");
      
      $Smarty = new Smarty();
      $Smarty->assign(“HelloStr”, “Hello, world”);
      $Smarty->display(“TestSmarty.tpl”);
      ?>
      

    这个文件的内容非常简单,任何有过PHP经验的程序员都应该能够理解:首先将Smarty类库所在的文件include进来,然后创建一个新的Smarty对象,并对HelloStr变量进行赋值,最后解析TestSmarty.tpl文件。
    TestSmarty.php的内容如下:
      
    Html代码
    This is a string from Smarty: {$HelloStr}

    This is a string from Smarty: {$HelloStr}
      
       解析的结果为:
      
    Html代码
    This is a string from Smarty: Hello, world

    This is a string from Smarty: Hello, world
      
       此时检查存放编译后的PHP文件的子目录(默认是./templates_c),可以找到一个名叫%%65^650^65099D8B%%TestSmarty.tpl.php的文件,内容如下:
      
    compiled from TestSmarty.tpl */ ?>
    This is a string from Smarty:
    _tpl_vars['HelloStr']; ?>
      
    这就是Smarty引擎编译生成的结果。
    为了启用缓存,可以在TestSmarty.php文件中加入这么一行(当然必须在display方法之前):
      
    Html代码
    $Smarty->caching = 1;

    $Smarty->caching = 1;
      
    重新访问该页面,然后检查存放缓存文件的子目录(默认是./cache),可以找到一个名叫%%65^650^65099D8B%%TestSmarty.tpl的文件,内容如下:
      
    136
       a:4:{s:8:”template”;a:1:{s:14:”TestSmarty.tpl”;b:1;}s:9:”timestamp”;i:1186888266;s:7:”expires”;i:1186891866;s:13:”cache_serials”;a:0:{}}This is a string from Smarty: Hello, world
      
    这就是生成的缓存文件,在静态的HTML文件之前,包含了已经序列化的PHP信息。虽然这些信息无法被直接阅读,但是多少还是能够猜测出来:模板的子目录,模板文件名,时间戳,生存期(过期时间),等等。如果读者有兴趣研究它们的详细定义,可以阅读Smarty的源代码。
    注意,上述信息中包含了一项:生存期,即当前缓存在多长时间以后过期。Smarty默认的生存期是1小时,即3600秒。可以通过修改Smarty属性来设置生命期,代码如下:
      
    Html代码
    $Smarty->cache_lifetime = 1800;

    $Smarty->cache_lifetime = 1800;
      
    时间单位是秒,设置为1800表示当前缓存半小时后过期。
    Smarty还支持为同一个模板创建多个缓存实例,这在实际应用中是非常常见的。举例来说,假设某个博客系统中,显示article的页面为 Article.php,对应的模板文件为Article.tpl。但是,article页面的内容根据不同的article ID而不同,因此,必须为同一个页面创建不同的缓存实例。Smarty可以轻松做到这一点:
      
    Html代码
    $Smarty->display(“Article.tpl”, $ArticleId);

    $Smarty->display(“Article.tpl”, $ArticleId);
      
    只要将一个唯一标识符(在这个例子中是article的ID)作为第二个参数传给display方法,Smarty就会自动完成一切。
      
    Smarty出现的时间虽然较老牌的PHPLib Template和FastTemplate为晚,但是发展非常迅速,而且已经成为PHP的官方子项目,拥有二级域名 http://smarty.php.net/。正如它的官方站点上所说,与其说Smarty是一个模板引擎,不如说它是一个表现层的 Framework。这句话极为重要。

    作者个人认为,Smarty诞生和逐渐取得主流地位的意义,不仅仅是提供了一个优秀的模板引擎,而是表示PHP在解决更大规模的应用上迈出了坚实的一步。如果说Java是一开始就将解决大规模应用作为自己的设计目标的话,那么PHP就是在开源社区的推动下,在不断的改进中逐渐接近这一目标。当一个PHP应用采用了Smarty这样优秀和成熟的表现层解决方案,以及其他一些类似的技术(例如好的ORM解决方案)以后,它能够解决的问题的规模就比过去大得多了。通过这些现象,我们可以看到,PHP,或者说LAMP,正在以稳健而持续的步伐,向企业级应用迈进。

    PHP沉思录之二

    2009年06月28日 上午 20:18 | 作者:pangyt

    左轻候 : http://blog.csdn.net/phphot/archive/2008/02/19/2105635.aspx
    PME模型

    在大规模的程序设计中,组件(component)已经成为一种非常流行的技术。常见的组件技术都基于PME模型,即属性(Property)、方法(Method)和事件(Event)。

    旧电脑回收基于PME的组件技术可以方便地实现IoC(Inversion of Control,控制反转),是从IDE的plugin到应用服务器的“热发布”等许多技术的基础。
    PHP从版本5开始,大大完善了对OO的支持,以前不能被应用的许多pattern现在都可以在PHP5中实现。因此,是否能够实现基于PHP的组件技术,也就成了一个值得讨论的问题。
    下面对PHP对于PME模型的支持,逐一进行讨论:

     属性(Property)
    PHP并不支持类似Delphi或者C#的property语法,但这并不是问题。Java也不支持property语法,但是通过getXXX()和setXXX()的命名约定,同样可以支持属性。

    PHP也可以通过这一方式来支持属性。但是,PHP提供了另一种也许更好的方法,那就是__set()和__get()方法。
    在PHP中,每一个class都会自动继承__set()和__get()方法。它们的定义如下:

    void __set ( string name, mixed value )
    mixed __get ( string name )

    这两个方法将在下列情况下被触发:当程序访问一个当前类没有显式定义的属性时。在这个时候,被访问的属性名称作为参数被传入相应的方法。任何类都可以重载__set()和__get()方法,以实现自己的功能。
    如下例:

    class PropertyTester {

    public function __get($PropName) {
    echo “Getting Property $PropNamen”;
    }

    public function __set($PropName, $Value) {
    echo “Setting Property $PropName to ‘$Value’n”;
    }
    }

    $Prop = new PropertyTester();
    $Prop->Name;
    $Prop->Name = “some string”;

    类 PropertyTester重载了__set()和__get()方法,为了测试,仅仅将参数打印输出,没有做更多的工作。测试代码创建了 PropertyTester类的实例,并试图读写它并不存在的一个属性Name。此时,__set()和__get()相继被调用,并打印出相关参数。它的输出结果如下:

    Getting Property Name
    Setting Property Name to ’some string’
    旧电脑回收基于这种机制,我们可以将属性的值放在一个private的List中,在读写属性时,通过重载__set()和__get()方法,读写List中的属性值。
    但是,__set()和__get()方法的有趣之处远不止及。通过这两个方法,可以实现动态属性,也就是不在程序中显式定义,而是在运行时动态生成的属性。只要想想这种技术在OR Mapping中的作用就能够明白它的重要性了。配合__call()方法(用于实现动态方法,在下一节中详述),它能够取代丑陋的代码生成器(code generator)的大部分功能。

     方法(Method)
    PHP对方法的支持比较简单,没有太多可以讨论的。值得一提的是,PHP从版本5开始支持类的静态方法(static method),这使得程序员再也不用无谓地增加许多全局函数了。

     事件(Event)
    事件也许是PHP遇到的最复杂的问题。PHP并没有在语法层面提供对事件的支持,我们只能考虑通过别的途径来实现。因此,我们需要先对事件的概念和其他语言对事件的实现方式进行讨论。

    事件模型可以简述如下:充当事件触发者的代码本身并不处理事件,而仅仅是在事件发生时,把程序控制权转交给事件的处理者,在事件处理完成后,再收回控制权。事件触发者本身并不知道事件将会被如何处理,在大多数情况下,事件触发者的代码要先于事件处理者的代码被完成。

    在传统的面向过程的语言(例如C或者PASCAL)中,事件可以通过函数指针来实现。具体来说,事件触发者定义一个函数指针,这个函数指针可以在以后被指向某个处理事件的函数。在事件发生时,调用该函数指针指向的处理函数,并将事件的上下文作为参数传入。处理完成后,控制权再回到事件触发者。

    在面向对象的语言中,方法指针(指向某个类的方法的指针)取代了函数指针。以Delphi为例,事件处理的例子如下:

    type
    TNotifyEvent = procedure(Sender: TObject) of object;
    TMainForm = class(TForm)
    procedure ButtonClick(Sender: TObject);

    End;
    Var
    MainForm: TMainForm;
    OnClick: TNotifyEvent;

    可以看出,TNotifyEvent被定义为所谓的过程类型(Procedural Type),事实上就是一个方法指针。TMainForm的ButtonClick方法是一个事件处理者,符合TNotifyEvent的签名。 OnClick是一个事件触发者。在实际使用时,通过如下代码:

    OnClick := MainForm.ButtonClick;

    将MainForm.ButtonClick方法绑定到了OnClick事件。当OnClick事件触发时,MainForm.ButtonClick方法将被调用,并且将Sender(触发事件的组件对象)作为参数传入。
    回到PHP,由于PHP不支持指针,因此无法使用函数指针这一技术。但是,PHP支持所谓的“函数变量”,可以把函数赋予某个变量,其作用类似于函数指针。如下例:

    function EventHandler($Sender) {
    echo “Calling EventHandler(), arv = $Sendern”;
    }

    $Func = ‘EventHandler’;
    $Func(‘Sender Name’);

    由于PHP是一种动态语言,变量可以为任何类型,所以无须先定义函数指针的类型作为事件的签名。直接定义了一个函数EventHandler作为事件处理者,然后将它赋予变量$Func(注意直接使用了字符串形式的函数名),最后触发该事件,并将一个字符串“Sender Name”传给它作为参数。输出的结果是:

    Calling EventHandler(), arv = Sender Name

    同样地,PHP也提供了类似方法指针的机制。如下例:

    Class EventHandler {

    public function DoEvent($Sender) {
    echo “Calling EventHandler.DoEvent(), arg = $Sendern”;
    }
    }

    $EventHanler = new EventHandler();
    $HandlerObject = $EventHanler;
    $Method = ‘DoEvent’;
    $HandlerObject->$Method(‘Sender Name’);

    由于PHP中没有能够直接引用对象方法的变量,因此需要使用两个变量来间接实现:$HandlerObject指向对象,$Method指向对象方法。通过$HandlerObject->$Method方式的调用,可以动态地指向任何对象方法。
    为了使代码更加优雅和更适合复用,可以定义一个专门的类NotifyEvent,并使用一段新的调用代码:

    final class NotifyEvent {

    private $HandlerObject;
    private $Method;

    public function __construct($HandlerObject, $Method) {
    $this->HandlerObject = $HandlerObject;
    $this->Method = $Method;
    }

    public function Call($Sender) {
    $Method = $this->Method;
    $this->HandlerObject->$Method($Sender);
    }
    }

    $EventHanler = new EventHandler();
    $NotifyEvent = new NotifyEvent($EventHanler, ‘DoEvent’);
    $NotifyEvent->Call(‘Sender Name’);

    NotifyEvent类定义了两个私有变量$HandlerObject和$Method,分别指向事件处理者对象和处理方法。在构造函数中对这两个变量赋值,再通过Call方法来调用。

    熟悉C#的读者可以发现,NotifyEvent类与C#中的Delegate十分类似。Delegate超过NotifyEvent的地方在于支持多播(Multicast),也就是一个事件可以绑定多个事件处理者。只要事件触发者自己维护一个NotifyEvent对象数组,支持多播也不是一件难事。

    至此,PHP对事件的支持已经得到了比较圆满的解决。但是,人的求知欲是无穷无尽的。还有没有可能通过其他的方式来实现事件呢?

    除了方法指针,接口(interface)也可以用于实现事件。在Java中,这种技术被广泛应用。其核心思想是,将事件处理者的处理函数定义抽象为一个接口(相当于函数指针的签名),事件触发者针对这个接口编程,事件处理者则实现这个接口。

    这种方式的好处在于,不需要语言支持函数指针或方法指针,让代码显得更加清晰和优雅,缺陷在于,实现同一种功能,要使用更多的代码。如下例:

    interface IEventHandler {
    public function DoEvent($Sender, $Arg);
    }

    class EventHanlerAdapter implements IEventHandler {

    public function DoEvent($Sender, $Arg) {
    echo “Calling EventHanlerAdapter.DoEvent(), Sender = $Sender, arg = $Argn”;
    }
    }

    class EventRaiser {

    private $EventHanlerVar;

    public function __construct($EventHanlerAdapter) {
    $this->EventHanlerVar = $EventHanlerAdapter;
    }

    public function RaiseEvent() {
    if ($this->EventHanlerVar != null) {
    $this->EventHanlerVar->DoEvent($this, ’some string’);
    }
    }

    public function __tostring() {
    return ‘Object EventRaier’;
    }

    }

    $EventHanlerAdapter = new EventHanlerAdapter();
    $EventRaiser = new EventRaiser($EventHanlerAdapter);
    $EventRaiser->RaiseEvent();

    首先定义了一个接口IEventHandler,它包含了方法的签名。EventHanlerAdapter类作为事件处理者,实现了这个接口,并提供了相应的处理方法。EventRaiser类作为事件触发者,针对$EventHanlerVar变量(它应该是IEventHandler接口类型,但是在 PHP中不用显式定义)编码。

    在实际应用中,将EventHanlerAdapter的实例作为参数赋予传给EventRaiser类的构造函数,当事件发生时,相应的处理方法将被调用。输出结果如下:

    Calling EventHanlerAdapter.DoEvent(), Sender = Object EventRaier, arg = some string

    最后,让我们回到现实世界中来。虽然我们用PHP完整地实现了PME模型,但是这到底有什么用呢?毕竟,我们不会用PHP去编写IDE,也不会用它编写应用服务器。回答是,基于PME模型的组件技术可以实现更加方便和更大规模的代码复用。

    在基于PHP的应用系统中,虽然插件已经被广泛使用,但是通过组件技术可以实现功能更强大、更加规范和更容易维护的插件。此外,组件技术在实现一些大的Framework(例如,针对Web UI的Framework)时,也是不可或缺的。

    [转]PHP沉思录之一

    2009年06月28日 上午 16:57 | 作者:pangyt

    左轻候:http://blog.csdn.net/phphot/archive/2008/02/19/2105600.aspx

    本文是一个系列的第一篇,目前想到的其他一些主题是:
      
       SQL注入问题
       事件模型
       AOP模型
       UI Framework的实现
       Template机制
      
      
       PHP沉思录
       工作模型
       PHP的工作模型非常特殊。从某种程度上说,PHP和ASP、ASP.NET、JSP/Servlet等流行的Web技术,有着本质上的区别。

       以Java为例,Java在Web应用领域,有两种技术:Java Servlet和JSP(Java Server Page)。Java Servlet是一种特殊类型的Java程序,它通过实现相关接口,处理Web服务器发送过来的请求,完成相应的工作。JSP在形式上是一种类似于PHP 的脚本,但是事实上,它最后也被编译成Servlet。

    也就是说,在Java解决方案中,JSP和Servlet是作为独立的Java应用程序执行的,它们在初始化之后就驻留内存,通过特定的接口和 Web服务器通信,完成相应工作。除非被显式地重启,否则它们不会终止。因此,可以在JSP和Servlet中使用各种缓存技术,例如数据库连接池。

       ASP.NET的机制与此类似。至于ASP,虽然也是一种解释型语言,但是仍然提供了Application对象来存放应用程序级的全局变量,它依托于ASP解释器在IIS中驻留的进程,在整个应用程序的生命期有效。

       PHP却完全不是这样。作为一种纯解释型语言,PHP脚本在每次被解释时进行初始化,在解释完毕后终止运行。这种运行是互相独立的,每一次请求都会创建一个单独的进程或线程,来解释相应的页面文件。页面创建的变量和其他对象,都只在当前的页面内部可见,无法跨越页面访问旧电脑回收。

    在终止运行后,页面中申请的、没有被代码显式释放的外部资源,包括内存、数据库连接、文件句柄、Socket连接等,都会被强行释放。
       也就是说,PHP无法在语言级别直接访问跨越页面的变量,也无法创建驻留内存的对象。见下例:
      
          class StaticVarTester {
       public static $StaticVar = 0;
       }
      
       function TestStaticVar() {
       StaticVarTester :: $StaticVar += 1;
       echo "StaticVarTester :: StaticVar = " . StaticVarTester :: $StaticVar;
       }
      
       TestStaticVar();
       echo "
    “;
       TestStaticVar();
       ?>
      
      在这个例子中,定义了一个名为StaticVarTester的类,它仅有一个公共的静态成员$StaticVar,并被初始化为0。然后,在 TestStaticVar()函数中,对StaticVarTester :: $StaticVar进行累加操作,并将它打印输出。
      熟悉 Java或C++的开发者对这个例子应该并不陌生。$StaticVar作为StaticVarTester类的一个静态成员,只在类被装载时进行初始化,无论StaticVarTester类被实例化多少次,$StaticVar都只存在一个实例,而且不会被多次初始化。因此,当第一次调用 TestStaticVar()函数时,$StaticVar进行了累加操作,值为1,并被保存。第二次调用TestStaticVar()函数,$ StaticVar的值为2。
       打印出来的结果和我们预料的一样:
      
       StaticVarTester :: StaticVar = 1
       StaticVarTester :: StaticVar = 2
      
       但是,当浏览器刷新页面,再次执行这段代码时,不同的情况出现了。在Java或C++里面,$StaticVar的值会被保存并一直累加下去,我们将会看到如下的结果:
      
       StaticVarTester :: StaticVar = 3
       StaticVarTester :: StaticVar = 4
       …
      
       但是在PHP中,由于上文叙及的机制,当前页面每次都解释时,都会执行一次程序初始化和终止的过程。也就是说,每次访问时,StaticVarTester都会被重新装载,而下列这行语句
      
       public static $StaticVar = 0;
      
      也会被重复执行。当页面执行完成后,所有的内存空间都会被回收,$StaticVar这个变量(连同整个StaticVarTester类)也就不复存在。因此,无论刷新页面多少次,$StaticVar变量都会回到起点:先被初始化为0,然后在TestStaticVar()函数调用中被累加。所以,我们看到的结果永远是这个:
      
       StaticVarTester :: StaticVar = 1
       StaticVarTester :: StaticVar = 2
    PHP这种独特的工作模型的优势在于,基本上解决了令人头疼的资源泄漏问题。Web应用的特点是大量的、短时间的并发处理,对各种资源的申请和释放工作非常频繁,很容易导致泄漏。

    同时,大量的动态html脚本的存在,使得追踪和调试的工作都非常困难。PHP的运行机制,以一种非常简单的方式避免了这个问题,同时也避免了将程序员带入到繁琐的缓冲池和同步等问题中去。在实践中,基于PHP的应用往往比基于Java或.NET的应用更加稳定,不会出现由于某个页面的BUG而导致整个站点崩溃的问题。

    (相比之下,Java或.NET应用可能因为缓冲池崩溃或其他的非法操作,而导致整个站点崩溃。)后果是,即使PHP程序员水平不高,也无法写出使整个应用崩溃的代码。PHP脚本可以一次调用极多的资源,从而导致页面执行极为缓慢,但是执行完毕后所有的资源都会被释放,应用仍然不会崩溃。

    甚至即使执行了一个死循环,也会在30秒(默认时间)后因为超时而中止。从理论上来说,基于PHP的应用是不太可能崩溃的,因为它的运行机制决定它不存在常规的崩溃这个问题。在实践中,很多开发者也认为PHP是最稳定的Web应用。

    但是,这种机制的缺点也非常明显。最直接的后果是,PHP在语言级别无法实现跨页面的缓冲机制。这种缓冲机制缺失造成的影响,可以分成两个方面:

      一是对象的缓冲。如我们所知,很多设计模式都依赖于对象的缓冲机制,对于需要频繁应付大量并发的服务端软件更是如此。因此,对象缓冲的缺失,理论上会极大地降低速度。但是,由于PHP本身的定位和工作机制等原因,它在实际工作中的速度非常快。就作者自己的经验来看,在小型的Web应用中,PHP至少不比 Java慢。

    在大型的应用中,为了榨干每一分硬件资源,即使PHP本身足够快,一个优秀的对象缓冲机制仍然是必要的。在这种情况下,可以使用第三方的内存缓冲软件,如Memcached。由于Memcached本身的优异特性(高性能,支持跨服务器的分布式存储,和PHP的无缝集成等),在大型的PHP应用中, Memcached几乎已经成为不可或缺的基础设施了。比起使用PHP语言自己实现对象缓冲来,这种第三方解决方案似乎更好一些。

       二是数据库连接的缓冲。对MySQL,PHP提供了一种内置的数据库缓冲机制,使用起来非常简单,程序员需要做的只是用mysql_pconnect()代替mysql_connect()来打开数据库而已。

    PHP会自动回收被废弃的数据库连接,以供重复使用。具有讽刺意味的是,在实际应用中,这种持久性数据库连接往往会导致数据库连接的伪泄漏现象:在某个时间,并发的数据库连接过多,超过了MySQL的最大连接数,从而导致新的进程无法连接数据库。

    但是过一段时间,当并发数减少时,PHP会释放掉一些连接,网站又会恢复正常。出现这种现象的原因是,当使用pconnect时,Apache的httpd 进程会不释放connect,而当Apache的httpd进程数超过了mysql的最大连接数时,就会出现无法连接的情况。因此,需要小心地调整 Apache和Mysql的配置,以使Apache的httpd进程数不会超出MySQL的最大连接数。在某些情况下,一些有经验的PHP程序员宁可继续使用mysql_connect(),而不是mysql_pconnect()。

      就作者所知,在PHP未来的roadmap中,对于工作模型这一部分,没有根本性的变动。这是PHP的缺点,也是PHP的优势,从本质上说,这就是PHP 的独特之处。因此,我们很难期待PHP在近期内会对这一问题做出重大的改变。但是,在对待这个问题带来的一系列后果时,我们必须谨慎应对旧电脑回收。
      
       数据库访问接口
      长期以来,PHP都缺乏一个象ADO或JDBC那样的统一的数据库访问接口。PHP在访问不同的数据库时,使用不同的专门API。例如,使用 mysql_connect函数连接MySQL,使用ora_logon函数连接Oracle。平心而论,这种方式并没有象我们想象的那样麻烦。
    在真实项目中,把系统从一种数据库完全迁移到另一种数据库的要求是比较少见的,特别是对于LAMP这样的小型项目而言。而且,只要将访问数据库的代码进行了良好的封装,迁移的工作量也会较少。另外,使用专门API,在效率上多少会有一些优势。

       虽然如此,PHP的开发人员仍然在努力构建PHP的统一的数据库访问接口。从PHP 5.1开始,PHP的发行包内置了PDO(PHP Data Objects,PHP数据对象)。PDO具有如下特性:
      
        统一的数据库访问接口。PDO为访问不同的数据库提供了统一的接口,并且能够通过切换数据库驱动程序,方便地支持各种流行的数据库。
        面向对象。PDO完全基于PHP 5的对象机制,因此区别于基于过程的专用API。
        高性能。PDO的底层用C编写,比起用纯PHP开发的其他类似解决方案,有更高的性能。
      
       一个典型的PDO应用如下例:
      
       $pdo = new PDO(“mysql:host=localhost;dbname=justtest”, ” mysql_user “, ” mysql_password”);
       $query = “SELECT id, username FROM userinfo ORDER BY ID”;
       foreach ($pdo->query($query) as $row) {
       echo $row['id'].” | “.$row['username'].”
    “;
       } 但是,PDO还有一个更重要的问题没有解决,那就是对数据集的抽象。
      无论是ADO还是JDBC,除了提供统一的数据库访问接口以外,也提供了对数据集的抽象。也就是说,在通过ADO/JDBC取回数据集结果以后,这些数据集以统一的格式被存放在RecordSet/RowSet对象中,业务逻辑代码只需要与数据集对象进行交互即可。对数据集进行抽象的直接后果,是彻底地分离了业务逻辑层和数据库访问层的代码,并且也在某种程度上起到了OR Mapping的效果。
       自从ADO.NET出现后,数据集的抽象又有了一次不小的进步。和ADO相比,ADO.NET中的DataTable/DataSet类的主要新特性如下:
      
        非连接性。在传统的ADO模型中,数据集需要占用一个数据库连接,直到所有工作完成。一旦连接被关闭,数据集的内容也就失效了。ADO.NET中的数据集是非连接的,也就是说,当连接被关闭后,数据集中的内容仍然保存。这种非连接性带来的直接后果是,数据库连接可以被最大限度地利用,因为一旦工作完成就可以将连接返回到数据库连接池中。(ADO也支持非连接的数据集,但是需要程序员自己实现,而ADO.NET的数据集在本质上就是非连接的。)
        自描述性。ADO.NET中的数据集是完全自我描述的,而且具有完备的metadata,其内容不但可以从任何特定的数据库生成,而且可以由代码动态生成。DataSet可以跟踪数据的变化,并完成相应的操作。
        互操作性。由于非连接性和自描述性,ADO.NET中的数据集可以非常方便地在网络之间进行传输。DataSet可以序列化/反序列化为XML或其他特定的格式。这样,DataSet不但可以用于同一平台的分布式网络环境,而且可以用于异构网络环境。
      
       Java从J2SE 5.0开始内置了CachedRowSet,其原理和ADO.NET的数据集类似。Borland开发的用于取代BDE的新一代数据库引擎dbExpress,其改进也与此类似。
       与之对比,PHP中对数据集的支持显得非常原始。无论是传统的API还是PDO,取回的数据仅仅表现为数组,并且没有任何缓存机制。这意味着,在所有需要访问数据集的地方,都必须频繁地使用直接访问数据库的API。下面是一个使用mysql API的例子:
      
       $link = mysql_connect(‘localhost’, ‘mysql_user’, ‘mysql_password’);
       if (!$link) {
       die(‘Could not connect: ‘ . mysql_error());
       }
       mysql_select_db(“justtest”);
       $result = mysql_query(“SELECT id, username FROM userinfo ORDER BY ID”);
       while ($row = mysql_fetch_array($result)) {
       echo $row['id'].” | “.$row['username'].”
    “;
       }
       mysql_close($link);
      
       这样做的后果是业务逻辑和访问数据库的代码无法分离,在规模较大的系统中尤其严重。
      就作者所知,PHP官方没有提供支持抽象数据集的计划。但是,自己实现这样一个数据集并不是一件难事。作者参照ADO.NET的架构,使用纯PHP代码编写了一个规模非常小的数据抽象类库,姑且称之为MyPDO。MyPDO大约有1300行代码,在几个真实项目中工作得很好。由于篇幅所限,本文不列出它的所有代码,仅仅给出几个最主要的类的描述:
      
        DataAdapter接口:定义了所有与数据库操作相关的方法。
        ConceptDataAdapter类:实现了DataAdapter,封装了访问特定数据库的代码。如MySqlDataAdapter。
        DataSet类:实现了自描述的抽象数据集,与具体数据库无关。可以自我跟踪数据的变化。
        SqlCommandBuilder类:可以跟据DataSet类的内容,自动实现Insert、Update、Delete等操作。
      
       下面是MyPDO的一个典型应用:
      
       $Conn = new MySqlDataConnection(new ConnectionInfo(“localhost”, “mysql_username”, “mysql_password”, “justtest”, “gbk”));
       $Da = $Conn->GetDataAdapter();
       $Da->SetSqlString(“SELECT id, username FROM userinfo ORDER BY ID”);
       $Ds = new DataSet();
       $Da->Fill($Ds);
       $Conn->Disconnect(); // 关闭数据库连接,但$Ds仍然保存数据内容
       echo $Ds; // 调用DataSet的__tostring()方法,格式化输出内容
      
       通过替换MySqlDataConnection,可以以最小的成本实现不同数据库之间的切换。
       由于上文中讨论过的PHP的工作模型,通过MyPDO实现的缓存在性能上获得的好处有限。但是,在采用Memcached的解决方案中,MyPDO还是能够带来很大的便利。因为只有基于非连接方式的数据集,才可能在Memcached这样的内存池中被缓存。
       另外,由于MyPDO中的DataSet是自描述的,内置了WriteToXml和ReadFromXml方式,它无需程序员编码就可以保存为XML或从XML中还原,在网络上甚至异构平台之间进行传输和识别。在电子商务领域中,这个特性是非常有用的。

    [转]apache的prefork模式和worker模式

    2009年06月28日 上午 59:54 | 作者:pangyt

    出处:平凡的世界
    链接:http://www.ccvita.com/339.html

    prefork模式
    这个多路处理模块(MPM)实现了一个非线程型的、预派生的web服务器,它的工作方式类似于Apache 1.3。它适合于没有线程安全库,需要避免线程兼容性问题的系统。它是要求将每个请求相互独立的情况下最好的MPM,这样若一个请求出现问题就不会影响到其他请求。

    这个MPM具有很强的自我调节能力,只需要很少的配置指令调整。最重要的是将MaxClients设置为一个足够大的数值以处理潜在的请求高峰,同时又不能太大,以致需要使用的内存超出物理内存的大小。

    worker模式
    此多路处理模块(MPM)使网络服务器支持混合的多线程多进程。由于使用线程来处理请求,所以可以处理海量请求,而系统资源的开销小于基于进程的MPM。但是,它也使用了多进程,每个进程又有多个线程,以获得基于进程的MPM的稳定性。

    控制这个MPM的最重要的指令是,控制每个子进程允许建立的线程数的ThreadsPerChild指令,和控制允许建立的总线程数的MaxClients指令。

    prefork和worker模式的切换
    1.将当前的prefork模式启动文件改名
    mv httpd httpd.prefork
    2.将worker模式的启动文件改名
    mv httpd.worker httpd
    3.修改Apache配置文件
    vi /usr/local/apache2/conf/extra/httpd-mpm.conf
    找到里边的如下一段,可适当修改负载等参数:

    StartServers 2
    MaxClients 150
    MinSpareThreads 25
    MaxSpareThreads 75
    ThreadsPerChild 25
    MaxRequestsPerChild 0

    4.重新启动服务
    /usr/local/apache2/bin/apachectl restart
    即可换成worker方式启动apache2

    处于稳定性和安全性考虑,不建议更换apache2的运行方式,使用系统默认prefork即可。另外很多php模块不能工作在worker模式下,例如redhat linux自带的php也不能支持线程安全。所以最好不要切换工作模式。

    prefork和worker模式的比较
    prefork模式使用多个子进程,每个子进程只有一个线程。每个进程在某个确定的时间只能维持一个连接。在大多数平台上,Prefork MPM在效率上要比Worker MPM要高,但是内存使用大得多。prefork的无线程设计在某些情况下将比worker更有优势:它可以使用那些没有处理好线程安全的第三方模块,并且对于那些线程调试困难的平台而言,它也更容易调试一些。

    worker模式使用多个子进程,每个子进程有多个线程。每个线程在某个确定的时间只能维持一个连接。通常来说,在一个高流量的HTTP服务器上,Worker MPM是个比较好的选择,因为Worker MPM的内存使用比Prefork MPM要低得多。但worker MPM也由不完善的地方,如果一个线程崩溃,整个进程就会连同其所有线程一起”死掉”.由于线程共享内存空间,所以一个程序在运行时必须被系统识别为”每个线程都是安全的”。

    总的来说,prefork方式速度要稍高于worker,然而它需要的cpu和memory资源也稍多于woker。

    prefork模式配置详解

    ServerLimit 256
    StartServers 5
    MinSpareServers 5
    MaxSpareServers 10
    MaxClients 256
    MaxRequestsPerChild 0

    ServerLimit
    默认的MaxClient最大是256个线程,如果想设置更大的值,就的加上ServerLimit这个参数。20000是ServerLimit这个参数的最大值。如果需要更大,则必须编译apache,此前都是不需要重新编译Apache。
    生效前提:必须放在其他指令的前面

    StartServers
    指定服务器启动时建立的子进程数量,prefork默认为5。

    MinSpareServers
    指定空闲子进程的最小数量,默认为5。如果当前空闲子进程数少于MinSpareServers ,那么Apache将以最大每秒一个的速度产生新的子进程。此参数不要设的太大。

    MaxSpareServers
    设置空闲子进程的最大数量,默认为10。如果当前有超过MaxSpareServers数量的空闲子进程,那么父进程将杀死多余的子进程。此参数不要设的太大。如果你将该指令的值设置为比MinSpareServers小,Apache将会自动将其修改成”MinSpareServers+1″。

    MaxClients
    限定同一时间客户端最大接入请求的数量(单个进程并发线程数),默认为256。任何超过MaxClients限制的请求都将进入等候队列,一旦一个链接被释放,队列中的请求将得到服务。要增大这个值,你必须同时增大ServerLimit。

    MaxRequestsPerChild
    每个子进程在其生存期内允许伺服的最大请求数量,默认为10000.到达MaxRequestsPerChild的限制后,子进程将会结束。如果 MaxRequestsPerChild为”0″,子进程将永远不会结束。将MaxRequestsPerChild设置成非零值有两个好处:
    1.可以防止(偶然的)内存泄漏无限进行,从而耗尽内存。
    2.给进程一个有限寿命,从而有助于当服务器负载减轻的时候减少活动进程的数量。

    worker模式配置详解

    StartServers 2
    MaxClients 150
    MinSpareThreads 25
    MaxSpareThreads 75
    ThreadsPerChild 25
    MaxRequestsPerChild 0

    StartServers
    服务器启动时建立的子进程数,默认值是”3″。

    MaxClients
    允许同时伺服的最大接入请求数量(最大线程数量)。任何超过MaxClients限制的请求都将进入等候队列。默认值是”400″ ,16(ServerLimit)乘以25(ThreadsPerChild)的结果。因此要增加MaxClients的时候,你必须同时增加 ServerLimit的值。

    MinSpareThreads
    最小空闲线程数,默认值是”75″。这个MPM将基于整个服务器监视空闲线程数。如果服务器中总的空闲线程数太少,子进程将产生新的空闲线程。

    MaxSpareThreads
    设置最大空闲线程数。默认值是”250″。这个MPM将基于整个服务器监视空闲线程数。如果服务器中总的空闲线程数太多,子进程将杀死多余的空闲线程。 MaxSpareThreads的取值范围是有限制的。Apache将按照如下限制自动修正你设置的值:worker要求其大于等于 MinSpareThreads加上ThreadsPerChild的和。

    ThreadsPerChild
    每个子进程建立的常驻的执行线程数。默认值是25。子进程在启动时建立这些线程后就不再建立新的线程了。

    MaxRequestsPerChild
    设置每个子进程在其生存期内允许伺服的最大请求数量。到达MaxRequestsPerChild的限制后,子进程将会结束。如果MaxRequestsPerChild为”0″,子进程将永远不会结束。将MaxRequestsPerChild设置成非零值有两个好处:
    1.可以防止(偶然的)内存泄漏无限进行,从而耗尽内存。
    2.给进程一个有限寿命,从而有助于当服务器负载减轻的时候减少活动进程的数量。
    注意对于KeepAlive链接,只有第一个请求会被计数。事实上,它改变了每个子进程限制最大链接数量的行为。

    注:restart并不等于stop+start。

    using expect

    2009年06月23日 上午 58:36 | 作者:pangyt

    热身小脚本
    function myscp
    {
    local FNAME=$1
    local UNAME=$2
    local HNAME=$3
    local PASSWD=$4
    local FNAME_REMOTE=$5
    expect < spawn scp $FNAME $UNAME@$HNAME:$FNAME_REMOTE
    set timeout 5
    set e 0
    while 1 {
    expect {
    “*(yes/no)?” { send “yes\r” }
    “*assword:” { if {\$e == 1} { send_user “\nPassword error!\n”; exit 1 }; set e [expr \$e+1]; send “$PASSWD\r” }
    timeout { send_user “Connect timeout!\n”; exit 1 }
    eof { exit 0 }
    }
    }
    EOF
    }

      Expect是一个控制交互式程序的工具。他用非交互的方式实现了所有交互式的功能
      Expect语言是基于Tcl的。Tcl实际上是一个子程序库,这些子程序库可以嵌入到程序里从而提供语言服务。 最终的语言有点象一个典型的 Shell语言。里面有给变量赋值的set命令,控制程序执行的if,for,continue等命令,还能进行普通的数学和字符串操作。当然了,还可以用exec来调用Unix程序。所有这些功能,Tcl都有。Tcl在参考书籍 Outerhour[3][4]里有详细的描述。

       Expect是在Tcl基础上创建起来的,它还提供了一些Tcl所没有的命令。spawn命令激活一个Unix程序来进行交互式的运行。 send命令向进程发送字符串。expect命令等待进程的某些字符串。 expect支持正规表达式并能同时等待多个字符串,并对每一个字符串执行不同的操作。 expect还能理解一些特殊情况,如超时和遇到文件尾。

      expect命令和Tcl的case命令的风格很相似。都是用一个字符串去匹配多个字符串。(只要有可能,新的命令总是和已有的Tcl命令相似,以使得该语言保持工具族的继承性)。下面关于expect的定义是从手册[5]上摘录下来的。

          expect patlist1 action1 patlist2 action2…..

        该命令一直等到当前进程的输出和以上的某一个模式相匹配,或者等到时间超过一个特定的时间长度,或者等到遇到了文件的结束为止。
        
        如果最后一个action是空的,就可以省略它。

        每一个patlist都由一个模式或者模式的表(lists)组成。如果有一个模式匹配成功,相应的action就被执行。执行的结果从expect返回。
        被精确匹配的字符串(或者当超时发生时,已经读取但未进行匹配的字符串)被存贮在变量expect_match里面。如果patlist是eof 或者timeout,则发生文件结束或者超时时才执行相应的action.一般超时的时值是10秒,但可以用类似”set timeout 30″之类的命令把超时时值设定为30秒。
        
        下面的一个程序段是从一个有关登录的脚本里面摘取的。abort是在脚本的别处定义的过程,而其他的action使用类似与C语言的Tcl原语。

          expect “*welcome*”        break     
               “*busy*”        {print busy;continue}
              “*failed*”        abort 
              timeout        abort

        模式是通常的C Shell风格的正规表达式。模式必须匹配当前进程的从上一个expect或者interact开始的所有输出(所以统配符*使用的非常)的普遍。但是,一旦输出超过2000个字节,前面的字符就会被忘记,这可以通过设定match_max的值来改变。

      expect命令确实体现了 expect语言的最好和最坏的性质。特别是,expect命令的灵活性是以经常出现令人迷惑的语法做代价。除了关键字模式(比如说eof,timeout)那些模式表可以包括多个模式。这保证提供了一种方法来区分他们。但是分开这些表需要额外的扫描,如果没有恰当的用 ["]括起来,这有可能会把和当成空白字符。由于Tcl提供了两种字符串引用的方法:单引和双引,情况变的更糟。(在Tcl里面,如果不会出现二义性话,没有必要使用引号)。在expect的手册里面,还有一个独立的部分来解释这种复杂性。幸运的是:有一些很好的例子似乎阻止了这种抱怨。但是,这个复杂性很有可能在将来的版本中再度出现。为了增强可读性,在本文中,提供的脚本都假定双引号是足够的。
    为“Tk”的工具箱如何简洁地表达 GUI 解决方案。