對于命名空間,官方文檔已經說得很詳細[檢視],我在這裡做了一下實踐和總結。
命名空間一個最明确的目的就是解決重名問題,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;
?>
要注意的是,目前腳本檔案的第一個命名空間前面不能有任何代碼,下面的寫法都是錯誤的:
//例一
//在腳本前面寫了一些邏輯代碼
$path = "/";
class comment { }
//例二
//在腳本前面輸出了一些字元
<html></html>
為什麼要說第一個命名空間呢?因為同一腳本檔案中可以建立多個命名空間。
下面我建立了兩個命名空間,順便為這兩個空間各自添加了一個comment類元素:
//此comment屬于article空間的元素
//建立一個名為'messageboard'的命名空間
namespace messageboard;
//此comment屬于messageboard空間的元素
在不同空間之間不可以直接調用其它元素,需要使用命名空間的文法:
//調用目前空間(messageboard)的comment類
$comment = new comment();
//調用article空間的comment類
$article_comment = new \article\comment();
可以看到,在messageboard空間中調用article空間裡的comment類時,使用了一種像檔案路徑的文法: \空間名\元素名
除了類之外,對函數和常量的用法是一樣的,下面我為兩個空間建立了新的元素,并在messageboard空間中輸出了它們的值。
const path = '/article';
function getcommenttotal() {
return 100;
}
const path = '/message_board';
return 300;
//調用目前空間的常量、函數和類
echo path; ///message_board
echo getcommenttotal(); //300
//調用article空間的常量、函數和類
echo \article\path; ///article
echo \article\getcommenttotal(); //100
然後我的确得到了article空間的元素資料。
子空間
命名空間的調用文法像檔案路徑一樣是有道理的,它允許我們自定義子空間來描述各個空間之間的關系。
抱歉我忘了說,article和message board這兩個子產品其實都是處于同一個blog項目内。如果用命名空間來表達它們的關系,是這樣:
//我用這樣的命名空間表示處于blog下的article子產品
namespace blog\article;
//我用這樣的命名空間表示處于blog下的message board子產品
namespace blog\messageboard;
//調用目前空間的類
//調用blog\article空間的類
$article_comment = new \blog\article\comment();
而且,子空間還可以定義很多層次,比如說 blog\article\archives\date
公共空間
我有一個common_inc.php腳本檔案,裡面有一些好用的函數和類:
function getip() { }
class filterxss { }
在一個命名空間裡引入這個腳本,腳本裡的元素不會歸屬到這個命名空間。如果這個腳本裡沒有定義其它命名空間,它的元素就始終處于公共空間中:
//引入腳本檔案
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),這樣可能會更容易了解。
我用了幾個示例來表示它們:
//建立空間blog
namespace blog;
//非限定名稱,表示目前blog空間
//這個調用将被解析成 blog\comment();
$blog_comment = new comment();
//限定名稱,表示相對于blog空間
//這個調用将被解析成 blog\article\comment();
$article_comment = new article\comment(); //類前面沒有反斜杆\
//完全限定名稱,表示絕對于blog空間
$article_comment = new \blog\comment(); //類前面有反斜杆\
$article_comment = new \blog\article\comment(); //類前面有反斜杆\
//建立blog的子空間article
其實之前我就一直在使用非限定名稱和完全限定名稱,現在它們終于可以叫出它們的名稱了。
别名和導入
别名和導入可以看作是調用命名空間元素的一種快捷方式。php并不支援導入函數或常量。
它們都是通過使用use操作符來實作:
//建立一個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();
我注意到,如果導入元素的時候,目前空間有相同的名字元素将會怎樣?顯然結果會發生緻命錯誤。
例:
class comt { }
$article_comment = new comment(); //與目前空間的comment發生沖突,程式産生緻命錯誤
$article_comment = new comt(); //與目前空間的comt發生沖突,程式産生緻命錯誤
動态調用
php提供了namespace關鍵字和__namespace__魔法常量動态的通路元素,__namespace__可以通過組合字元串的形式來動态通路:
const path = '/blog/article';
//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. 使用雙引号的時候特殊字元可能被轉義
class name { }
//我是想調用blog\article\name
$class_name = __namespace__ . "\name"; //但是\n将被轉義為換行符
$name = new $class_name(); //發生緻命錯誤
2. 不會認為是限定名稱
php在編譯腳本的時候就确定了元素所在的空間,以及導入的情況。而在解析腳本時字元串形式調用隻能認為是非限定名稱和完全限定名稱,而永遠不可能是限定名稱。
//導入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類不存在
class common { }
總結
我對php的命名空間剛剛接觸,也不能随便給一些沒有實踐的建議。我個人認為命名空間的作用和功能都很強大,如果要寫插件或者通用庫的時候再也不用擔心重名問題。不過如果項目進行到一定程度,要通過增加命名空間去解決重名問題,我覺得工作量不會比重構名字少。也不得不承認它的文法會對項目增加一定的複雜度,是以從項目一開始的時候就應該很好的規劃它,并制定一個命名規範。