Chinaunix首页 | 论坛 | 博客
  • 博客访问: 885592
  • 博文数量: 254
  • 博客积分: 5350
  • 博客等级: 大校
  • 技术积分: 2045
  • 用 户 组: 普通用户
  • 注册时间: 2008-06-27 13:27
文章分类

全部博文(254)

文章存档

2015年(1)

2014年(9)

2013年(17)

2012年(30)

2011年(150)

2010年(17)

2009年(28)

2008年(2)

分类: 系统运维

2011-07-04 14:12:05

为什么我们需要命名空间?

随着你的PHP代码库的增长,对之前定义的函数和类名进行修改时风险也更高了,当你试图增加第三方组件或插件时问题更严重,如果存在两个或两个以上的代码集实现了一个“Database”和“User”类会怎么样?

直到目前,唯一的解决办法是使用长的类/函数名,例如Wordpress在每个类和函数名前都使用了前缀“WP_”, Zend Framework使用了极具描述性的命名约定,导致类名非常冗长,如:

Zend_Search_Lucene_Analysis_Analyzer_Common_Text_CaseInsensitive

命名冲突问题可以使用命名空间来解决,PHP常量、类和函数可以被组合到命名空间库中。

如何定义命名空间?

默认情况下,所有常量、类和函数名都放在全局空间下,就和PHP支持命名空间之前一样。

在PHP文件的顶部使用一个关键字namespace就可以定义命名空间,它必须是第一个命令(declare除外),在它前面不能出现非PHP代码、HTML或空格。如:

  1. < ?php  
  2. // define this code in the 'MyProject' namespace  
  3. namespace MyProject;  
  4.  
  5. // ... code ...  

这一行下面的代码都是指定给MyProject命名空间的,为相同代码块嵌套命名空间或定义多个命名空间是不可能的,如果你真这样干,只有最后一个命名空间才能识别,但你可以在同一个文件中定义不同的命名空间代码,如:

  1. < ?php  
  2. namespace MyProject1;  
  3. // PHP code for the MyProject1 namespace  
  4.  
  5. namespace MyProject2;  
  6. // PHP code for the MyProject2 namespace  
  7.  
  8. // Alternative syntax  
  9. namespace MyProject3 {  
  10.  // PHP code for the MyProject3 namespace  
  11. }  
  12. ?>  

尽管这么干是可以的,但我建议你不要这么做,最好还是每个文件中只定义一个命名空间,免得把你弄糊涂了。

子命名空间

PHP允许定义具有层次的命名空间以便库能够细分,子命名空间使用一个反斜线字符(\)分隔,如:

◆MyProject\SubName

◆MyProject\Database\MySQL

◆CompanyName\MyProject\Library\Common\Widget1

调用命名空间代码

在lib1.php文件中我们使用App\Lib1 namespace命名空间定义了一个常量、一个函数和一个类,如:

lib1.php

  1. < ?php  
  2. // application library 1  
  3. namespace App\Lib1;  
  4.  
  5. const MYCONST = 'App\Lib1\MYCONST';  
  6.  
  7. function MyFunction() {  
  8.  return __FUNCTION__;  
  9. }  
  10.  
  11. class MyClass {  
  12.  static function WhoAmI() {  
  13.   return __METHOD__;  
  14.  }  
  15. }  
  16. ?> 

现在我们可以在另一个PHP文件包括这段代码,如:

myapp.php

  1. < ?php  
  2. header('Content-type: text/plain');  
  3. require_once('lib1.php');  
  4.  
  5. echo \App\Lib1\MYCONST . "\n";  
  6. echo \App\Lib1\MyFunction() . "\n";  
  7. echo \App\Lib1\MyClass::WhoAmI() . "\n";  
  8. ?> 

在myapp.php中并没有定义命名空间,因此这段代码存在全局空间中,任何对MYCONST、MyFunction和MyClass的直接引用 都会失败,因为它们存在于App\Lib1命名空间中,为了调用lib1.php中的代码,我们可以在\App\Lib1命名空间前添加前缀定义一个完全 合格的名称,下面是我载入myapp.php时的输出结果:

  1. App\Lib1\MYCONST    
  2. App\Lib1\MyFunction    
  3. App\Lib1\MyClass::WhoAmI 

完全合格名称可以变得很长,定义长名称,如App-Lib1-MyClass,有一些明显的好处。



清单 1. 定义名称空间

            
php
namespace Foo;
class Example {}
?>            

注意,以上 namespace 的声明必须是文件中的第一个命令或输出。在它的前面添加任何内容都会导致一个致命的错误。清单 2 展示了有关这方面的一些例子。

清单 2. 定义名称空间的错误方法
            
/* File1.php */
php
echo "hello world!";
namespace Bad;
class NotGood {}
?>

/* File2.php */
php
namespace Bad;
class NotGood {}
?>            

在清单 2 的第 1 部分中,我们尝试在名称空间定义之前回传到控制台,这导致产生一个致命错误。在清单的第 2 部分中,我们在 php 打开标记的前面多加了一个空格,这样也导致一个致命错误。在编写自己的代码时一定要注意这种情况,因为这是 PHP 名称空间中很常见的一种错误。
但是,上面的两个例子都可以重新编写,将名称空间定义和将在名称空间声明中放入的代码放到独立的文件中,然后再将此文件包含到原始文件中。清单 3 演示了这一点。

清单 3. 修正定义名称空间的错误方法
            
/* Good.php */
php
namespace Good;
class IsGood() {}
?>

/* File1.php */
php
echo "hello world!";
include './good.php';
?>

/* File2.php */
php
include './good.php';
?>            

现在我们已经了解了如何在一个文件中定义代码的名称空间,接下来让我们看看如何在应用程序中利用这个使用名称空间的代码。
使用带有名称空间的代码定义了名称空间并在其中放入代码后,我们就可以在应用程序中方便地使用它。可以使用很多种方法调用带有名称空间的函数、类或常量。一种方式是显式地将名称空间引用为调用的前缀。另一种方法是为名称空间定义一个别名并使用该别名作为调用的前缀,这样做的目的是简化名称空间前缀。最后,我们可以只在代码中使用名称空间,这就使它成为默认名称空间,并且在默认情况下,使所有代码都引用默认名称空间。清单 4 演示了调用之间的不同之处。

清单 4. 在名称空间内调用函数
            
/* Foo.php */
php
namespace Foo;
function bar()
{
    echo "calling bar....";
}
?>

/* File1.php */
php
include './Foo.php';
Foo/bar(); // outputs "calling bar....";
?>

/* File2.php */
php
include './Foo.php';
use Foo as ns;
ns/bar(); // outputs "calling bar....";
?>

/* File3.php */
php
include './Foo.php';
use Foo;
bar(); // outputs "calling bar....";
?>            

清单 4 演示了在名称空间 Foo 内调用函数 bar() 的不同方法。在 File1.php内,我们看到了如何进行显式调用,使用名称空间的名称作为调用前缀。File2.php使用名称空间名称的别名,因此我们使用别名代替名称空间的名称。最后,File3.php 仅使用名称空间,这允许我们不需要使用任何前缀来调用 bar()。
我们还可以在一个文件内定义多个名称空间,只需要在文件中添加更多 namespace 调用。清单 5 演示了这一点。

清单 5. 文件中的多个名称空间
            
php
namespace Foo;
class Test {}

namespace Bar;
class Test {}

$a = new Foo\Test;
$b = new Bar\Test;

var_dump($a, $b);

Output:
object(Foo\Test)#1 (0) {  
}  
object(Bar\Test)#2 (0) {  
}            

现在我们已经基本了解了如何在名称空间内进行调用,让我们了解一些更复杂的名称空间调用以及它们如何工作。
名称空间解析要 熟悉名称空间的使用,其中一个难点就是了解如何进行范围解析。尽管清单 4所示的简单例子是合理的,但是当我们开始对名称空间进行彼此嵌套时,或者在一个名称空间中试图针对全局空间发出调用是,就会出现问题。PHP V5.3提供了可以以合理的方式自动解决这些问题的规则。
让我们创建一些包含(include)文件,每个文件都定义了函数 hello()。

清单 6. 在不同名称空间中定义的 hello() 函数
            
/* global.php */
php
function hello()
{
    echo 'hello from the global scope!';
}
?>

/* Foo.php */
php
namespace Foo;
function hello()
{
    echo 'hello from the Foo namespace!';
}
?>

/* Foo_Bar.php */
php
namespace Foo/Bar;
function hello()
{
    echo 'hello from the Foo/Bar namespace!';
}
?>            

清单 6 在三个不同范围内对 hello() 函数定义了三次:在全局范围内,在 Foo 名称空间中,在 Foo/Bar 名称空间中。根据发出 hello() 函数调用的范围,决定对哪个 hello() 函数执行调用。下面展示了这些调用的例子。在这里,我们将使用 Foo 名称空间查看如何在另一个名称空间中调用 hello() 函数。

清单 7. 从 Foo 名称空间调用所有 hello() 函数
            
php
include './global.php';
include './Foo.php';
include './Foo_Bar.php';

use Foo;

hello();         // outputs 'hello from the Foo namespace!'
Bar\hello();   // outputs 'hello from the Foo/Bar namespace!'
\hello();       // outputs 'hello from the global scope!'
?>            

可以看到,在当前名称空间内引用子名称空间时,可以缩短名称空间前缀(Foo/Bar/hello() 调用可被缩短为 Bar/hello())。并且我们看到如何指定以在全局空间内调用方法:只需使用名称空间操作符作为调用的前缀。
现在,我们已经了解了名称空间的工作机制,下面我们将查看如何在自己的代码中使用它们。


PHP 名称空间用例名称空间的总体目标就是帮助我们更好地组织代码,减少全局空间内的定义数量。在本节中,我们将查看一些例子,看看名称空间如何帮助我们轻松地实现这些目标。
使用名称空间的第三方代码许 多 PHP 应用程序使用来自不同来源的代码,包括像 PEAR 库那样经过精心设计的代码,或者来自 CakePHP 或 ZendFramework 等各种框架的代码,或是来自 Internet上不同位置的代码。在集成这些代码时,最主要的问题之一就是这些代码可能无法恰当地融合到已有代码中;函数或类名可能与应用程序中已经在 使用的内容冲?。
其中一个例子就是 PEAR Date 包。它使用类名 Date,这是一个非常通用的类名,并且可以很好地切入到代码中的其他位置。因此,一个良好的解决方法就是在包内部的 Date.php文件的顶部添加一个简单的名称空间命令。现在,当希望使用 PEAR Date 类而不是我们自己的 PEAR Date 类时,就不会感到迷惑。

清单 8. 按照名称空间的定义使用 PEAR Date 类
            
php

require_once('PEAR/Date.php');

use PEAR\Date;    // the name of the namespace we've specified in PEAR/Date.php

// since the current namespace is PEAR\Date, we don't need to prefix the namespace name
$now = new Date();
echo $now->getDate();  // outputs the ISO formatted date

// this example shows the full namespace specified as part of the class name
$now = new PEAR\Date\Date();
echo $now->getDate();  // outputs the ISO formatted date  
?>            

我们已经在 PEAR/Date.php 文件的 PEAR/Date 名称空间内定义了 PEAR Date 类,因此现在只需在我们的文件中包含代码并使用名称空间,或使用名称空间的名称作为类或函数名的前缀。通过这种方法,我们就可以安全地在应用程序中包含第三方代码。
名称冲突不仅仅是第三方代码才有的问题。如果大型代码库的各个部分永远不会互相靠近,那么也会出现此问题。在下一小节中,我们将了解名称空间如何应对这个情况。


避免实用函数名冲突几乎所有 PHP 应用程序都具有大量实用方法。虽然并非应用程序的任意对象都包含实用方法,并且也不一定存在于应用程序的所有部分,但是总的来说它在应用程序中确实发挥着作用。但是,随着应用程序不断壮大,实用方法会引起维护问题。
其中一个产生问题的位置就是单元测试,我们编写代码来测试运行应用程序的代码。大多数单元测试套件被设计为运行整个测试套件中的所有测试。比如,我们有两 个永远不会包含在一起的实用方法文件,但是在测试套件中,它们就会包含在一起,因为我们会一次性测试整个应用程序。尽管使用这种方式设计应用程序不利于长 期维护,但是它确实存在于大型遗留代码库中。
清单 9 展示了如何避免这一问题。我们有两个文件 utils_left.php 和 utils_right.php,这是面向主要使用右手的用户和主要使用左手的用户的实用函数集合。对于每个文件,我们在其各自的名称空间内分别定义。

清单 9. utils_left.php 和 utils_right.php
            
/* utils_left.php */
php
namespace Utils\Left;

function whichHand()
{
    echo "I'm using my left hand!";
}
?>

/* utils_right.php */
php
namespace Utils\Right;

function whichHand()
{
    echo "I'm using my right hand!";
}
?>            

我们定义了一个 whichHand() 函数,函数的输出表示我们使用哪一只手。在清单 10 中,我们看到可以方便地包含两个文件并在希望调用的名称空间之间进行切换。

清单 10. 同时使用 utils_left.php 和 utils_right.php 的示例
            
php
include('./utils_left.php');
include('./utils_right.php');

Utils\Left\whichHand();    // outputs "I'm using my left hand!"
Utils\Right\whichHand();  // outputs "I'm using my right hand!"

use Utils\Left;
whichHand();                 // outputs "I'm using my left hand!"

use Utils\Right;
whichHand();                 // outputs "I'm using my right hand!"            

现在,两个文件可以安全地包含在一起,并且我们指定了处理函数调用所需使用的名称空间。而且,对现有代码的影响很小,因为重构功能只需要我们在文件的顶部添加 use 语句,表示要使用的名称空间
可以对我们定义的 PHP 代码进一步扩展。在下一小节,我们将了解如何在名称空间内覆盖内部函数。


覆盖内部函数名称虽然 PHP 的内部函数经常可以提供非常棒的实用方法,但有时它们不能按照我们期望的那样执行。我们需要增强它们的行为,以使函数符合我们的期望,但是我们也需要使用另一个名字重新定义函数,从而避免进一步混淆范围。
文件系统函数就需要我们执行这些操作。假设我们需要确保 file_put_contents() 创建的任何文件具有某些权限集。比如,假设我们希望这些文件的权限为只读;我们可以使用一个新的名称空间重新定义函数,如下所示。

清单 11. 在名称空间内定义 file_put_contents()                                
            
php
namespace Foo;

function file_put_contents( $filename, $data, $flags = 0, $context = null )
{
    $return = \file_put_contents( $filename, $data, $flags, $context );
   
    chmod($filename, 0444);

    return $return;
}
?>            

我们在函数内调用内部 file_put_contents() 函数并使用一个反斜杠作为函数名的前缀,表示该函数应当在全局范围内处理,这表示将调用内部函数。调用了内部函数后,我们随后对文件执行 chmod() 命令来设置相应的权限。
还有许多例子可以演示如何使用名称空间增强代码。在任何情况下,我们应避免执行不恰当的修改,比如将函数名或类名作为前缀以生成独特的名称。我们现在还了解了如何使用名称空间在大型应用程序中更加安全地包含第三方代码,同时不需要担心名称冲突。

结束语PHP V5.3的名称空间是该语言中一个非常受欢迎的新增特性,可以帮助开发人员合理地组织应用程序的代码。该特性使您能够避免使用标准来处理名称空间,允许您编写更高效的代码。尽管名称空间的出现经历了很长时间,但对于受名称冲突困扰的大型 PHP 应用程序来说,它是一个非常受欢迎的特性
阅读(882) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~