利用 PHP V5.3 名称空间编写可读且可维护的代码

组织代码并防止名称冲突

您是否想在 PHP 应用程序开发中使用名称空间?在本文中,了解名称空间语法,学习使用它的最佳实践,研究一个使用名称空间的非常简单的 Model-View-Controller 示例应用程序。

Don Denoncourt, 作家,顾问

Don DenoncourtDon Denoncourt 是专注于 Java 技术、Groovy、Grails 和 PHP 的自由顾问、培训师、导师和作家。



2011 年 7 月 04 日

“Conan 是我榜样。” 如果我在餐桌上说这句话,我儿子会以为我说的是游戏 “野蛮人柯南”,而我妻子会以为我说的是脱口秀主持人 Conan O'Brien。这种上下文混淆在 IT 中称为名称冲突。许多语言都有防止名称冲突的战略,PHP V5.3 也是这样。PHP 使用新的名称空间特性解决名称冲突问题。当然,PHP 要解决的冲突的名称并不是人名,而是类、函数和常量的名称。

本文解释为什么应该考虑在项目中使用名称空间。本文概述名称空间的语义,介绍最佳实践,并提供一个使用名称空间的简单的 Model-View-Controller 应用程序。还讨论 Eclipse、NetBeans 和 Zend Studio 中的名称空间支持,特别是在 Eclipse 中使用名称空间的方法。

我需要名称空间吗?

PHP 语言的优点之一是简单。如果您是 PHP 新手,名称空间只是您需要了解的一个概念。但是如果出现以下任何一种情况,就应该考虑使用名称空间:

  • 您正在开发一个包含数百个 PHP 文件的大型应用程序。
  • 您的应用程序由程序员团队编写。
  • 您打算使用的框架使用 V5.3 和名称空间。
  • 您在其他语言中使用过名称空间(或包等相似的功能),比如 Java™、Ruby 或 Python 语言。

如果您独自开发一个相当小的应用程序,可能不需要名称空间。但是对于其他情况,名称空间提供了组织类结构和防止名称冲突的简便方法。这就是许多框架开发人员使用名称空间的原因。例如,强大的 PHP 框架 Zend Framework V2.0 就使用了名称空间。


概述

名称空间为名称提供上下文。清单 1 中的两个类有名称冲突。

清单 1. 在没有名称空间的情况下,同名的两个类会导致冲突
class Conan {
	var $bodyBuild = "extremely muscular";
	var $birthDate = 'before history';
	var $skill = 'fighting';
}

class Conan {
	var $bodyBuild = "very skinny";
	var $birthDate = '1963';
	var $skill = 'comedy';
}

要想指定名称空间,只需作为源代码的第一个语句添加名称空间声明。

清单 2. 两个类同名,但是名称空间解决了冲突
<?php
namespace barbarian;
class Conan {
	var $bodyBuild = "extremely muscular";
	var $birthDate = 'before history';
	var $skill = 'fighting';
}
namespace obrien;
class Conan {
	var $bodyBuild = "very skinny";
	var $birthDate = '1963';
	var $skill = 'comedy';
}
$conan = new \barbarian\Conan();
assert('extremely muscular born: before history' == 
   "$conan->bodyBuild born: $conan->birthDate");

$conan = new \obrien\Conan();
assert('very skinny born: 1963' == "$conan->bodyBuild born: $conan->birthDate");
?>

上面的代码可以顺利运行。在解释两个都名为 Conan 的类为什么可以同时存在之前,先要指出两点。首先,我使用断言证实代码符合预期。第二,我做了您绝对不应该做的事情:在一个源代码文件中声明多个名称空间。

名称空间为两个 Conan 类提供惟一的限定符。代码能够明确地区分要引用的是野蛮人柯南,还是脱口秀主持人。注意,实例化语法使用反斜杠 (\),后面跟着名称空间名称:

$conan = new \barbarian\Conan();

和:

$conan = new \obrien\Conan();

这些限定符看起来像 Windows® 的目录限定符,这样看待它们是有意义的,因为名称空间支持相对和绝对引用(就像目录一样),而且最好把类文件的源代码放在与名称空间匹配的目录中。


使用名称空间

更现实的做法是把两个 Conan 类分别放在称为 barbarianobrien 的目录中,然后从其他 PHP 文件引用这些类。有三种引用 PHP 名称空间的方法:

  • 在类名前面加上名称空间
  • 导入名称空间
  • 给名称空间指定别名

要想使用第一种方法,只需在类名前面加上名称空间(当然是在包含源代码文件之后):

include "barbarian/Conan.php";
$conan = new \barbarian\Conan();

这非常简单,但是对于大型应用程序,这种方法的问题是必须反复输入名称空间。除了输入量大之外,还会不必要地弄乱代码。对于第二种方法,使用 PHP V5.3 保留字 use 导入名称空间:

include "barbarian/Conan.php";
use barbarian\Conan;  
$conan = new Conan();

第三种方法允许为名称空间指定别名:

include "barbarian/Conan.php";
use \barbarian\Conan as Cimmerian;
$conan = new Cimmerian();

(顺便说一句,Cimmerian 是野蛮人柯南的绰号。)

以上三个示例都有的一个问题是要使用 include 语句。可以通过使用 __autoload 函数避免使用 include。每当引用源代码文件中还不包含的类时,调用 __autoload 函数。把清单 3 中的代码放在名为 autoload.php 的文件中。

清单 3. __autoload 函数动态地包含源代码文件
<?php
function __autoload($classname) {
  $classname = ltrim($classname, '\\');
  $filename  = '';
  $namespace = '';
  if ($lastnspos = strripos($classname, '\\')) {
    $namespace = substr($classname, 0, $lastnspos);
    $classname = substr($classname, $lastnspos + 1);
    $filename  = str_replace('\\', '/', $namespace) . '/';
  }
  $filename .= str_replace('_', '/', $classname) . '.php';
  require $filename;
}
?>

然后把 autoload.php 导入源代码:

require_once "autoload.php"; 
use \barbarian\Conan as Cimmerian;

自动装载器的主要好处是不必为每个类创建 include 语句。注意,尽管可以对函数、常量和类使用 PHP 名称空间,但是自动装载器技术只适用于类。自动装载器非常方便,所以可以不编写函数,而是在适当命名的实用程序类中创建方法并把常量放在不可变的类中。


通过 MVC 应用程序了解实际用法

把 O'Brien 和野蛮人柯南这个示例放在一边,我们来看一个简单的 MVC 示例应用程序。为了有效地使用名称空间,应该在编写代码之前设计自己的命名约定。常用的最佳实践是使用名称空间树。名称空间分为高层名称空间和子名称空间。如果您的公司有多个应用程序,采用公司名作为高层名称空间可能很方便。然后,使用子名称空间表示应用程序。接下来,用一个级别表示目录,进而用名称指定其中包含的 PHP 类的应用程序功能。例如,假设高层名称空间是公司名 denoncourt,第一个子级别是 retail,第二个子级别是功能名称,见清单 4。

清单 4. 名称空间的设计可以包含嵌套的子名称空间
/denoncourt
	/retail
		/common
		/controller
		/model
		/utility
		/view

controllermodelview 子名称空间显然代表 MVC 架构,而 utilitycommon 子名称空间用于表示不属于其他子名称空间的一般性的类。

现在看看这个简单的 MVC 应用程序的代码。清单 5 给出 index.php 的代码,这个文件放在根文件夹中。

清单 5. MVC 应用程序的 index PHP 使用 controller 类
<?php
require "autoload.php";
use denoncourt\retail\controller as Control;
$controller = new Control\Controller();
$controller->execute();
?>

注意,名称空间比较长,所以使用别名 Control。由于两个原因,我喜欢对名称空间使用别名:首先,如果以后要改变名称空间,在每个源代码文件中只有一行需要修改。第二,由于在实例化类时最好完全限定名称空间,使用 Control\Controller() 实际上就等于 \denoncourt\retail\controller\Controller()。注意,也可以只为高层名称空间创建别名,然后使用子名称空间的名称进行类实例化:

use denoncourt\retail as Retail;
$controller = new retail\controller\Controller();

当在同一源代码文件中引用名称空间的多个级别时,这个特性很方便。我在 denoncourt/retail/controller 目录中创建了 Controller.php,见清单 6。

清单 6. MVC Controller 类根据用户输入决定操作
<?php
namespace denoncourt\retail\controller;
use denoncourt\retail as retail;

class Controller {
  public function execute() {
    switch ($_GET['action']) {
    case 'showItem' :
      $item = new retail\model\Item();
      require "denoncourt/retail/utils/format.php";
      require "denoncourt/retail/view/item.php";
      break;
    }
  }
}
?>

我在 denoncourt/retail/model 中创建了 Item.php。清单 7 给出代码。

清单 7. MVC Item 类在 model 子名称空间中
<?php
namespace denoncourt\retail\model;
class Item {
  public $itemNo = '123';
  public $price = 2.45;
  public $qtyOnHand = 87;
}
?>

我在 denoncourt/retail/utils 中创建了 format.php,见清单 8。

清单 8. dollar PHP 函数说明如何对函数使用名称空间
<?php
namespace denoncourt\retail;
function dollar($dollar) {
    return "\$$dollar";
}
?>

注意,正如前面提到的,我喜欢把格式化函数放在实用程序类中(这样自动装载器就会处理代码的导入,我不需要为 format.php 编写 require 语句)。

最后,在 denoncourt/retail/views 中创建视图页面 item.php。清单 9 给出代码。

清单 9. item 页面显示在控制器中实例化的模型
<html>
<head>
<style>
dt {
  float:left; clear:left;
  font-weight:bold;
  margin-right:10px;
  width:15%;
  text-align: right;
}
dd { text-align:left; }
</style>
</head>
<body>
<dl>
  <dt>Item No:</dt><dd><?php echo "$item->itemNo"; ?></dd>
  <dt>Price:</dt><dd>
       <?php echo \denoncourt\retail\dollar($item->price); ?>
       </dd>
  <dt>Quantity On Hand:</dt><dd><?php echo "$item->qtyOnHand"; ?></dd>
</dl>
</body>
</html>

注意 item 页面如何用 \denoncourt\retail\ 名称空间限定 dollar 函数。


后退

如果源代码文件中有名称空间声明,那么对类、函数和常量的所有引用都使用名称空间语义。当 PHP 遇到未限定的类、函数或常量时,它会执行后退 (fallback)。用户类上的后退会让编译器假设使用当前的名称空间。要想引用没有名称空间的类,需要加上一个反斜杠。例如,要想引用 PHP Exception 类,应该使用 $error = new \Exception();。在使用任何 Standard PHP Library 类(比如 ArrayObjectFindFileKeyFilter)时要记住这一点。

对于函数和常量,如果当前的名称空间不包含这个函数或常量,PHP 的后退机制会后退到标准的 PHP 函数。例如,如果您编写了自己的 strlen 函数,PHP 会解析出您的函数。但是,如果也希望使用标准的 PHP strlen 函数(比如在自己的 strlen 实现内部),就需要在函数调用前面加上反斜杠,见清单 10。

清单 10. 可以用反斜杠限定 PHP 标准函数以表示全局名称空间
<?php
namespace denoncourt\retail;
function strlen($str) {
    return \strlen();
}
?>

名称空间全局变量和字符串

如果您喜欢编写动态的方法,可能想把名称空间放在带双引号的字符串中:"denoncourt\retail\controller"。但是要记住,需要对反斜杠进行转义:"denoncourt\\retail\\controller"。一种解决方法是使用单引号:'denoncourt\retail\controller'

在进行动态编程时,要记住 PHP V5.3 有一个新的全局变量 __NAMESPACE__。可以考虑使用这个全局变量而不是输入名称空间:

$echo 'I am using this namespace:'.__NAMESPACE__;

IDE 对名称空间的支持

大多数主流的 IDE 已经支持 PHP V5.3。NetBeans V6.8 对名称空间的支持很不错。它不但有代码补全,还会对通过最佳实践改进代码提出建议。例如,PHP 名称空间的最佳实践之一是,在代码中使用绝对引用完全限定名称空间,而不是相对引用。如果输入使用相对名称空间限定符的代码,NetBeans 会在最左边的代码空白边中显示一个灯泡图标。如果把鼠标停在这个图标上,NetBeans 会显示工具提示,它描述建议的修改。如果单击这个图标,NetBeans 会替您修改代码。

Zend Studio 提供相似的功能。如果您还不太愿意开始使用名称空间,可以考虑升级 IDE,在 IDE 的帮助下尝试使用名称空间。注意,可能会发现甚至不需要升级 IDE,因为许多 IDE 已经提供 PHP V5.3 特性一年多了。

PHP Development Tools (PDT) V2.1 也对名称空间提供很好的支持。PDT 是 Eclipse 的插件。参考资料 中提供 PDT 安装说明的链接。

为了启用名称空间支持,首先必须让 Eclipse/PDT 使用 PHP V5.3。在应用程序主菜单中,单击 Window > Preferences,见 图 1。在树面板中展开 PHP,然后选择 PHP Interpreter。然后,把 PHP 版本改为 PHP 5.3 并单击 OK

图 1. Eclipse PDT 插件要求把解释器设置为 PHP V5.3
把解释器设置为 PHP V5.3

单击 File > New Project,展开 PHP 节点,然后单击 PHP Project,就可以创建 PHP 项目。要想创建 PHP 文件,只需在 PHP Explorer 中右键单击项目,然后单击 PHP file。PDT 对名称空间关键字 namespaceuse 使用适当的语法突出显示(见图 2)。

图 2. PDT 对名称空间关键字使用语法突出显示并在 PHP Explorer 和 Outline 视图中显示名称空间
展示 PDT 如何使用语法突出显示的图片

让 PDT 在 PHP Explorer 和 Outline 视图中显示名称空间会很方便,这有助于了解如何把名称空间分配给各个类。PDT 还提供我们希望 IDE 具备的功能:代码补全(见 图 3)。当输入 use 语句时,PDT 会调用代码补全。

图 3. PDT 为名称空间提供代码补全
屏幕图显示用于在代码清单中进行代码补全的名称空间选项列表

当输入类名时,PDT 也会弹出代码补全窗口。例如,如果输入 new Item,PDT 会显示一个列出 Item � denoncourt\retail\item 的窗口。

当选择 denoncourt\retail\item 时,PDT 在实例化行上插入所需的 use 语句和限定符:

use denoncourt\retail\model;
new model\Item();

更酷的是,当输入 new Conan 时,PDT 会显示列出以下内容的窗口:

	Conan � obrien
	Conan � barbarian

这让我能够选择合适的 Conan 类。既然我们又绕回到了两个 Conan 类的示例,本文应该结束了。


结束语

如果您仍然对使用名称空间有点儿犹豫,在放弃学习它之前,我建议打开具有 PHP V5.3 支持的 IDE 并尝试使用名称空间。对于命名约定,一定要设计得比较简单,而不要追求完美的战略。由于我有长期 Java 开发背景,所以喜欢采用 Java 命名约定。我对于 PHP 名称空间使用驼峰大小写形式并去掉下划线。通过在 PHP 项目中使用名称空间,代码会更清晰,更有条理。您会掌握这种在大多数先进语言中很常见的特性。您还会为使用已经使用 PHP V5.3(尤其是名称空间)的许多框架做好准备。


下载

描述名字大小
示例脚本os-php-5.3namespaces_code.zip6KB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


这是您第一次登陆到 developerWorks,已经自动为您创建了您的概要文件。 选择您概要文件中可以公开的信息的信息(如姓名、国家/地区,以及公司),这些信息同时也会与您所发布的内容相关联。 您可以随时更新您的 IBM 账号。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source
ArticleID=697115
ArticleTitle=利用 PHP V5.3 名称空间编写可读且可维护的代码
publish-date=07042011