PHP 命名空間(namespace)是在PHP 5.3加入的,如果你學過C#和Java,那麼命名空間就不算什麼新事物。 不過在PHP當中還是有著相當重要的意義。
PHP 命名空間可以解決以下兩類問題:
使用者所寫的程式碼與PHP內部的類別/函數/常數或第三方類別/函數/常數之間的名字衝突。
為很長的識別碼名稱(通常是為了緩解第一類問題而定義的)建立一個別名(或簡短)的名稱,提高原始碼的可讀性。
預設情況下,所有常數、類別和函數名稱都放在全域空間下,就和PHP支援命名空間之前一樣。
命名空間透過關鍵字namespace 來聲明。如果一個檔案中包含命名空間,它必須在其它所有程式碼之前聲明命名空間。文法格式如下;
<?php // 定義程式碼在'MyProject' 命名空間中namespace MyProject; // ... 程式碼...
你也可以在同一個檔案中定義不同的命名空間程式碼,如:
<?php namespace MyProject;const CONNECT_OK = 1;class Connection { /* ... */ }function connect() { /* ... */ }namespace AnotherProject;const CONNECT_OK = 1;class Connection { /* .. . */ }function connect() { /* ... */ }?>
不建議使用這種語法在單一檔案中定義多個命名空間。建議使用下面的大括號形式的語法。
<?phpnamespace MyProject { const CONNECT_OK = 1; class Connection { /* ... */ } function connect() { /* ... */ }}namespace AnotherProject { const CONNECT_OK = 1; class Connection { /* .. . */ } function connect() { /* ... */ }}?>
將全域的非命名空間中的程式碼與命名空間中的程式碼組合在一起,只能使用大括號形式的語法。全域程式碼必須用一個不帶名稱的namespace 語句加上大括號括起來,例如:
<?phpnamespace MyProject {const CONNECT_OK = 1;class Connection { /* ... */ }function connect() { /* ... */ }}namespace { // 全域程式碼session_start();$a = MyProject connect();echo MyProjectConnection::start();}?>
在聲明命名空間之前唯一合法的程式碼是用來定義原始檔編碼方式的declare 語句。所有非PHP 程式碼包括空白符號都不能出現在命名空間的聲明之前。
<?phpdeclare(encoding='UTF-8'); //定義多個命名空間和不包含在命名空間中的程式碼namespace MyProject {const CONNECT_OK = 1;class Connection { /* ... */ }function connect () { /* ... */ }}namespace { // 全域程式碼session_start();$a = MyProjectconnect();echo MyProjectConnection::start();}?>
以下程式碼會出現語法錯誤:
<html><?phpnamespace MyProject; // 命名空間前出現了「<html>」 會致命錯誤- 命名空間必須是程式腳本的第一個語句?>
與目錄和檔案的關係很像,PHP 命名空間也允許指定層次化的命名空間的名稱。因此,命名空間的名字可以使用分層的方式來定義:
<?phpnamespace MyProjectSubLevel; //宣告分層的單一命名空間const CONNECT_OK = 1;class Connection { /* ... */ }function Connect() { /* ... */ }?>
上面的範例創建了常數MyProjectSubLevelCONNECT_OK,類別MyProjectSubLevelConnection 和函數MyProjectSubLevelConnect。
PHP 命名空間中的類別名稱可以透過三種方式引用:
非限定名稱,或不包含前綴的類別名稱,例如$a=new foo(); 或foo::staticmethod();。如果目前命名空間是currentnamespace,foo 將解析為currentnamespacefoo。如果使用foo 的程式碼是全域性的,不包含在任何命名空間中的程式碼,則foo 會被解析為foo。 警告:如果命名空間中的函數或常數未定義,則該非限定的函數名稱或常數名稱會被解析為全域函數名稱或常數名稱。
限定名稱,或包含前綴的名稱,例如$a = new subnamespacefoo(); 或subnamespacefoo::staticmethod();。如果目前的命名空間是currentnamespace,則foo 會被解析為currentnamespacesubnamespacefoo。如果使用foo 的程式碼是全域性的,不包含在任何命名空間中的程式碼,foo 會被解析為subnamespacefoo。
完全限定名稱,或包含了全域前綴運算符的名稱,例如, $a = new currentnamespacefoo(); 或currentnamespacefoo::staticmethod();。在這種情況下,foo 總是被解析為程式碼中的文字名稱(literal name)currentnamespacefoo。
以下是一個使用這三種方式的實例:
file1.php 檔案程式碼
<?phpnamespace FooBarsubnamespace; const FOO = 1;function foo() {}class foo{ static function staticmethod() {}}?>
file2.php 檔案程式碼
<?phpnamespace FooBar;include 'file1.php';const FOO = 2;function foo() {}class foo{ static function staticmethod() {}}/* 非限定名稱*/foo(); // 解析為函數FooBarfoofoo::staticmethod(); // 解析為類別FooBarfoo ,方法為staticmethodecho FOO; //解析為常數FooBarFOO/* 限定名稱*/subnamespacefoo(); // 解析為函數FooBarsubnamespacefoosubnamespacefoo::staticmethod(); // 解析為類別FooBarsubnamespace foo, // 以及類別的方法staticmethodecho subnamespaceFOO; // 解析為常數FooBarsubnamespaceFOO /*完全限定名稱*/FooBarfoo(); // 解析為函數FooBarfooFooBarfoo::staticmethod(); // 解析為類別FooBarfoo, 以及類別的方法staticmethodecho FooBarFOO; // 解析為常數FooBarFOO?>
注意存取任意全域類別、函數或常數,都可以使用完全限定名稱,例如strlen() 或Exception 或INI_ALL。
在命名空間內部存取全域類別、函數和常數:
<?phpnamespace Foo;function strlen() {}const INI_ALL = 3;class Exception {}$a = strlen('hi'); // 呼叫全域函數strlen$b = INI_ALL; // 存取全域常數INI_ALL$ c = new Exception('error'); // 實例化全域類別Exception?>
PHP 命名空間的實作受到其語言本身的動態特徵的影響。因此,如果要將下面的程式碼轉換到命名空間中,動態存取元素。
example1.php 檔案程式碼:
<?phpclass classname{ function __construct() { echo __METHOD__,"n"; }}function funcname(){ echo __FUNCTION__,"n";}const constname = "global";$a = 'classname';$objjj = new $a; // prints classname::__construct$b = 'funcname';$b(); // prints funcnameecho constant('constname'), "n"; // prints global?>
必須使用完全限定名稱(包括命名空間前綴的類別名稱)。注意因為在動態的類別名稱、函數名稱或常數名稱中,限定名稱和完全限定名稱沒有區別,因此其前導的反斜線是不必要的。
動態存取命名空間的元素
<?phpnamespace namespacename;class classname{ function __construct() { echo __METHOD__,"n"; }}function funcname(){ echo __FUNCTION__,"n";}const constname = "namespaced";include 'example1.php' ;$a = 'classname';$obj = new $a; // 輸出classname::__construct$b = 'funcname';$b(); // 輸出函式名稱echo constant('constname'), "n"; // 輸出global/* 若使用雙引號,使用方法為"\namespacename\classname" */$a = 'namespacenameclassname';$obj = new $a; // 輸出namespacenameclassname::__construct$a = 'namespacenameclassname';$obj = new $a; // 輸出namespacenameclassname::__construct$b = 'namespacenamefuncname';$b(); // 輸出namespacenamefuncname$b = 'namespacenamefuncname';$b(); // 輸出namespacenamefuncnameecho constant('namespacenameconstname'), "n"; // 輸出namespacedecho constant('namespacenameconstname'), "n"; // 輸出namespaced?>
PHP支援兩種抽象的存取目前命名空間內部元素的方法,__NAMESPACE__ 魔術常數和namespace關鍵字。
常數__NAMESPACE__的值是包含目前命名空間名稱的字串。在全域的,不包括在任何命名空間中的程式碼,它包含一個空的字串。
__NAMESPACE__ 範例, 在命名空間中的程式碼
<?phpnamespace MyProject;echo '"', __NAMESPACE__, '"'; // 輸出"MyProject"?>
__NAMESPACE__ 範例,全域程式碼
<?phpecho '"', __NAMESPACE__, '"'; // 輸出""?>
常數__NAMESPACE__ 在動態建立名稱時很有用,例如:
使用__NAMESPACE__動態建立名稱
<?phpnamespace MyProject;function get($classname){ $a = __NAMESPACE__ . '\' . $classname; return new $a;}?>
關鍵字namespace 可用來明確存取目前命名空間或子命名空間中的元素。它等價於類別中的self 運算子。
namespace運算符,命名空間中的程式碼
<?phpnamespace MyProject;use blahblah as mine; // see "Using namespaces: importing/aliasing"blahmine(); // calls function blahblahmine()namespaceblahmine(); // calls function MyProjectblahmine()namespacefunc(); // calls function MyProjectfunc()namespacesubfunc(); // calls function MyProjectsubfunc()namespacecname::method(); // calls static method "method" of class MyProjectcname$a = new namespacesubcname(); // instantiates object of class MyProjectsubcname$b = namespaceCONSTANT; // assigns value of constant MyProjectCONSTANT to $b?>
namespace運算子, 全域程式碼
<?phpnamespacefunc(); // calls function func()namespacesubfunc(); // calls function subfunc()namespacecname::method(); // calls static method "method" of class cname$a = new namespacesubcname(); // instantiates object of class subcname$b = namespaceCONSTANT; // assigns value of constant CONSTANT to $b?>
PHP 命名空間支援有兩種使用別名或匯入方式:為類別名稱使用別名,或為命名空間名稱使用別名。
在PHP中,別名是透過運算子use 來實現的. 以下是一個使用所有可能的三種導入方式的範例:
1、使用use操作符導入/使用別名
<?phpnamespace foo;use MyFullClassname as Another;// 下面的範例與use MyFullNSname as NSname 相同use MyFullNSname;// 匯入一個全域類別use ArrayObject;$obj = new namespaceAnother; // 實例化fooAnother 物件$obj = new Another; // 實例化MyFullClassname物件NSnamesubnsfunc(); // 呼叫函數MyFullNSnamesubnsfunc$a = new ArrayObject(array(1)); // 實例化ArrayObject 物件// 如果不使用"use ArrayObject" ,則實例化一個fooArrayObject 物件?>
2、 一行中包含多個use語句
<?phpuse MyFullClassname as Another, MyFullNSname;$obj = new Another; // 實例化MyFullClassname 物件NSnamesubnsfunc(); // 呼叫函數MyFullNSname subnsfunc?>
導入操作是在編譯執行的,但動態的類別名稱、函數名稱或常數名稱則不是。
3、導入和動態名稱
<?phpuse MyFullClassname as Another, MyFullNSname;$obj = new Another; // 實例化一個MyFullClassname 物件$a = 'Another';$obj = new $a; //實際化一個Another 物件?>
另外,導入操作只影響非限定名稱和限定名稱。完全限定名稱由於是確定的,故不受導入的影響。
4、導入和完全限定名稱
<?phpuse MyFullClassname as Another, MyFullNSname;$obj = new Another; // 實例化MyFullClassname 類別$obj = new Another; // 實例化Another 類別$obj = new Anotherthing; // 實例化MyFullClassnamething 類別$obj = new Anotherthing; // 實例化Anotherthing 類別?>
在一個命名空間中,當PHP 遇到一個非限定的類別、函數或常數名稱時,它會使用不同的優先策略來解析該名稱。類別名稱總是解析到目前命名空間中的名稱。因此在存取系統內部或不包含在命名空間中的類別名稱時,必須使用完全限定名稱,例如:
1.在命名空間中存取全域類
<?phpnamespace ABC;class Exception extends Exception {}$a = new Exception('hi'); // $a 是類別ABCException 的一個物件$b = new Exception( 'hi'); // $b 是類別Exception 的物件$c = new ArrayObject; // 致命錯誤, 找不到ABCArrayObject 類別?>
對於函數和常數來說,如果目前命名空間中不存在該函數或常數,PHP 會退而使用全域空間中的函數或常數。
2、 命名空間中後備的全域函數/常數
<?phpnamespace ABC;const E_ERROR = 45;function strlen($str){ return strlen($str) - 1;}echo E_ERROR, "n"; // 輸出"45"echo INI_ALL, " n"; // 輸出"7" - 使用全域常數INI_ALLecho strlen('hi'), "n"; //輸出"2"if (is_array('hi')) { // 輸出"is not array" echo "is arrayn";} else { echo "is not arrayn";}?>
如果沒有定義任何命名空間,所有的類別與函數的定義都是在全域空間,與PHP 引入命名空間概念前一樣。在名稱前加上前綴 表示該名稱是全域空間中的名稱,即使名稱位於其它的命名空間中也是如此。
使用全域空間說明
<?phpnamespace ABC;/* 這個函數是ABCfopen */function fopen() { /* ... */ $f = fopen(...); // 呼叫全域的fopen函數return $f;} ?>
自從有了命名空間之後,最容易出錯的該是使用類別的時候,這個類別的尋找路徑是什麼樣的了。
<?phpnamespace A;use BD, CE as F;// 函數呼叫foo(); // 首先嘗試呼叫定義在命名空間"A"中的函數foo() // 再嘗試呼叫全域函數" foo"foo(); // 呼叫全域空間函數"foo" myfoo(); // 呼叫定義在命名空間"Amy"中函數"foo" F(); //首先嘗試呼叫定義在命名空間"A"中的函數"F" // 再嘗試呼叫全域函數"F"// 類別引用new B(); // 建立命名空間"A" 中定義的類別"B"的一個物件// 如果未找到,則嘗試自動裝載類別"AB"new D(); // 使用匯入規則,建立命名空間"B" 中定義的類別"D" 的一個物件// 如果未找到,則嘗試自動裝載類別"BD"new F(); // 使用導入規則,建立命名空間"C" 中定義的類別"E" 的一個物件// 如果未找到,則嘗試自動載入類別"CE"new B(); //建立定義在全域空間中的類別"B" 的一個物件// 如果未發現,則嘗試自動裝載類別"B"new D(); // 建立定義在全域空間中的類別"D" 的一個對象// 如果未發現,則嘗試自動載入類別"D"new F(); // 建立定義在全域空間中的類別"F" 的一個物件// 如果未發現,則嘗試自動裝載類別"F"// 呼叫另一個命名空間中的靜態方法或命名空間函數Bfoo(); // 呼叫命名空間"AB" 中函數"foo"B::foo(); // 呼叫命名空間"A" 中定義的類別"B" 的"foo" 方法//如果未找到類別"AB" ,則嘗試自動載入類別"AB"D::foo(); // 使用導入規則,呼叫命名空間"B" 中定義的類別"D" 的"foo" 方法// 如果類別"BD " 未找到,則嘗試自動裝載類別"BD"Bfoo(); // 呼叫命名空間"B" 中的函數"foo" B::foo(); // 呼叫全域空間中的類"B" 的"foo"方法// 如果類別"B" 未找到,則嘗試自動載入類別"B"// 在目前命名空間中的靜態方法或函數AB::foo(); // 呼叫命名空間"AA" 中定義的類別"B" 的"foo" 方法// 如果類別"AAB" 未找到,則嘗試自動載入類別"AAB"AB::foo(); // 呼叫命名空間"A"中定義的類別"B" 的"foo" 方法// 如果類別"AB" 未找到,則嘗試自動載入類別"AB"?>
名稱解析遵循下列規則:
對完全限定名稱的函數,類別和常數的呼叫在編譯時解析。例如new AB解析為類別AB 。
所有的非限定名稱和限定名稱(非完全限定名稱)根據目前的導入規則在編譯時進行轉換。例如,如果命名空間ABC被導入為C ,那麼對CDe()的呼叫就會被轉換為ABCDe() 。
在命名空間內部,所有的沒有根據匯入規則轉換的限定名稱都會在其前面加上目前的命名空間名稱。例如,在命名空間AB內部呼叫CDe() ,則CDe()會轉換為ABCDe() 。
非限定類別名稱根據目前的導入規則在編譯時轉換(以全名取代短的導入名稱)。例如,如果命名空間ABC導入為C,則new C()轉換為new ABC() 。
在命名空間內部(例如AB),對非限定名稱的函數呼叫是在執行時間解析的。例如對函數foo()的呼叫是這樣解析的:
在目前命名空間中尋找名為ABfoo()的函數
嘗試尋找並呼叫全域(global)空間中的函數foo() 。
在命名空間(例如AB )內部對非限定名稱或限定名稱類別(非完全限定名稱)的呼叫是在執行時間解析的。下面是呼叫new C()及new DE()的解析過程: new C()的解析:
在目前命名空間中尋找ABC類別。
嘗試自動裝載類ABC 。
new DE()的解析:在類別名稱前面加上目前命名空間名稱變成: ABDE ,然後尋找該類別。
嘗試自動裝載類ABDE 。
為了引用全域命名空間中的全域類,必須使用完全限定名稱new C() 。