本教程適用于PHP 5 >= 5.3.0、PHP 7和PHP 8。
一、命名空間概述
什么是命名空間?在PHP中,命名空間是一種用于解決命名沖突和創(chuàng)建代碼別名的方法。可以將其理解為將相關(guān)的類、函數(shù)和常量封裝在一個(gè)命名空間中,以便在不同的上下文中進(jìn)行區(qū)分和使用。
命名空間類似于操作系統(tǒng)中的目錄結(jié)構(gòu),可以防止不同目錄下的文件名沖突。例如,文件foo.txt可以同時(shí)存在于/home/greg和/home/other目錄中,但在同一個(gè)目錄中不能存在兩個(gè)相同的foo.txt文件。而當(dāng)從/home/greg目錄外訪問(wèn)foo.txt文件時(shí),需要在文件名之前加上目錄名和目錄分隔符,例如/home/greg/foo.txt。這個(gè)概念應(yīng)用到程序設(shè)計(jì)中就是命名空間的概念。
在PHP中,命名空間解決了兩類問(wèn)題:
1、避免用戶自定義的代碼與PHP內(nèi)部類、函數(shù)、常量或第三方類、函數(shù)、常量之間的命名沖突。
2、為長(zhǎng)命名標(biāo)識(shí)符創(chuàng)建簡(jiǎn)短的別名,提高源代碼的可讀性。
PHP 命名空間提供了一種將相關(guān)的類、函數(shù)和常量組合到一起的途徑。下面是一個(gè)說(shuō)明 PHP 命名空間語(yǔ)法的示例:
<?php namespace?my\name;?//?參考?"定義命名空間"?小節(jié) class?MyClass?{} function?myfunction()?{} const?MYCONST?=?1; $a?=?new?MyClass; $c = new \my\name\MyClass;? $a = strlen('hi'); $d = namespace\MYCONST;? $d?=?__NAMESPACE__?.?'\MYCONST'; echo constant($d);? ?>
注意:
- 命名空間名稱大小寫(xiě)不敏感;
- 名為 PHP 的命名空間,以及以這些名字開(kāi)頭的命名空間 (例如 PHP\Classes)被保留用作語(yǔ)言內(nèi)核使用, 而不應(yīng)該在用戶空間的代碼中使用。
二、定義命名空間
雖然任意合法的 PHP 代碼都可以包含在命名空間中,但只有以下類型的代碼受命名空間的影響,它們是:類(包括抽象類和 trait)、接口、函數(shù)和常量。
命名空間通過(guò)關(guān)鍵字 namespace 來(lái)聲明。如果一個(gè)文件中包含命名空間,它必須在其它所有代碼之前聲明命名空間,除了一個(gè)以外:declare關(guān)鍵字。
聲明單個(gè)命名空間示例:
<?php namespace?MyProject; const?CONNECT_OK?=?1; class?Connection?{?/*?...?*/?} function?connect()?{?/*?...?*/??} ?>
注意:完全限定名稱(就是以反斜杠開(kāi)頭的名稱)不能用于命名空間的聲明。 因?yàn)樵摻Y(jié)構(gòu)會(huì)解析成相對(duì)命名空間表達(dá)式。
在聲明命名空間之前唯一合法的代碼是用于定義源文件編碼方式的 declare 語(yǔ)句。另外,所有非 PHP 代碼包括空白符都不能出現(xiàn)在命名空間的聲明之前:
<html> <?php namespace?MyProject;?//?致命錯(cuò)誤?- 命名空間必須是程序腳本的第一條語(yǔ)句 ?>
另外,與 PHP 其它的語(yǔ)言特征不同,同一個(gè)命名空間可以定義在多個(gè)文件中,即允許將同一個(gè)命名空間的內(nèi)容分割存放在不同的文件中。
三、定義子命名空間
與目錄和文件的關(guān)系很象,PHP 命名空間也允許指定層次化的命名空間的名稱。因此,命名空間的名字可以使用分層次的方式定義:
<?php namespace?MyProject\Sub\Level; const?CONNECT_OK?=?1; class?Connection?{?/*?...?*/?} function?connect()?{?/*?...?*/??} ?>
上面的例子創(chuàng)建了常量 MyProject\Sub\Level\CONNECT_OK,類 MyProject\Sub\Level\Connection 和函數(shù) MyProject\Sub\Level\connect。
四、同一文件定義多個(gè)命名空間
也可以在同一個(gè)文件中定義多個(gè)命名空間。在同一個(gè)文件中定義多個(gè)命名空間有兩種語(yǔ)法形式。
1、簡(jiǎn)單組合語(yǔ)法(不推薦)
<?php namespace?MyProject; const?CONNECT_OK?=?1; class?Connection?{?/*?...?*/?} function?connect()?{?/*?...?*/??} namespace?AnotherProject; const?CONNECT_OK?=?1; class?Connection?{?/*?...?*/?} function?connect()?{?/*?...?*/??} ?>
2、大括號(hào)語(yǔ)法(推薦)
<?php namespace?MyProject?{ const?CONNECT_OK?=?1; class?Connection?{?/*?...?*/?} function?connect()?{?/*?...?*/??} } namespace?AnotherProject?{ const?CONNECT_OK?=?1; class?Connection?{?/*?...?*/?} function?connect()?{?/*?...?*/??} } ?>
在實(shí)際的編程實(shí)踐中,非常不提倡在同一個(gè)文件中定義多個(gè)命名空間。這種方式的主要用于將多個(gè) PHP 腳本合并在同一個(gè)文件中。
將全局的非命名空間中的代碼與命名空間中的代碼組合在一起,只能使用大括號(hào)形式的語(yǔ)法。全局代碼必須用一個(gè)不帶名稱的 namespace 語(yǔ)句加上大括號(hào)括起來(lái),例如:
定義多個(gè)命名空間和不包含在命名空間中的代碼:
<?php namespace?MyProject?{ const?CONNECT_OK?=?1; class?Connection?{?/*?...?*/?} function?connect()?{?/*?...?*/??} } namespace?{?//?全局代碼 session_start(); $a?=?MyProject\connect(); echo?MyProject\Connection::start(); } ?>
除了開(kāi)始的 declare 語(yǔ)句外,命名空間的括號(hào)外不得有任何 PHP 代碼。
定義多個(gè)命名空間和不包含在命名空間中的代碼:
<?php declare(encoding='UTF-8'); namespace?MyProject?{ const?CONNECT_OK?=?1; class?Connection?{?/*?...?*/?} function?connect()?{?/*?...?*/??} } namespace?{?//?全局代碼 session_start(); $a?=?MyProject\connect(); echo?MyProject\Connection::start(); } ?>
五、使用命名空間
在討論如何使用命名空間之前,必須了解 PHP 是如何知道要使用哪一個(gè)命名空間中的元素的??梢詫?PHP 命名空間與文件系統(tǒng)作一個(gè)簡(jiǎn)單的類比。在文件系統(tǒng)中訪問(wèn)一個(gè)文件有三種方式:
1、相對(duì)文件名形式如 foo.txt。它會(huì)被解析為 currentdirectory/foo.txt,其中 currentdirectory 表示當(dāng)前目錄。因此如果當(dāng)前目錄是 /home/foo,則該文件名被解析為 /home/foo/foo.txt。
2、相對(duì)路徑名形式如 subdirectory/foo.txt。它會(huì)被解析為 currentdirectory/subdirectory/foo.txt。
3、絕對(duì)路徑名形式如 /main/foo.txt。它會(huì)被解析為 /main/foo.txt。
PHP 命名空間中的元素使用同樣的原理。例如,類名可以通過(guò)三種方式引用:
1、非限定名稱,或不包含前綴的類名稱,例如 $a=new foo(); 或 foo::staticmethod();。如果當(dāng)前命名空間是 currentnamespace,foo 將被解析為 currentnamespace\foo。如果使用 foo 的代碼是全局的,不包含在任何命名空間中的代碼,則 foo 會(huì)被解析為 foo。 警告:如果命名空間中的函數(shù)或常量未定義,則該非限定的函數(shù)名稱或常量名稱會(huì)被解析為全局函數(shù)名稱或常量名稱。詳情參見(jiàn) 使用命名空間:后備全局函數(shù)名稱/常量名稱。
2、限定名稱,或包含前綴的名稱,例如 $a = new subnamespace\foo(); 或 subnamespace\foo::staticmethod();。如果當(dāng)前的命名空間是 currentnamespace,則 foo 會(huì)被解析為 currentnamespace\subnamespace\foo。如果使用 foo 的代碼是全局的,不包含在任何命名空間中的代碼,foo 會(huì)被解析為 subnamespace\foo。
3、完全限定名稱,或包含了全局前綴操作符的名稱,例如, $a = new \currentnamespace\foo(); 或 \currentnamespace\foo::staticmethod();。在這種情況下,foo 總是被解析為代碼中的文字名(literal name)currentnamespace\foo。
下面是一個(gè)使用這三種方式的實(shí)例:
實(shí)例一:
<?php namespace?Foo\Bar\subnamespace; const?FOO?=?1; function?foo()?{} class?foo { ????static?function?staticmethod()?{} } ?>
實(shí)例二:
<?php namespace?Foo\Bar; include?'file1.php'; const?FOO?=?2; function?foo()?{} class?foo { ????static?function?staticmethod()?{} } /*?非限定名稱?*/ foo();?//?解析為函數(shù)?Foo\Bar\foo? foo::staticmethod();?//?解析為類?Foo\Bar\foo?的靜態(tài)方法?staticmethod echo?FOO;?//?解析為常量?Foo\Bar\FOO /*?限定名稱?*/ subnamespace\foo();?//?解析為函數(shù)?Foo\Bar\subnamespace\foo subnamespace\foo::staticmethod();?//?解析為類?Foo\Bar\subnamespace\foo, ??????????????????????????????????//?以及類的方法?staticmethod echo?subnamespace\FOO;?//?解析為常量?Foo\Bar\subnamespace\FOO ?????????????????????????????????? /*?完全限定名稱?*/ \Foo\Bar\foo();?//?解析為函數(shù)?Foo\Bar\foo \Foo\Bar\foo::staticmethod();?//?解析為類?Foo\Bar\foo,?以及類的方法?staticmethod echo?\Foo\Bar\FOO;?//?解析為常量?Foo\Bar\FOO ?>
注意訪問(wèn)任意全局類、函數(shù)或常量,都可以使用完全限定名稱,例如 \strlen() 或 \Exception 或 \INI_ALL。
實(shí)例三、在命名空間內(nèi)部訪問(wèn)全局類、函數(shù)和常量:
<?php namespace?Foo; function?strlen()?{} const?INI_ALL?=?3; class?Exception?{} $a?=?\strlen('hi');?//?調(diào)用全局函數(shù)strlen $b?=?\INI_ALL;?//?訪問(wèn)全局常量?INI_ALL $c?=?new?\Exception('error');?//?實(shí)例化全局類?Exception ?>
六、命名空間和動(dòng)態(tài)語(yǔ)言特征
PHP 命名空間的實(shí)現(xiàn)受到其語(yǔ)言自身的動(dòng)態(tài)特征的影響。因此,如果要將下面的代碼轉(zhuǎn)換到命名空間中:
1、動(dòng)態(tài)訪問(wèn)元素
<?php class?classname { ????function?__construct() ????{ ????????echo?__METHOD__,"\n"; ????} } function?funcname() { ????echo?__FUNCTION__,"\n"; } const?constname?=?"global"; $a?=?'classname'; $obj?=?new?$a;?//?輸出?classname::__construct $b?=?'funcname'; $b();?//?輸出?funcname echo?constant('constname'),?"\n";?//?輸出?global ?>
必須使用完全限定名稱(包括命名空間前綴的類名稱)。注意因?yàn)樵趧?dòng)態(tài)的類名稱、函數(shù)名稱或常量名稱中,限定名稱和完全限定名稱沒(méi)有區(qū)別,因此其前導(dǎo)的反斜杠是不必要的。
2、動(dòng)態(tài)訪問(wèn)命名空間的元素
<?php namespace?namespacename; class?classname { ????function?__construct() ????{ ????????echo?__METHOD__,"\n"; ????} } function?funcname() { ????echo?__FUNCTION__,"\n"; } const?constname?=?"namespaced"; /*?注意,如果使用雙引號(hào),要這樣寫(xiě)?"\\namespacename\\classname"?*/ $a?=?'\namespacename\classname'; $obj?=?new?$a;?//?輸出?namespacename\classname::__construct $a?=?'namespacename\classname'; $obj?=?new?$a;?//?也會(huì)輸出?namespacename\classname::__construct $b?=?'namespacename\funcname'; $b();?//?輸出?namespacename\funcname $b?=?'\namespacename\funcname'; $b();?//?也會(huì)輸出?namespacename\funcname echo?constant('\namespacename\constname'),?"\n";?//?輸出?namespaced echo?constant('namespacename\constname'),?"\n";?//?也會(huì)輸出?namespaced ?>
七、namespace和__NAMESPACE__
PHP支持兩種抽象的訪問(wèn)當(dāng)前命名空間內(nèi)部元素的方法,__NAMESPACE__ 魔術(shù)常量和 namespace 關(guān)鍵字。
常量 __NAMESPACE__ 的值是包含當(dāng)前命名空間名稱的字符串。在全局的,不包括在任何命名空間中的代碼,它包含一個(gè)空的字符串。
__NAMESPACE__ 示例, 在命名空間中的代碼:
<?php namespace?MyProject; echo?'"',?__NAMESPACE__,?'"';?//?輸出?"MyProject" ?>
__NAMESPACE__ 示例,全局代碼:
<?php echo?'"',?__NAMESPACE__,?'"';?//?輸出?"" ?>
常量 __NAMESPACE__ 在動(dòng)態(tài)創(chuàng)建名稱時(shí)很有用,例如:
使用 __NAMESPACE__ 動(dòng)態(tài)創(chuàng)建名稱:
<?php namespace?MyProject; function?get($classname) { ????$a?=?__NAMESPACE__?.?'\\'?.?$classname; ????return?new?$a; } ?>
關(guān)鍵字 namespace 可用來(lái)顯式訪問(wèn)當(dāng)前命名空間或子命名空間中的元素。它等價(jià)于類中的 self 操作符。
namespace 操作符,命名空間中的代碼:
<?php namespace?MyProject; use?blah\blah?as?mine;?//?參考?"使用命名空間:別名/導(dǎo)入" blah\mine();?//?調(diào)用函數(shù)?MyProject\blah\mine() namespace\blah\mine();?//?調(diào)用函數(shù)?MyProject\blah\mine() namespace\func();?//?調(diào)用函數(shù)?MyProject\func() namespace\sub\func();?//?調(diào)用函數(shù)?MyProject\sub\func() namespace\cname::method();?//?調(diào)用?class?MyProject\cname?的靜態(tài)方法?"method" $a?=?new?namespace\sub\cname();?//?class?MyProject\sub\cname?的實(shí)例對(duì)象 $b?=?namespace\CONSTANT;?//?設(shè)置?$b?的值為常量?MyProject\CONSTANT ?>
namespace 操作符, 全局代碼:
<?php namespace\func();?//?調(diào)用函數(shù)?func() namespace\sub\func();?//?調(diào)用函數(shù)?sub\func() namespace\cname::method();?//?調(diào)用?class?cname?的靜態(tài)方法?"method" $a?=?new?namespace\sub\cname();?//?class?sub\cname?的實(shí)例對(duì)象 $b?=?namespace\CONSTANT;?//?設(shè)置?$b?的值為常量?CONSTANT ?>
八、使用命名空間:別名/導(dǎo)入
參見(jiàn)《PHP別名/導(dǎo)入》
九、全局空間
如果沒(méi)有定義任何命名空間,所有的類與函數(shù)的定義都是在全局空間,與 PHP 引入命名空間概念前一樣。在名稱前加上前綴 \ 表示該名稱是全局空間中的名稱,即使該名稱位于其它的命名空間中時(shí)也是如此。
<?php namespace?A\B\C; /*?這個(gè)函數(shù)是?A\B\C\fopen?*/ function?fopen()?{? ?????/*?...?*/ ?????$f?=?\fopen(...);?//?調(diào)用全局的fopen函數(shù) ?????return?$f; }? ?>
十、后備全局函數(shù)/常量
在一個(gè)命名空間中,當(dāng) PHP 遇到一個(gè)非限定的類、函數(shù)或常量名稱時(shí),它使用不同的優(yōu)先策略來(lái)解析該名稱。類名稱總是解析到當(dāng)前命名空間中的名稱。因此在訪問(wèn)系統(tǒng)內(nèi)部或不包含在命名空間中的類名稱時(shí),必須使用完全限定名稱,例如:
1、在命名空間中訪問(wèn)全局類
<?php namespace?A\B\C; class?Exception?extends?\Exception?{} $a?=?new?Exception('hi');?//?$a?是類?A\B\C\Exception?的一個(gè)對(duì)象 $b?=?new?\Exception('hi');?//?$b?是類?Exception?的一個(gè)對(duì)象 $c?=?new?ArrayObject;?//?致命錯(cuò)誤,?找不到?A\B\C\ArrayObject?類 ?>
對(duì)于函數(shù)和常量來(lái)說(shuō),如果當(dāng)前命名空間中不存在該函數(shù)或常量,PHP 會(huì)退而使用全局空間中的函數(shù)或常量。
2、命名空間中后備的全局函數(shù)/常量
<?php namespace?A\B\C; const?E_ERROR?=?45; function?strlen($str) { ????return?\strlen($str)?-?1; } echo?E_ERROR,?"\n";?//?輸出?"45" echo?INI_ALL,?"\n";?//?輸出?"7"?-?使用全局常量?INI_ALL echo?strlen('hi'),?"\n";?//?輸出?"1" if?(is_array('hi'))?{?//?輸出?"is?not?array" ????echo?"is?array\n"; }?else?{ ????echo?"is?not?array\n"; } ?>
十一、名稱解析規(guī)則
參見(jiàn)《PHP名稱解析規(guī)則》
十二、常見(jiàn)問(wèn)題
參見(jiàn)《PHP命名空間常見(jiàn)問(wèn)題》