PHP7語言基礎——命名空間

命名空間概述

從廣義上來說,命名空間是一種封裝事物的方法。例如,在操作系統中目錄用來將相關文件分組,對於目錄中的文件來說,目錄就扮演了命名空間的角色。具體舉個例子,文件 foo.txt 可以同時在目錄/home/greg 和 /home/other 中存在,但在同一個目錄中不能存在兩個 foo.txt 文件。另外,在目錄 /home/greg 外訪問 foo.txt 文件時,我們必須將目錄名以及目錄分隔符放在文件名之前得到 /home/greg/foo.txt。這個原理應用到程序設計領域就是命名空間的概念。

在PHP中,命名空間用來解決在編寫類庫或應用程序時創建可重用的代碼如類或函數時碰到的兩類問題:

  • 用戶編寫的代碼與PHP內部的類/函數/常量或第三方類/函數/常量之間的名字衝突。
  • 爲很長的標識符名稱(通常是爲了緩解第一類問題而定義的)創建一個別名(或簡短)的名稱,提高源代碼的可讀性。

PHP 命名空間提供了一種將相關的類、函數和常量組合到一起的途徑。

名爲PHP或php的命名空間,以及以這些名字開頭的命名空間(例如PHP\Classes)被保留用作語言內核使用,而不應該在用戶空間的代碼中使用。

定義命名空間

雖然任意合法的PHP代碼都可以包含在命名空間中,但只有以下類型的代碼受命名空間的影響,它們是:類(包括抽象類和traits)、接口、函數和常量。

命名空間通過關鍵字namespace來聲明。如果一個文件中包含命名空間,它必須在其它所有代碼之前聲明命名空間,除了一個以外:declare關鍵字。

<?php
namespace MyProject;		// 定義命名空間

const CONNECT_OK = 1;
class Connection {/* ... */}
function connect() {/* ... */}
?>

在聲明命名空間之前唯一合法的代碼是用於定義源文件編碼方式的 declare 語句。另外,所有非 PHP 代碼包括空白符都不能出現在命名空間的聲明之前:

<html>
<?php
namespace MyProject;	// 致命錯誤 —— 命名空間必須是程序的第一條語句
?>

另外,與PHP其它的語言特徵不同,同一個命名空間可以定義在多個文件中,即允許將同一個命名空間的內容分割存放在不同的文件中。

定義子命名空間

與目錄和文件的關係很象,PHP 命名空間也允許指定層次化的命名空間的名稱。因此,命名空間的名字可以使用分層次的方式定義:

<?php
namespace MyProject\Sub\Level;

const CONNECT_OK = 1;
class Connection {/* ... */}
function connect() {/* ... */}
?>

上面的例子創建了常量MyProject\Sub\Level\CONNECT_OK,類 MyProject\Sub\Level\Connection和函數 MyProject\Sub\Level\connect。

在同一個文件中定義多個命名空間

也可以在同一個文件中定義多個命名空間。在同一個文件中定義多個命名空間有兩種語法形式。

  1. 簡單組合語法

    <?php
    namespace MyProject;
    
    const CONNECT_OK = 1;
    class Connection {/* ... */}
    function connect() {/* ... */}
    
    namespace AnotherProject;
    
    const CONNECT_OK = 1;
    class Connection {/* ... */}
    function connect() {/* ... */}
    ?>
    

    雖然這樣做是被允許的,但是不建議使用這種語法在單個文件中定義多個命名空間。

  2. 大括號語法

    <?php
    namespace MyProject {
    
    const CONNECT_OK = 1;
    class Connection {/* ... */}
    function connect() {/* ... */}
    }
    
    namespace AnotherProject {
    
    const CONNECT_OK = 1;
    class Connection {/* ... */}
    function connect() {/* ... */}
    }
    ?>
    

在實際的編程實踐中,非常不提倡在同一個文件中定義多個命名空間。這種方式的主要用於將多個 PHP 腳本合併在同一個文件中。

將全局的非命名空間中的代碼與命名空間中的代碼組合在一起,只能使用大括號形式的語法。全局代碼必須用一個不帶名稱的 namespace 語句加上大括號括起來.

除了開始的declare語句外,命名空間的括號外不得有任何PHP代碼。

使用命名空間

可以將 PHP 命名空間與文件系統作一個簡單的類比。在文件系統中訪問一個文件有三種方式:

  1. 相對文件名形式如foo.txt。它會被解析爲 currentdirectory/foo.txt,其中 currentdirectory 表示當前目錄。因此如果當前目錄是 /home/foo,則該文件名被解析爲/home/foo/foo.txt。
  2. 相對路徑名形式如subdirectory/foo.txt。它會被解析爲 currentdirectory/subdirectory/foo.txt。
  3. 絕對路徑名形式如/main/foo.txt。它會被解析爲/main/foo.txt。

PHP 命名空間中的元素使用同樣的原理。例如,類名可以通過三種方式引用:

  1. 非限定名稱,或不包含前綴的類名稱,例如 $a=new foo(); 或 foo::staticmethod();。如果當前命名空間是 currentnamespace,foo 將被解析爲 currentnamespace\foo。如果使用 foo 的代碼是全局的,不包含在任何命名空間中的代碼,則 foo 會被解析爲foo。警告:如果命名空間中的函數或常量未定義,則該非限定的函數名稱或常量名稱會被解析爲全局函數名稱或常量名稱。
  2. 限定名稱,或包含前綴的名稱,例如 $a = new subnamespace\foo(); 或 subnamespace\foo::staticmethod();。如果當前的命名空間是 currentnamespace,則 foo 會被解析爲 currentnamespace\subnamespace\foo。如果使用 foo 的代碼是全局的,不包含在任何命名空間中的代碼,foo 會被解析爲subnamespace\foo。
  3. 完全限定名稱,或包含了全局前綴操作符的名稱,例如, $a = new \currentnamespace\foo(); 或 \currentnamespace\foo::staticmethod();。在這種情況下,foo 總是被解析爲代碼中的文字名(literal name)currentnamespace\foo。

file1.php

<?php
namespace Foo\Bar\subnamespace;

const F00 = 1;
function foo() {}
class foo {
	static function staticmethod(){}
}
?>

file2.php

<?php
namespace Foo\Bar;
include 'file1.php';

const F00 = 2;
function foo() {}
class foo {
	static function staticmethod(){}
}

/* 非限定名稱 */
foo();	//解析爲Foo\Bar\foo
foo::staticmethod();	// 解析爲類 Foo\Bar\foo的靜態方法staticmethod。
echo F00;	//解析爲常量Foo\Bar\F00

/* 限定名稱 */
subnamespace\foo();	//解析爲函數Foo\Bar\subnamespace\foo
subnamespace\foo::staticmethod(); // 解析爲類Foo\Bar\subnamespace\foo的靜態方法staticmethod
echo subnamespace\F00;	// 解析爲常量Foo\Bar\subnamespace\F00

/* 完全限定名稱 */
\Foo\Bar\foo(); // 解析爲函數 Foo\Bar\foo
\Foo\Bar\foo::staticmethod(); // 解析爲類 Foo\Bar\foo, 以及類的方法 staticmethod
echo \Foo\Bar\F00; // 解析爲常量 Foo\Bar\F00
?>

**訪問任意全局類、函數或常量,都可以使用完全限定名稱,例如 \strlen() 或 \Exception 或 \INI_ALL。 **

如果要在命名空間內部訪問全局類、函數和常量可以這樣做:

<?php
namespace Foo;

function strlen(){}
const INI_ALL = 3;
class Exception{}

$a = \strlen('hi');	// 調用全局函數strlen
$b = \INI_ALL;	// 訪問全局常量INI_ALL
$c = new \Exception('error');	// 實例化全局類Exception
?>

所有支持命名空間的PHP版本支持三種別名或導入方式:爲類名稱使用別名、爲接口使用別名或爲命名空間名稱使用別名。PHP 5.6開始允許導入函數或常量或者爲它們設置別名。

在PHP中,別名是通過操作符 use 來實現的. 下面是一個使用所有可能的五種導入方式的例子:

<?php
namespace foo;
use My\Full\Classname as Another;

// 下面的例子與 use My\Full\NSname as NSname 相同
use My\Full\NSname;

// 導入一個全局類
use ArrayObject;

// importing a function (PHP 5.6+)
use function My\Full\functionName;

// aliasing a function (PHP 5.6+)
use function My\Full\functionName as func;

// importing a constant (PHP 5.6+)
use const My\Full\CONSTANT;

$obj = new namespace\Another; // 實例化 foo\Another 對象
$obj = new Another; // 實例化 My\Full\Classname 對象
NSname\subns\func(); // 調用函數 My\Full\NSname\subns\func
$a = new ArrayObject(array(1)); // 實例化 ArrayObject 對象
// 如果不使用 "use \ArrayObject" ,則實例化一個 foo\ArrayObject 對象
func(); // calls function My\Full\functionName
echo CONSTANT; // echoes the value of My\Full\CONSTANT
?> 

注意對命名空間中的名稱(包含命名空間分隔符的完全限定名稱如 Foo\Bar以及相對的不包含命名空間分隔符的全局名稱如 FooBar)來說,前導的反斜槓是不必要的也不推薦的,因爲導入的名稱必須是完全限定的,不會根據當前的命名空間作相對解析。

爲了簡化操作,PHP還支持在一行中使用多個use語句 :

<?php
use My\Full\Classname as Another, My\Full\NSname;

$obj = new Another; // 實例化 My\Full\Classname 對象
NSname\subns\func(); // 調用函數 My\Full\NSname\subns\func
?> 

導入操作隻影響非限定名稱和限定名稱。完全限定名稱由於是確定的,故不受導入的影響。

namespace關鍵字和__NAMESPACE__常量

PHP支持兩種抽象的訪問當前命名空間內部元素的方法,__NAMESPACE__ 魔術常量和namespace關鍵字。

常量__NAMESPACE__的值是包含當前命名空間名稱的字符串。在全局的,不包括在任何命名空間中的代碼,它包含一個空的字符串。

常量__NAMESPACE__在動態創建名稱時很有用,例如:

<?php
namespace MyProject;

function get($classname) {
	$a = __NAMESPACE__.'\\'.$classname;
	return new $a;
}
?>

關鍵字 namespace 可用來顯式訪問當前命名空間或子命名空間中的元素。它等價於類中的 self 操作符。

全局空間

如果沒有定義任何命名空間,所有的類與函數的定義都是在全局空間,與 PHP 引入命名空間概念前一樣。在名稱前加上前綴 \ 表示該名稱是全局空間中的名稱,即使該名稱位於其它的命名空間中時也是如此。

後備全局函數/常量

在一個命名空間中,當 PHP 遇到一個非限定的類、函數或常量名稱時,它使用不同的優先策略來解析該名稱。類名稱總是解析到當前命名空間中的名稱。因此在訪問系統內部或不包含在命名空間中的類名稱時,必須使用完全限定名稱.

對於函數和常量來說,如果當前命名空間中不存在該函數或常量,PHP 會退而使用全局空間中的函數或常量。

名稱解析規則

命名空間名稱定義

非限定名稱
  名稱中不包含命名空間分隔符的標識符,例如 Foo
限定名稱
  名稱中含有命名空間分隔符的標識符,例如 Foo\Bar
完全限定名稱
  名稱中包含命名空間分隔符,並以命名空間分隔符開始的標識符,例如 \Foo\Bar。 namespace\Foo 也是一個完全限定名稱。
名稱解析遵循下列規則:

  1. 對完全限定名稱的函數,類和常量的調用在編譯時解析。例如 new \A\B 解析爲類 A\B。
  2. 所有的非限定名稱和限定名稱(非完全限定名稱)根據當前的導入規則在編譯時進行轉換。例如,如果命名空間 A\B\C 被導入爲 C,那麼對 C\D\e() 的調用就會被轉換爲 A\B\C\D\e()。
  3. 在命名空間內部,所有的沒有根據導入規則轉換的限定名稱均會在其前面加上當前的命名空間名稱。例如,在命名空間 A\B 內部調用 C\D\e(),則 C\D\e() 會被轉換爲 A\B\C\D\e() 。
  4. 非限定類名根據當前的導入規則在編譯時轉換(用全名代替短的導入名稱)。例如,如果命名空間 A\B\C 導入爲C,則 new C() 被轉換爲 new A\B\C() 。
  5. 在命名空間內部(例如A\B),對非限定名稱的函數調用是在運行時解析的。例如對函數 foo() 的調用是這樣解析的:
    1. 在當前命名空間中查找名爲 A\B\foo() 的函數
    2. 嘗試查找並調用 全局(global) 空間中的函數 foo()。
  6. 在命名空間(例如A\B)內部對非限定名稱或限定名稱類(非完全限定名稱)的調用是在運行時解析的。下面是調用 new C() 及 new D\E() 的解析過程: new C()的解析:
    1. 在當前命名空間中查找A\B\C類。
    2. 嘗試自動裝載類A\B\C。=

new D\E()的解析:

1. 在類名稱前面加上當前命名空間名稱變成:A\B\D\E,然後查找該類。
  2. 嘗試自動裝載類 A\B\D\E。

<?php
namespace A;
use B\D, C\E as F;

// 函數調用

foo();      // 首先嚐試調用定義在命名空間"A"中的函數foo()
            // 再嘗試調用全局函數 "foo"

\foo();     // 調用全局空間函數 "foo" 

my\foo();   // 調用定義在命名空間"A\my"中函數 "foo" 

F();        // 首先嚐試調用定義在命名空間"A"中的函數 "F" 
            // 再嘗試調用全局函數 "F"

// 類引用

new B();    // 創建命名空間 "A" 中定義的類 "B" 的一個對象
            // 如果未找到,則嘗試自動裝載類 "A\B"

new D();    // 使用導入規則,創建命名空間 "B" 中定義的類 "D" 的一個對象
            // 如果未找到,則嘗試自動裝載類 "B\D"

new F();    // 使用導入規則,創建命名空間 "C" 中定義的類 "E" 的一個對象
            // 如果未找到,則嘗試自動裝載類 "C\E"

new \B();   // 創建定義在全局空間中的類 "B" 的一個對象
            // 如果未發現,則嘗試自動裝載類 "B"

new \D();   // 創建定義在全局空間中的類 "D" 的一個對象
            // 如果未發現,則嘗試自動裝載類 "D"

new \F();   // 創建定義在全局空間中的類 "F" 的一個對象
            // 如果未發現,則嘗試自動裝載類 "F"

// 調用另一個命名空間中的靜態方法或命名空間函數

B\foo();    // 調用命名空間 "A\B" 中函數 "foo"

B::foo();   // 調用命名空間 "A" 中定義的類 "B" 的 "foo" 方法
            // 如果未找到類 "A\B" ,則嘗試自動裝載類 "A\B"

D::foo();   // 使用導入規則,調用命名空間 "B" 中定義的類 "D" 的 "foo" 方法
            // 如果類 "B\D" 未找到,則嘗試自動裝載類 "B\D"

\B\foo();   // 調用命名空間 "B" 中的函數 "foo" 

\B::foo();  // 調用全局空間中的類 "B" 的 "foo" 方法
            // 如果類 "B" 未找到,則嘗試自動裝載類 "B"

// 當前命名空間中的靜態方法或函數

A\B::foo();   // 調用命名空間 "A\A" 中定義的類 "B" 的 "foo" 方法
              // 如果類 "A\A\B" 未找到,則嘗試自動裝載類 "A\A\B"

\A\B::foo();  // 調用命名空間 "A\B" 中定義的類 "B" 的 "foo" 方法
              // 如果類 "A\B" 未找到,則嘗試自動裝載類 "A\B"
?> 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章