[译]PHP命名空间

标准

空间是一条坎坷的路程。谢天谢地它被加入到了PHP5.3中,从那以后PHP代码的可用结构得到了很大的改善。但是我们应该怎么使用它呢?

 

什么是命名空间?

把命名空间想像成一个抽屉,你可以把各种各样的东西放到里面:铅笔,尺子,纸张等等。这些都是你的东西。在抽屉下面一层是其他人的东西,他也放了同样的东西在里面。为了防止互相使用他人的东西,你决定给抽屉贴一个标签这样就很清楚哪些东西是谁的了。

之前开发者必须在他们的类名、函数名和常量中使用下划线来区分代码库。这样当于给每样东西都打了个标签然后把他们全部放到一个大抽屉里。当然这也是一种组织方式,但是它是非常低效的。

命名空间解决了这个问题!你可以在不同的命名空间中声明同名函数,类,接口和常量,而不会收到“fatal error”错误。本质上命名空间仅仅就是一个分层标记的包含正常PHP代码的块。

 

你正在使用它们!

记住很重要的一点,你在间接使用命名空间;因为在PHP 5.3中,所有没有声明在用户定义的命名空间的定义都牌全局命名空间下。

全局命名空间同时包含了所有内置的PHP函数定义,像echo(),mysqli_connect(),以及Exception类。因为全局命名空间没有唯一的标识名,它常常被称作全局空间。

你的PHP脚本可以在没有命名空间的情况下完美的运行,并且短期内这种情况不会改变。

 

定义命名空间

命名空间应当是PHP解释器在PHP文件中碰到的第一个声明。唯一允许出现在命名空间前面的声明是“declare”,而且必须是它用于声明脚本的编码。

声明命名空间很简单,就是使用“namespace”关键字。命名空间的取名规则与PHP中其它标识符(如变量)一样。因此命名空间必须以字母或下划线开头,以任意多个字母、数字或下划线组成。

<?php 
namespace MyProject {
 // Regular PHP code goes here, anything goes!
   function run() { 
      echo 'Running from a namespace!'; 
   }
 }

如果你希望将代码块放入到全局空间,你可以使用后面不跟名字的namespace关键字:

<?php 
namespace{
 // Global space! 
}

你可以在一个文件中使用多个命名空间:

<?php 
namespace MyProject { } 
namespace MySecondProject { } 
namespace{ }

你也可以将一个命名空间的代码分散到不同的文件中;文件包含的过程将自动合并它们。但是良好的写码习惯是将一个命名空间放在一个文件中,就像写class一样。

注意命名空间代码块两头的括号是可有可无的。事实上坚持一个命名空间一个文件的原则并省略大括号使你的代码更加清晰——这样就不需要缩进被嵌套的代码。

子命名空间

命名空间可以遵循一定的层级,就像你计算机文件系统中的目录一样。子命名空间对于组织一个项目的结构非常有用。例如,如果你的项目需要数据库访问,你可能会把所有数据库相关的代码,如数据库异常和连接处理,放在一个叫做“Database”的子命名空间中。

为了保持灵活性,建议将子命名空间放在子目录下。这使得你的项目结构更好并且更容易使用PSR-0标准规定的自动装载功能。

PHP使用反斜杠作为命名空间的分隔符。

// myproject/database/connection.php 
<?php 
namespace MyProject\Database classConnection {
    // Handling database connections
 }

你可以使用任意多级子命名空间。

<?php 
namespace MyProject\Blog\Auth\Handler\Social; classTwitter { 
// Handles Twitter authentification
 }

PHP暂时不支持使用嵌套代码块定义子命名空间。下面的代码会抛出fatal error:“Namespace declarations cannot be nested(命名空间声明不能嵌套)”

<?php 
namespace MyProject { 
    namespace Database { 
       classConnection { }
     }
 }

 

调用命名空间中的代码

如果你想要实例化一个新的对象,调用或使用不同命名空间下的方法或常量,使用反斜杠符号。它们可以以三种观察点来解析:

  • 非限定名(Unqualified name)
  • 限定名(Qualified name)
  • 完全限定名(Fully qualified name)

非限定名

它就是不包含任何名称空间引用的类、函数或常量名。如果你刚接触名称空间,它就和你原来常用的观察点一样。

<?php 
namespace MyProject;
class MyClass { 
    static function static_method() {
         echo 'Hello, world!';
    }
 } 
// Unqualified name, resolves to the namespace you are currently in 

(MyProject\MyClass) MyClass:static_method();

 

限定名

这是我们访问子命名空间的方法,使用反斜杠符号。

<?php 
namespace MyProject; 
require 'myproject/database/connection.php'; 
// Qualified name, instantiating a class from a sub-namespace of MyProject $connection = new Database\Connection();

下面的例子会抛出一个错误:“Fatal error: Class ‘MyProject\Database\MyProject\FileAccess\Input’ not found”,因为路径“MyProject\FileAccess\Input”是相对于你当前所在的命名空间的。

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

完全限定名

非限定名和限定名都是相对于你当前所在的命名空间的,它们只能用于同级和更深级别的命名空间。

如果你想访问上级命名空间的函数,类或者常量,你需要使用完全限定名——使用绝对路径而不是相对路径。简单的说就是在路径最前面加一个反斜杠。这告诉PHP它应该从全局空间开始解析而不是相对于当前所在空间。

<?php 
namespace MyProject\Database; 
require 'myproject/fileaccess/input.php';
 // Trying to access the MyProject\FileAccess\Input class 
// This time it will work because we use the fully qualified name, note the leading backslash 
$input = new \MyProject\FileAccess\Input();

PHP内置的函数不需要使用完全限定名。如果使用非限定名调用的常量或函数名在你所在的命名空间中不存在,PHP将会在全局命名空间中查找它们。这是内置的容错特性,但不适用于非限定类名。

了解这些后,我们可以在重载PHP内置函数的同时又还能使用原生的函数(常量也一样)。

<?php 
namespace MyProject; var_dump($query);
// Overloaded \var_dump($query); 
// Internal 
// We want to access the global Exception class 
// The following will not work because there's no class called Exception in the MyProject\Database namespace and unqualified class names do not have a fallback to global space 
// throw new Exception('Query failed!'); 
// Instead, we use a single backslash to indicate we want to resolve from global space throw new \Exception('ailed!'); 
function var_dump() { 
    echo 'Overloaded global var_dump()!<br />';
}

动态调用

PHP是动态编程放言,所以你可以动态地调用命名空间。这与使用变量初始化类或包含文件类似。PHP使用的命名空间分隔符是字符串中的元字符。所以当你将命名空间存在字符串中时别忘了转义把斜杠!

<?php 
namespace OtherProject; 
$project_name = 'MyProject'; 
$package_name = 'Database'; 
$class_name = 'Connection'; // Include a variable file require strtolower($project_name . '/'. $package_name . '/' . $class_name) . '.php'; 
// Name of a variable class in a variable namespace. Note how the backslash is escaped to use it properly 
$fully_qualified_name = $project_name . '\\' . $package_name . '\\' . $class_name; 
$connection = new $fully_qualified_name();

namespace关键字

namespace关键字不光可以用于定义命名空间,它还可以用于显式声明当前命名空间,有点像类中的self关键字。

<?php 
namespace MyProject; function run() { 
echo 'Running from a namespace!'; 
}
// Resolves to MyProject\run run(); 
// Explicitly resolves to MyProject\run namespace\run();

 

__NAMESPACE__ 常量

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

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

当你刚开始学命名空间时这个常量非常有用,它对代码调试也很有帮助。因为它是一个字符串,所以也可以像前面讲的一样用来与动态代码连接。

 

别名与导入

PHP的命名空间支持导入。导入也可以被看作是别名。只有类,接口和命名空间可以被赋予别名或导入。

导入是命名空间非常有用和基础的功能。它赋予你充分利用外部包的能力,像类库一样,而不需要担心命名冲突。使用use关键字进行导入,也可以用as关键字给命名空间赋一个别名。

use [name of class, interface or namespace] as [optional_custom_alias]

 

它是如何工作的

我们可以给完全限定名取一个更短的非限定别名这样你就不用每次使用它时都写长长的完全限定名了。别名或导入必须出现在命名空间和全局作用域中的最外层作用域。在方法或函数中使用它会报语法错误。

<?php 
namespace OtherProject; 
// This holds the MyProject\Database namespace with a Connection class in it 
require 'myproject/database/connection.php'; 
// If we want to access the database connection of MyProject, we need to use its fully qualified name as we're in a different name space 
$connection = new \MyProject\Database\Connection(); 
// Import the Connection class (it works exactly the same with interfaces) use MyProject\Database\Connection;
 // Now this works too! Before the Connection class was aliased PHP would not have found an OtherProject\Connection class 
$connection = new Connection(); 
// Import the MyProject\Database namespace use MyProject\Database; $connection = new Database\Connection()

此外,你可以给它取一个不同的别名:

<?php 
namespace OtherProject; 
require 'myproject/database/connection.php'; 
use MyProject\Database\Connection as MyConnection;
$connection = new MyConnection(); 
use MyProject\Database as MyDatabase; 
$connection = new MyDatabase\Connection();

 

你也可以导入全局类,比如Exception类。当它被导入后,你就再也不需要写完全限定名了。

注意导入不是被解析为相对当前命名空间的,而是绝对的,从全局空间开始。这意味着不需要也不推荐写开头的反斜杠。

<?php 
namespace MyProject; 
// Fatal error: Class 'SomeProject\Exception' not found throw new Exception('An exception!'); 
// OK! throw new \Exception('An exception!'); 
// Import global Exception. 'Exception' is resolved from an absolute standpoint, the leading backslash is unnecessary use Exception; 
// OK! throw new Exception('An exception!');

虽然可以动态的调用命名空间代码,但是不能动态地导入命名空间。

<?php 

namespace OtherProject; 
$parser = 'markdown'; // This is valid PHP 
require 'myproject/blog/parser/' . $parser . '.php'; // This is not use 
MyProject\Blog\Parser\$parser;

总结

命名空间可以用于避免命名冲突,为你的代码库引入了更加灵活和结构化的特性。不过记住你不一定要使用命名空间,它用于结合面向对象的工作流。不过希望你将会考虑在你将来的项目中使用命名空间以使项目更棒。你决定了吗?

 

发表评论