PHP的命名空间
1.什么是命名空间,官方文档定义为:
什么是命名空间?从广义上来说,命名空间是一种封装事物的方法。在很多地方都可以见到这种抽象概念。例如,在操作系统中目录用来将相关文件分组,对于目录中的文件来说,它就扮演了命名空间的角色。具体举个例子,文件 foo.txt 可以同时在目录/home/greg 和 /home/other 中存在,但在同一个目录中不能存在两个 foo.txt 文件。另外,在目录 /home/greg 外访问 foo.txt 文件时,我们必须将目录名以及目录分隔符放在文件名之前得到 /home/greg/foo.txt。这个原理应用到程序设计领域就是命名空间的概念。
2.命名空间的作用,官方定义为:
- 用户编写的代码与php内部的类/函数/常量或第三方类/函数/常量之间的名字冲突。
- 为很长的标识符名称(通常是为了缓解第一类问题而定义的)创建一个别名(或简短)的名称,提高源代码的可读
其实命名空间的作用就是解决类、函数、常量重名的问题,变量名重复,可以放到不同的函数中,函数名重复可以放到不同的类中,那类名重复呢?就可以用命名空间解决
3.如何定义一个命名空间
namespace xx;
范围:从这一行开始到下一个命名空间的开始
注意事项:在声明命名空间之前唯一合法的代码是用于定义源文件编码方式的 declare 语句。另外,所有非 php 代码包括空白符都不能出现在命名空间的声明之前
虽然任意合法的php代码都可以包含在命名空间中,但只有以下类型的代码受命名空间的影响,它们是:类(包括抽象类和traits)、接口、函数和常量。另外,与php其它的语言特征不同,同一个命名空间可以定义在多个文件中,即允许将同一个命名空间的内容分割存放在不同的文件中。
4.定义子命名空间
与目录和文件的关系很象,php 命名空间也允许指定层次化的命名空间的名称。因此,命名空间的名字可以使用分层次的方式定义:
<?php
namespace myproject\sub\level;
const connect_ok = 1;
class connection { /* ... */ }
function connect() { /* ... */ }
?>
上面的例子创建了常量myproject\sub\level\connect_ok,类 myproject\sub\level\connection和函数 myproject\sub\level\connect。
5.也可以在同一个文件中定义多个命名空间。在同一个文件中定义多个命名空间有两种语法形式。
<?php
namespace myproject;
const connect_ok = 1;
class connection { /* ... */ }
function connect() { /* ... */ }
namespace anotherproject;
const connect_ok = 1;
class connection { /* ... */ }
function connect() { /* ... */ }
?>
不建议使用这种语法在单个文件中定义多个命名空间,建议使用下面大括号的语法定义命名空间
<?php
namespace myproject {
const connect_ok = 1;
class connection { /* ... */ }
function connect() { /* ... */ }
}
namespace anotherproject {
const connect_ok = 1;
class connection { /* ... */ }
function connect() { /* ... */ }
}
?>
在实际编程中非常不提倡在一个文件中定义多个命名空间。这种方式的主要用于将多个 php 脚本合并在同一个文件中。
将全局的非命名空间中的代码与命名空间中的代码组合在一起,只能使用大括号形式的语法。全局代码必须用一个不带名称的 namespace 语句加上大括号括起来,例如:
<?php
declare(encoding='utf-8'); // 除了开始的declare语句外,命名空间的括号外不得有任何php代码。
namespace myproject {
const connect_ok = 1;
class connection { /* ... */ }
function connect() { /* ... */ }
}
namespace { // global code
session_start();
$a = myproject\connect();
echo myproject\connection::start();
}
?>
6.使用命名空间
注明:
本文提到的常量:php5.3开始const关键字可以用在类的外部。const和define都是用来声明常量的(它们的区别不详述),但是在命名空间里,define的作用是全局的,而const则作用于当前空间。我在文中提到的常量是指使用const声明的常量。
命名空间将代码划分出不同的空间(区域),每个空间的常量、函数、类(为了偷懒,我下边都将它们称为元素)的名字互不影响, 这个有点类似我们常常提到的‘封装'的概念。
创建一个命名空间需要使用namespace关键字,这样:
<?php
//创建一个名为'article'的命名空间
namespace article;
?>
要注意的是,当前脚本文件的第一个命名空间前面不能有任何代码,下面的写法都是错误的:
//例一
//在脚本前面写了一些逻辑代码
<?php
$path = "/";
class comment { }
namespace article;
?>
//例二
//在脚本前面输出了一些字符
<html></html>
<?php
namespace article;
?>
为什么要说第一个命名空间呢?因为同一脚本文件中可以创建多个命名空间。
下面我创建了两个命名空间,顺便为这两个空间各自添加了一个comment类元素:
<?php
//创建一个名为'article'的命名空间
namespace article;
//此comment属于article空间的元素
class comment { }
//创建一个名为'messageboard'的命名空间
namespace messageboard;
//此comment属于messageboard空间的元素
class comment { }
?>
在不同空间之间不可以直接调用其它元素,需要使用命名空间的语法:
<?php
namespace article;
class comment { }
namespace messageboard;
class comment { }
//调用当前空间(messageboard)的comment类
$comment = new comment();
//调用article空间的comment类
$article_comment = new \article\comment();
?>
可以看到,在messageboard空间中调用article空间里的comment类时,使用了一种像文件路径的语法: \空间名\元素名
除了类之外,对函数和常量的用法是一样的,下面我为两个空间创建了新的元素,并在messageboard空间中输出了它们的值。
<?php
namespace article;
const path = '/article';
function getcommenttotal() {
return 100;
}
class comment { }
namespace messageboard;
const path = '/message_board';
function getcommenttotal() {
return 300;
}
class comment { }
//调用当前空间的常量、函数和类
echo path; ///message_board
echo getcommenttotal(); //300
$comment = new comment();
//调用article空间的常量、函数和类
echo \article\path; ///article
echo \article\getcommenttotal(); //100
$article_comment = new \article\comment();
?>
然后我的确得到了article空间的元素数据。
子空间
命名空间的调用语法像文件路径一样是有道理的,它允许我们自定义子空间来描述各个空间之间的关系。
抱歉我忘了说,article和message board这两个模块其实都是处于同一个blog项目内。如果用命名空间来表达它们的关系,是这样:
<?php
//我用这样的命名空间表示处于blog下的article模块
namespace blog\article;
class comment { }
//我用这样的命名空间表示处于blog下的message board模块
namespace blog\messageboard;
class comment { }
//调用当前空间的类
$comment = new comment();
//调用blog\article空间的类
$article_comment = new \blog\article\comment();
?>
而且,子空间还可以定义很多层次,比如说 blog\article\archives\date
公共空间
我有一个common_inc.php脚本文件,里面有一些好用的函数和类:
<?php
function getip() { }
class filterxss { }
?>
在一个命名空间里引入这个脚本,脚本里的元素不会归属到这个命名空间。如果这个脚本里没有定义其它命名空间,它的元素就始终处于公共空间中:
<?php
namespace blog\article;
//引入脚本文件
include './common_inc.php';
$filter_xss = new filterxss(); //出现致命错误:找不到blog\article\filterxss类
$filter_xss = new \filterxss(); //正确
?>
调用公共空间的方式是直接在元素名称前加 \ 就可以了,否则php解析器会认为我想调用当前空间下的元素。除了自定义的元素,还包括php自带的元素,都属于公共空间。
要提一下,其实公共空间的函数和常量不用加 \ 也可以正常调用(不明白php为什么要这样做),但是为了正确区分元素,还是建议调用函数的时候加上 \
7.名称术语
在说别名和导入之前,需要知道关于空间三种名称的术语,以及php是怎样解析它们的。官方文档说得非常好,我就直接拿来套了。
1.非限定名称,或不包含前缀的类名称,例如 $comment = new comment();。如果当前命名空间是blog\article,comment将被解析为blog\article\comment。如果使用comment的代码不包含在任何命名空间中的代码(全局空间中),则comment会被解析为comment。
2.限定名称,或包含前缀的名称,例如 $comment = new article\comment();。如果当前的命名空间是blog,则comment会被解析为blog\article\comment。如果使用comment的代码不包含在任何命名空间中的代码(全局空间中),则comment会被解析为comment。
3.完全限定名称,或包含了全局前缀操作符的名称,例如 $comment = new \article\comment();。在这种情况下,comment总是被解析为代码中的文字名(literal name)article\comment。
其实可以把这三种名称类比为文件名(例如 comment.php)、相对路径名(例如 ./article/comment.php)、绝对路径名(例如 /blog/article/comment.php),这样可能会更容易理解。
我用了几个示例来表示它们:
<?php
//创建空间blog
namespace blog;
class comment { }
//非限定名称,表示当前blog空间
//这个调用将被解析成 blog\comment();
$blog_comment = new comment();
//限定名称,表示相对于blog空间
//这个调用将被解析成 blog\article\comment();
$article_comment = new article\comment(); //类前面没有反斜杆\
//完全限定名称,表示绝对于blog空间
//这个调用将被解析成 blog\comment();
$article_comment = new \blog\comment(); //类前面有反斜杆\
//完全限定名称,表示绝对于blog空间
//这个调用将被解析成 blog\article\comment();
$article_comment = new \blog\article\comment(); //类前面有反斜杆\
//创建blog的子空间article
namespace blog\article;
class comment { }
?>
其实之前我就一直在使用非限定名称和完全限定名称,现在它们终于可以叫出它们的名称了。
别名和导入
别名和导入可以看作是调用命名空间元素的一种快捷方式。php并不支持导入函数或常量。
它们都是通过使用use操作符来实现:
<?php
namespace blog\article;
class comment { }
//创建一个bbs空间(我有打算开个论坛)
namespace bbs;
//导入一个命名空间
use blog\article;
//导入命名空间后可使用限定名称调用元素
$article_comment = new article\comment();
//为命名空间使用别名
use blog\article as arte;
//使用别名代替空间名
$article_comment = new arte\comment();
//导入一个类
use blog\article\comment;
//导入类后可使用非限定名称调用元素
$article_comment = new comment();
//为类使用别名
use blog\article\comment as comt;
//使用别名代替空间名
$article_comment = new comt();
?>
我注意到,如果导入元素的时候,当前空间有相同的名字元素将会怎样?显然结果会发生致命错误。
例:
<?php
namespace blog\article;
class comment { }
namespace bbs;
class comment { }
class comt { }
//导入一个类
use blog\article\comment;
$article_comment = new comment(); //与当前空间的comment发生冲突,程序产生致命错误
//为类使用别名
use blog\article\comment as comt;
$article_comment = new comt(); //与当前空间的comt发生冲突,程序产生致命错误
?>
动态调用
php提供了namespace关键字和__namespace__魔法常量动态的访问元素,__namespace__可以通过组合字符串的形式来动态访问:
<?php
namespace blog\article;
const path = '/blog/article';
class comment { }
//namespace关键字表示当前空间
echo namespace\path; ///blog/article
$comment = new namespace\comment();
//魔法常量__namespace__的值是当前空间名称
echo __namespace__; //blog\article
//可以组合成字符串并调用
$comment_class_name = __namespace__ . '\comment';
$comment = new $comment_class_name();
?>
字符串形式调用问题
上面的动态调用的例子中,我们看到了字符串形式的动态调用方式,如果要使用这种方式要注意两个问题。
1. 使用双引号的时候特殊字符可能被转义
<?php
namespace blog\article;
class name { }
//我是想调用blog\article\name
$class_name = __namespace__ . "\name"; //但是\n将被转义为换行符
$name = new $class_name(); //发生致命错误
?>
2. 不会认为是限定名称
php在编译脚本的时候就确定了元素所在的空间,以及导入的情况。而在解析脚本时字符串形式调用只能认为是非限定名称和完全限定名称,而永远不可能是限定名称。
<?php
namespace blog;
//导入common类
use blog\article\common;
//我想使用非限定名称调用blog\article\common
$common_class_name = 'common';
//实际会被当作非限定名称,也就表示当前空间的common类,但我当前类没有创建common类
$common = new $common_class_name(); //发生致命错误:common类不存在
//我想使用限定名称调用blog\article\common
$common_class_name = 'article\common';
//实际会被当作完全限定名称,也就表示article空间下的common类,但我下面只定义了blog\article空间而不是article空间
$common = new $common_class_name(); //发生致命错误:article\common类不存在
namespace blog\article;
class common { }
?>
总结
我对php的命名空间刚刚接触,也不能随便给一些没有实践的建议。我个人认为命名空间的作用和功能都很强大,如果要写插件或者通用库的时候再也不用担心重名问题。不过如果项目进行到一定程度,要通过增加命名空间去解决重名问题,我觉得工作量不会比重构名字少。也不得不承认它的语法会对项目增加一定的复杂度,因此从项目一开始的时候就应该很好的规划它,并制定一个命名规范。
上一篇: 实现一个三级菜单小功能