PHP V5 迁移指南

使用 PHP V5
的新语言特性,可以明显地提高代码的可维护性和可靠性。通过阅读本文,您将了解如何利用这些新特性将用
PHP V4 开发的代码迁移到 PHP V5。 PHP V5 在 PHP V4
基础上做了重大改进。新语言特性使构建可靠的类库和维护类库更加容易。另外,重写标准库帮助使
PHP 更符合其同一 Web 语系,例如 Java 编程语言。让我们来看一些 PHP
新的面向对象特性,并了解如何将现有 PHP V4 代码迁移到 PHP V5。

本文举例说明了创建可配置 PHP
应用程序的几种方法。文中也探讨了应用程序中理想的配置点,并在应用程序过分可配置和过分封闭之间寻求一个平衡点。

首先,先来了解新语言特性及 PHP 的创建程序怎样更改了用 PHP V4
创建对象的方法。用 V5 的想法是要创建一种工业级语言用于 Web
应用程序开发。那意味着要了解 PHP V4 的限制,然后从其他语言中
抽取已知优秀语言架构并将这些架构并入 PHP 中。

如果计划让其他人或公司可以使用您的 PHP
应用程序,需要确保该程序是可配置的。至少,要允许用户以一种安全的方式设置数据库登录及密码,从而使其中的材料不会对外公开。

第一个也是最重要的新特性是针对类的方法和实例变量的访问保护 ――
public、protected 和 private 关键字。
这个新特性使类设计人员可以保证对类的内在特性的控制,同时告诉类的使用者哪些类可以而哪些类不可以触及。

本文展示了几种用于存储配置设置及编辑这些设置的技术。另外,文中也为哪些元素需要设为可配置以及如何避免陷入配置过度或者配置不足的困境提供了指导。

在 PHP V4 中,所有代码都是 public 的。在 PHP V5
中,类设计人员可以声明哪些代码是对外部可见的 (public)
而哪些代码仅对类内部可见 (private) 或仅对类的子类可见
(protected)。如果没有这些访问控制,则在大型团队中开发代码或将代码分布为库的工作会受阻,因为那些类的使用者很可能使用错误的方法或访问应当为
private 成员变量的代码。

使用 INI 文件进行配置

另一个较大的新功能是关键字 interface 和
abstract,这两个关键字允许进行契约编程。契约编程
意味着一个类向另一个类提供一张契约 ―― 换言之:
“这是我要做的工作,你不需要知道它是怎样完成的”。 实现 interface
的所有类都遵循该契约。interface 的所有使用者都同意仅使用 interface
中指定的方法。abstract 关键字使得使用接口十分容易,我稍后将加以说明。

PHP 内建了对配置文件的支持。这是通过 php.ini
文件这样的初始化文件机制实现的,在 php.ini
文件中定义了数据库连接超时或会话如何存储等常量。如果愿意的话,可以在这个
php.ini 文件中为应用程序定制配置。为了说明,我将下列代码行添加到 php.ini
文件中。

这两个主要特性 ―― 访问控制和契约编程 ――
允许大型编码人员团队更顺畅地使用大型代码库。这些特性还使 IDE
可以提供更丰富的语言智能特性集。本文不但说明了若干个迁移问题,而且还花了一些时间说明如何使用这些新主要语言特性。

myapptempdir=foo

访问控制

然后,我编写了一个小 PHP 脚本来读取这个配置项,如清单 1 所示。

为了演示新语言特性,我使用了一个名为 Configuration
的类。这个简单的类中含有用于 Web 应用程序的配置项 ――
例如,指向图片目录的路径。在理想的情况下,此信息将驻存在一个文件或数据库里。清单
1 显示了一个简化的版本。

清单 1. ini1.php

清单 1. access.php4

<?php function get_template_directory() { $v = get_cfg_var(
“myapptempdir” ); return ( $v == null ) ? “tempdir” : $v; }

?php class Configuration { var $_items = array();

echo( get_template_directory().”” ); ?>

function Configuration() { $this-_items[ imgpath ] = images; }
function get( $key ) { return $this-_items[ $key ]; } }

当在命令行中运行这段代码时,得到如下结果:

$c = new Configuration(); echo( $c-get( imgpath ).”” ); ?

% php ini1.php foo %

这是一个完全正统的 PHP V4
类。成员变量保存配置项的列表,构造程序装入项,然后名为 get()
的访问方法返回项的值。

太棒了。但为什么不能用标准的 INI 函数来获取 myapptempdir
配置项的值呢?我研究了一下,发现在大多数情况下,定制配置项不能使用这些方法来获取。然而,使用
get_cfg_var 函数却是可以访问的。

运行脚本后,以下代码将显示在命令行中:

为使这个方法更加简单,将对变量的访问封装在第二个函数中,该函数使用配置键名及一个缺省值作为参数,如下所示。

% php access.php4 images %

清单 2. ini2.php

很好!这个结果意味着代码运行正常并且正常设定和读取了 imgpath
配置项的值。

function get_ini_value( $n, $dv ) { $c = get_cfg_var( $n ); return (
$c == null ) ? $dv : $c; }

将这个类转换为 PHP V5 的第一步是要将构造程序重命名。在 PHP V5
中,初始化对象 的方法称为 __construct。这次小改动如下所示。

function get_template_directory() { return get_ini_value(
“myapptempdir”, “tempdir” ); }

清单 2. access1.php5

澳门新葡亰网站注册,这是对如何访问 INI
文件的一个很好的概括,所以,如果要使用一个不同的机制或将这个 INI
文件存储到其他位置,就不需要为更改大量的函数而大费周折。

?php class Configuration { var $_items = array();

我不推荐使用 INI
文件作为应用程序的配置,这有两个理由。首先,虽然这样做较容易读取 INI
文件,但却几乎不可能安全地写 INI
文件。所以这样做只适合于只读配置项。第二,php.ini
文件在服务器的所有应用程序上共享,所以我认为特定于应用程序的配置项不应该写在该文件中。

function __construct() { $this-_items[ imgpath ] = images; }
function get( $key ) { return $this-_items[ $key ]; } }

需要对 INI 文件了解什么呢?最重要的是如何重置 include
路径来添加配置项,如下所示。

$c = new Configuration(); echo( $c-get( imgpath ).”” ); ?

清单 3. ini3.php

这次改动并不大。只是移至 PHP V5
约定。下一步是添加对类的访问控制以确保类的使用者无法直接读写 $_items
成员变量。这次改动如下所示。

<?php echo( ini_get(“include_path”).”” ); ini_set(“include_path”,
ini_get(“include_path”).”:./mylib” ); echo(
ini_get(“include_path”).”” ); ?>

清单 3. access2.php5

在本例中,我将我的本地 mylib 目录添加到了 include
路径中,所以能够从该目录中 require PHP 文件,而不需要将该路径添加到
require 语句中。

?php class Configuration { private $_items = array();

PHP 中的配置

public function __construct() { $this-_items[ imgpath ] = images; }
public function get( $key ) { return $this-_items[ $key ]; } }

通常对于在 INI 文件中存储配置条目的一个替代办法是使用一个简单的 PHP
脚本来保持数据。如下是一个样例。

$c = new Configuration(); echo( $c-get( imgpath ).”” ); ?

清单 4. config.php

如果这个对象的使用者都要直接访问项阵列,访问将被拒绝,因为该阵列被标记为
private。幸运的是,使用者发现 get() 方法可以提供广受欢迎的读取权限。

<?php # Specify the location of the temporary directory #
$TEMPLATE_DIRECTORY = “tempdir”; ?>

为了说明如何使用 protected 权限,我需要另一个类,该类必须继承自
Configuration 类。我把那个类称为
DBConfiguration,并假定该类将从数据库中读取配置值。此设置如下所示。

使用该常量的代码如下所示。

清单 4. access3.php

清单 5. php.php

?php class Configuration { protected $_items = array();

<?php require_once config.php;

public function __construct() { $this-load(); } protected function
load() { } public function get( $key ) { return $this-_items[ $key ];
} }

function get_template_directory() { global $TEMPLATE_DIRECTORY;
return $TEMPLATE_DIRECTORY; }

class DBConfiguration extends Configuration { protected function load()
{ $this-_items[ imgpath ] = images; } }

echo( get_template_directory().”” ); ?>

$c = new DBConfiguration(); echo( $c-get( imgpath ).”” ); ?

该代码首先包含配置文件,接着就可以直接使用这些常量了。

这张清单显示了 protected 关键字的正确用法。基类定义了名为 load()
的方法。此类的子类将覆盖 load() 方法把数据添加到 items 表中。load()
方法对类及其子类是内部方法,因此该方法对所有外部使用者都不可见。如果关键字都是
private 的,则 load() 方法不能被覆盖。

使用这项技术有很多优势。首先,如果某些人仅仅浏览 config.php
文件,该页面是空白的。所以可以将 config.php 放到相同的文件中,并作为 Web
应用程序的根。第二,在任何编辑器中都可编辑,并且在一些编辑器中甚至具备语法着色及语法检查功能。

我并不十分喜欢此设计,但是,由于必须让 DBConfiguration
类能够访问项阵列而选用了此设计。我希望继续由 Configuration
类来完全维护项阵列,以便在添加其他子类后,那些类将不需要知道如何维护项阵列。我做了以下更改。

这项技术的缺点是,这是一个像 INI
文件一样的只读技术。将数据从此文件中提取出来是轻而易举的,但在该 PHP
文件中调整数据却很困难,在一些情况下甚至是不可能的。

清单 5. access4.php5

下面的替代方法显示了如何编写在本质上既可读又可写的配置系统。

?php class Configuration { private $_items = array();

文本文件

public function __construct() { $this-load(); } protected function
load() { } protected function add( $key, $value ) { $this-_items[ $key
] = $value; } public function get( $key ) { return $this-_items[ $key
]; } }

前面的两个例子对于只读配置条目都是合适的,但对于既读又写的配置参数来说又如何呢?首先,看看清单
6 中的文本配置文件。

class DBConfiguration extends Configuration { protected function load()
{ $this-add( imgpath, images ); } }

清单 6. config.txt

$c = new DBConfiguration(); echo( $c-get( imgpath ).”” ); ?

# My applications configuration file Title=My App
TemplateDirectory=tempdir

现在,项阵列可以是 private 的,因为子类使用受保护的 add()
方法将配置项添加到列表中。Configuration
类可以更改存储和读取配置项的方法而不需要考虑它的子类。只要 load() 和
add() 方法以同样的方法运行,子类就应当不会出问题。

这是同 INI
文件相同的文件格式,但我自己编写了辅助工具。为此,我创建了自己的
Configuration 类,如下所示。

对于我来说,增加了访问控制是考虑移至 PHP V5 的主要原因。难道就因为 Grady
Booch 说 PHP V5
是四大面向对象的语言之一么?不,因为我曾经接受了一个任务来维护 100KLOC
C++ 代码,在这些代码中所有方法和成员都被定义为 public
的。我花了三天时间来清除这些定义,并在清除过程中,明显地减少了错误数并提高了可维护性。为什么?因为没有访问控制,就不可能知道对象怎样使用其他对象,也就不可能在不知道要突破什么难关的情况下做任何更改。使用
C++,至少我还有编译程序可用。PHP
没有配备编译程序,因此这类访问控制变得愈加重要。

清单 7. text1.php

契约编程

<?php class Configuration { private $configFile = config.txt; private
$items = array(); function __construct() { $this->parse(); } function
__get($id) { return $this->items[ $id ]; } function parse() { $fh =
fopen( $this->configFile, r ); while( $l = fgets( $fh ) ) { if (
preg_match( /^#/, $l ) == false ) { preg_match( /^(.*?)=(.*?)$/,
$l, $found ); $this->items[ $found[1] ] = $found[2]; } } fclose(
$fh ); } }

从 PHP V4 迁移到 PHP V5
时要利用的下一个重要特性是支持通过接口、抽象类和方法进行契约编程。清单 6
显示了一个版本的 Configuration 类,在该类中 PHP V4
编码人员尝试了构建基本接口而根本不使用 interface 关键字。

$c = new Configuration();

清单 6. interface.php4

echo( $c->TemplateDirectory.”” ); ?>

?php class IConfiguration { function get( $key ) { } }

该代码首先创建了一个 Configuration 对象。该构造函数接下来读取 config.txt
并用解析过的文件内容来设置局部变量 $items。

class Configuration extends IConfiguration { var $_items = array();

该脚本随后寻找
TemplateDirectory,这并没有在对象中直接定义。因此,使用设置成
TemplateDirectory 的 $id 来调用神奇的 __get 方法,__get
方法针对该键返回 $items 数组中的值。

function Configuration() { $this-load(); } function load() { } function
get( $key ) { return $this-_items[ $key ]; } }

这个 __get 方法特定于 PHP V5 环境,所以此脚本必须在 PHP V5
下运行。实际上,本文中所有的脚本都需要在 PHP V5 下运行。

class DBConfiguration extends Configuration { function load() {
$this-_items[ imgpath ] = images; } }

当在命令行运行此脚本时,能看到下列结果:

$c = new DBConfiguration(); echo( $c-get( imgpath ).”” ); ?

% php text1.php tempdir %

清单开始于一个小型 IConfiguration 类,该类定义所有 Configuration
类或派生类所提供的接口。此接口将在类与其所有使用者之间定义契约。契约声明了实现
IConfiguration 的所有类必须配有 get() 方法并且 IConfiguration
的所有使用者都必须坚持仅使用 get() 方法。

一切都在预料之中,该对象读取 config.txt 文件,然后为 TemplateDirectory
配置项获得正确的值。

下面的这段代码是在 PHP V5 中运行的,但最好使用提供的接口系统,如下所示。

但对于设置一个配置值,应该怎么做呢?在此类中建立一个新方法及一些新的测试代码,就能够得到这个功能,如下所示。

清单 7. interface1.php5

清单 8. text2.php

?php interface IConfiguration { function get( $key ); }

<?php class Configuration { …

class Configuration implements IConfiguration { … }

function __get($id) { return $this->items[ $id ]; }

class DBConfiguration extends Configuration { … }

function __set($id,$v) { $this->items[ $id ] = $v; } function
parse() { … } } $c = new Configuration(); echo(
$c->TemplateDirectory.”” ); $c->TemplateDirectory = foobar; echo(
$c->TemplateDirectory.”” ); ?>