PHP命名空间(Namespace)的使⽤详解
命名空间⼀个最明确的⽬的就是解决重名问题,PHP中不允许两个函数或者类出现相同的名字,否则会产⽣⼀个致命的错误。这种情况下只要避免命名重复就可以解决,最常见的⼀种做法是约定⼀个前缀。
例:项⽬中有两个模块:article和message board,它们各⾃有⼀个处理⽤户留⾔的类Comment。之后我可能想要增加对所有⽤户留⾔的⼀些信息统计功能,⽐如说我想得到所有留⾔的数量。这时候调⽤它们Comment提供的⽅法是很好的做法,但是同时引⼊各⾃的Comment类显然是不⾏的,代码会出错,在另⼀个地⽅重写任何⼀个Comment也会降低维护性。那这时只能重构类名,我约定了⼀个命名规则,在类名前⾯加上模块名,像这样:Article_Comment、MessageBoard_Comment
可以看到,名字变得很长,那意味着以后使⽤Comment的时候会写上更多的代码(⾄少字符多了)。并且,以后如果要对各个模块增加更多的⼀些整合功能,或者是互相调⽤,发⽣重名的时候就需要重构名字。当然在项⽬开始的时候就注意到这个问题,并规定命名规则就能很好的避免这个问题。另⼀个解决⽅法可以考虑使⽤命名空间。
注明:本⽂提到的常量:PHP5.3开始const关键字可以⽤在类的外部。const和define都是⽤来声明常量的(它们的区别不详述),但是在命名空间⾥,define的作⽤是全局的,⽽const则作⽤于当前空间。我在⽂中提到的常量是指使⽤const声明的常量。
基础
命名空间将代码划分出不同的空间(区域),每个空间的常量、函数、类(为了偷懒,我下边都将它们称为元素)的名字互不影响,这个有点类似我们常常提到的’封装’的概念。
创建⼀个命名空间需要使⽤namespace关键字,这样:
<?php
//创建⼀个名为'Article'的命名空间
namespace Article;
>
要注意的是,当前脚本⽂件的第⼀个命名空间前⾯不能有任何代码,下⾯的写法都是错误的:
例⼀
//在脚本前⾯写了⼀些逻辑代码
<?php
$path = "/";
class Comment { }
国内php空间
namespace Article;
>
例⼆
</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为什么要这样做),但是为了正确区分元素,还是建议调⽤函数的时候加上 \
名称术语
在说别名和导⼊之前,需要知道关于空间三种名称的术语,以及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的命名空间刚刚接触,也不能随便给⼀些没有实践的建议。我个⼈认为命名空间的作⽤和功能都很强⼤,如果要写插件或者通⽤库的时候再也不⽤担⼼重名问题。不过如果项⽬进⾏到⼀定程度,要通过增加命名空间去解决重名问题,我觉得⼯作量不会⽐重构名字少。也不得不承认它的语法会对项⽬增加⼀定的复杂度,因此从项⽬⼀开始的时候就应该很好的规划它,并制定⼀个命名规范。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。