PHP的命名空间功能已经出来很久了,但是一直以来没怎么深究过,这次赶着有时间所以特意翻着手册做一个整理和总结帮助自己完善完善,原本准备一篇写完,但发现内容其实还是蛮多的,放一起太长看着累,所以分两篇博客要好些。
一:命名空间概念:命名空间是一种封装事物的方法,类似于目录和文件。
命名空间解决的问题(手册上也写的很清楚,下面按照自己的理解简化了):
1:解决程序编写者自己写的类、常量、函数和PHP内部的或者第三方的出现名称冲突的情况。
2:创建别名,帮助解决类、常量、函数名称过长的情况,帮助提高代码的可读性,另外名称过长其实通常都是因为为了缓解第一类问题导致的。
二:如何定义命名空间
1:命名空间用关键字namespace声明,同时命名空间必须位于其他代码之前,包括任何非PHP代码以及空白符(PHP的declare关键字除外),否则会抛出一个fatal error。
例如:
注意1:如果命名空间namespace前没有任何代码及空白符,但还是出现fatal error,这个应该是由于bom头导致的,去掉bom头就可以了。 注意2:在命名空间下,虽然可以放置所有合法的PHP代码,但是受命名空间影响的仅有类(抽象类以及traits)和接口、常量和函数。
2:与目录和文件的关系很象,PHP 命名空间也允许指定层次化的命名空间的名称。因此,命名空间的名字可以使用分层次的方式定义,分隔符是\。
例如:
3:一个文件中可以定义多个命名空间,定义的语法有两种,一种是简单组合语法,另一种是大括号形式语法,另外一个文件定义多个命名空间的使用一般是多个文件合并成一个文件的场景,但不到万不得已最好不要这样,因为这样增加了代码的复杂度,可读性会降低,一般情况也没有这种使用的必要。
简单组合语法:
const INSTANCE=2;
?>
大括号语法,一个文件多个命名空间,如果还需要写上非命名空间的代码,就只能用大括号语法,并且非命名空间代码用namespace声明一个没有名称的命名空间,再用大括号即可:
namespace Col{
const INSTANCE=2;
}
/全局非命名空间代码/
namespace {
const INSTANCE=3;
}
?>
4:多个不同的文件可以定义同一个命名空间,也就是说同一个命名空间的内容可以分别存储到多个不同的文件中,这里就不举例了。
三:命名空间的识别原理
命名空间的使用原理有三种情况,手册上其实说的详细但可能因为翻译问题导致一些凌乱,这里我简化一下用自己的例子梳理一下:
1:没有限定名称,也就是直接使用要读取的类、常量、函数、接口名称,这种情况会读取该内容所属的命名空间的类、常量、函数、接口名称,但如果命名空间内没有相关的数据,如果是类和接口名称会返回fatal error,如果是函数和常量会自动读取全局的函数和常量,如果全局中也没有,才会报fatal error。
下面举例:
echo 1;
}
class foo{
static function fool(){
echo 1;
}
}
var_dump(INSTANCE); //打印出来的是1
test(); //输出1
foo::fool(); //输出1
}
/命名空间Index/
namespace Index{
const INSTANCE=2;
function test(){
echo 2;
}
class foo{
static function fool(){
echo 2;
}
}
var_dump(INSTANCE); //打印出来的是2
test(); //输出2
foo::fool(); //输出2
}
/命名空间Col/
namespace Col{
const INSTANCE=3;
function test(){
echo 3;
}
class foo{
static function fool(){
echo 3;
}
}
var_dump(INSTANCE); //打印出来的是3
test(); //输出2
foo::fool(); //输出2
}
?>
上面的例子每个命名空间里输出的都没有限定名称,所以会得到当前命名空间下设置的对应数据值。
如果当前命名空间没有设置,函数和常量则会读取全局设置的对应数据值,全局没有对应的才会报fatal error,类和接口都会直接报fatal error,如下面代码所示。
static function fool(){
echo 1;
}
}
var_dump(INSTANCE); //打印出来的是1
test(); //输出1
foo::fool(); //输出1
}
/命名空间Index/
namespace Index{
var_dump(INSTANCE); //打印出来的是1
test(); //输出1
foo::fool(); //fatal error
}
?>
2:限定名称,分为两种情况,一种是包含前缀的限定名称情况,一种是包含全局限定名称的情况。手册上将这两种单独分开了,但我觉得这两种可以合并成一起说,他们都是有限定名称,只是前者没有全局限定,后者有全局限定。
①包含前缀的限定名称,这种前缀可以有多个或者一个层级,但最左侧不能为\全局限定词,这种情况会读取该代码所在命名空间加上该前缀限定名称所对应数据,也就是:
所处命名空间\前缀限定\名称来读取,如果该代码是全局没有命名空间的,则直接用前缀限定名称来读取,也就是:前缀限定\名称来读取。
实例代码:
namespace Index{
const INSTANCE=2;
}
/命名空间Col/
namespace Col{
const INSTANCE=3;
var_dump(Index\INSTANCE); //打印出来的是1 读取的是Col\Index\INSTANCE
}
/全局非命名空间代码/
namespace {
const INSTANCE=4;
var_dump(Index\INSTANCE); //打印出来的是2 读取的是Index\INSTANCE
}
?>
②全局限定前缀名称:也就是在最左侧有全局操作符\进行修饰的前缀限定名称,当然也可以没有前缀限定直接全局操作符\加上名称也是可以的。但加上全局操作符后就跟目录里的绝对路径一样,只会按照全局限定后的所设置的进行读取。
具体实例如下:
namespace Index{
const INSTANCE=2;
}
/命名空间Col/
namespace Col{
const INSTANCE=3;
var_dump(\Index\INSTANCE); //打印出来的是2 读取的是Index\INSTANCE
}
/全局非命名空间代码/
namespace {
const INSTANCE=4;
var_dump(\Index\INSTANCE); //打印出来的是2 读取的是Index\INSTANCE
}
namespace Lin{
const INSTANCE=5;
var_dump(\INSTANCE); //打印出来的是4 读取的是INSTANCE,是全局非命名空间里的INSTANCE,如果没有全局操作符\,读取的会是当前命名空间的Lin\INSTANCE=5
}
?>
四:命名空间在字符串中的转义
有时候命名空间会放在字符串中使用,如果是单引号不会通过编译器解释,所以没有任何问题,但是如果是双引号,那么就会有些意外情况了,要知道双引号里的内容是需要经过编译器进行解释然后再进行输出的,而\在编译器里的解释容易造成歧义。
例如"index\name"这里就有\n会被解释成换行,除此之外还有很多这种造成意外的情况。
因此一般我们推荐命名空间如果要放在字符串中使用,最好使用单引号,一是效率,二是安全,如果使用双引号,则必须增加一个\进行转义避免歧义,例如"index\\name"这样就没有问题了。
随手双引号的举个例子:
namespace{
$a= "Index\Name\foo"; //用\转义了\所以可以正常运行,但是如果去掉转义的话会报错Class 'Index\Nameoo',因为/f被解释成了换页符
$obj=new $a;
}
这部分碍于篇幅就暂时到这里了,下一篇主要总结命名空间里的namespace和__NAMESPACE__的使用,以及别名的使用等。