PHP支持命名空间(Namespace)是一个曲折的过程。令人欣慰的是,它被添加到了PHP 5.3中,它的出现让PHP的结构改变了很多。但是,我们该如何正确使用它?



什么是命名空间?


把命名空间想象成一个你可以把所有东西放到当中去的抽屉:铅笔、尺子、纸张等等。这些都是你的资产。别人无法直接看到你抽屉里的东西,当然也没办法往里面放东西。为避免使用别人的东西,你要为你的抽屉定义一个清晰的名称。


以前,开发者不得不在他们的类、变量和常量中使用下划线(underscore),以避免混淆。这相当于把所有人的资产贴上一个很长的标签,然后放到同一个大抽屉里。当然,它至少是一种不错的组织方式,但它的效率很低。


命名空间将改善这一情况。你可以在单独的命名空间里定义相同函数、类、接口和常量,而不会产生致命错误。本质上,命名空间无非是PHP代码中,分层次的、被标记的代码块。



定义命名空间


命名空间定义语句必须是一个PHP文件中的第一条语句,declare是唯一的例外,它是唯一一个可以放在namespace语句之前的语句,并且仅当其用于定义脚本的编码时。


只需简单的使用namespace关键字便可定义一个命名空间。命名空间的名称必须符合PHP的命名规则。因此,命名空间必须以字母或下划线开头,后面跟任意字母、数字或下划线。



<?php
namespace LoRui {
function run() {
echo '运行在一个命名空间里!';
}
}

如果你想把一个代码块分配到全局空间,可以使用没有名称的namespace关键字代码段里



<?php
namespace {
//全局空间!
}

同一个文件内可以定义多个命名空间。


<?php
namespace LoRui_com {
}
namespace Sisi_biz {
}
namespace {
}

足间舞ROOM



你可以将一个命名空间定义到不同的文件里,处理器会自动包含合并这些文件。因此,一个好的编程实践是限制一个文件中的命名空间数量,就像类那样。


需要注意的是,命名空间两端的大括号是可选的。事实上,坚持“一个命名空间一个文件”原则,即使省略大括号也可以让你的代码保持简洁。



子命名空间(Sub-namespaces)


命名空间可以像电脑上的文件系统的目录那样保持层次。子命名空间对于组织项目结构非常有用。例如,如果你的项目需要访问数据库,你也许想把异常和连接句柄等所有数据库相关的代码组织在一个名叫Database的子命名空间里。


将子命名空间存放在子目录下,以维护可扩展性。这是推荐的结构。按照PSR-0标准,可以在你的项目中使用自动载入来简化操作。


使用反斜杠(backslash)来分隔命名空间。



//com/lorui/database/connection.php
<?php
namespace Com\Lorui\Database
class Connection{
}

你可以有任意多个命名空间,如果你需要的话。



<?php
namespace Com\Lorui\Blog\Auth\Handler\Mail;
class Gmail{
}

PHP不支持内嵌子命名空间的定义。下面的例子将引发描述为“Namespace declarations cannot be nested”(命名空间的定义不能嵌套)的致命错误。



<?php
//本段代码将引发Namespace declarations cannot be nested
namespace Com{
namespace Lorui{
namespace Database {
class Connection {}
}
}
}


调用命名空间


如果你有一个新项目,要从一个不同的命名空间里调用一个函数或使用一个常量,请使用反斜杠。它们可以视为三个不同的点:



  • 非限定名称

  • 限定名称

  • 完全限定名称




完全限定名称、限定名称和非限定名称


非限定名称


它是指未包含任意命名空间以外的类、函数或常量。



<?php
namespace LoRui_com;
class MyClass{
static function method(){
echo 'lorui.com';
}
}
MyClass::method();


限定名称


它是指如何访问子命名空间层次;它使用反斜杠进行操作。



<?php
namespace LoRui_com;
require 'lorui_com/database/connection.php';
$connection = new Database\Connection();

下面代码引发致命错误:“Fatal error: Class ‘MyProject\Database\MyProject\FileAccess\Input’ not found”,因为MyProject\FileAccess\Input是从当前命名空间查找。



<?php
namespace MyProject\Database;
requrie 'myproject/fileaccess/input.php';
$input = new MyProject\FileAccess\Input();

足间舞ROOM



完全限定名称


上述的非限定名称和限定名称都是相对于当前的命名空间。它们只用来访问该层或更深层的命名空间。


如果你想访问更高层次的函数、类或常量,你需要使用完全限定名称——完整路径而不是相对路径。在调用前加上反斜杠,让PHP知道该调用是从全局空间而不是相对于本层的空间。



<?php
namespace MyProject\Database;
require 'myproject/fileaccess/input.php';
$input = new \MyProject\FileAccess\Input();

PHP内置函数不需要使用完全限定名称。使用完全限定名称调用一个不存在的命名空间里的常量或函数,PHP从全局命名空间中查找。



<?php
namespace LoRui_com;
var_dump($query); //重载内置函数
\var_dump($query); //内置函数

function var_dump($v) {
echo '重载内置函数 var_dump():'.$v.'
';
}


动态调用


PHP是一种动态编程语言,自然也可以在调用命名空间时使用该功能。它的本质与实例化一个变量类或包含变量文件一样。


<?php
namespace Sisi_biz;
$project_name = 'Lorui_com';
$package_name = 'Database';
$class_name = 'Connection';

足间舞ROOM


//包含变量文件
require strtolower($project_name . '/' . $package_name . '/' . $class_name) . '.php';
//变量命名空间中的变量类的名称。注意如何转义反斜杠
$full_qualified_name = $project_name . '\\' . $package_name . '\\' . $class_name;
$connection = new $full_qualified_name;



namespace关键字


关键字namespace的作用不是仅仅用于定义命名空间,它也可以用来指示当前命名空间,功能上类似于类中的self关键字。


<?php
namespace LoRui_com;
function run() {
echo '运行于命名空间中。';
}
//解析为 LoRui_com\run
run();
//明确解析为 LoRui_com\run
namespace\run();


__NAMESPACE__常量


self不能用于确定当前类的名称一样,关键字namespace也不能用于确定当前命名空间的名称。这就是为什么需要__NAMESPACE__常量。



<?php
namespace LoRui_com\Database;
echo __NAMESPACE__; // 'LoRui_com\Database'

这个常量非常有用,它可以用于调试,可用于我们之前讨论的动态代码中。



别名或导入


PHP的命名空间支持导入。导入也称为别名。只有类、接口和命名空间可被别名或导入。


导入是命名空间非常有用的基本功能。它让你有能力去使用外部包的代码,比如类库,而不用担心命名冲突。使用use关键字来实现导入。另外,你可以使用as关键字指定别名。


use [类、接口、命名空间的名称] as [可选的别名]


如何完成


一个完整限定名称可以使用一个短的别名,所以你不需要在每次使用它的时候都写上长长的完整限定名称。应该在命名空间的上层作用域或全局作用域使用别名或导入。



<?php
namespace Sisi_biz;
require 'lorui_com/database/connection.php';
//需要使用完整限定名称来访问数据库连接
$connection = new \Lorui_com\Database\Connection();
//导入命名空间
use Lorui_com\Database\Connection;

足间舞ROOM



//可以直接调用了
$connection = new Connection();
//导入Lorui_com\Database命名空间
user Lorui_com\Database;
$connection = new Database\Connection();

或者,你可以使用别名:



<?php
namespace Sisi_biz;
require 'lorui_com/database/connection.php';
use Lorui_com\Database\Connection as LoruiConnection;
$connection = new LoruiConnection();
use Lorui_com\Database as LoruiDatabase;
$connection = new LoruiDatabase\Connection();

你也可以导入全局类,比如Exception。一旦完成导入,你再也不需要写它的完整限定名称。



<?php
namespace Lorui;
throw new Exception('exception!');//致命错误:类'Lorui\Exception'不存在
throw new \Execption('exception!'); //正确
//导入全局Exception。'Exception'由标准点的绝对路径指定,其前置的反斜杠可以省略
use Exception;
throw new Execption('exception!'); //正确

虽然可以动态调用命名空间,但不支持动态导入。



<?php
namespace Sisi_biz;
$paraser = 'markdown';
//合法
require 'lorui/blog/parser/'. $parser. '.php';
//非法
user Lorui\Blog\Parser\$parser;


上一篇 下一篇