PHPV5.3中的新特性,第3部分:名稱空間

測試5555發表於2011-12-19

很多語言都提供了名稱空間特性,包括 C++ 和 Java™ 程式語言。引入名稱空間是為了幫助組織大型的程式碼庫,因為在大型程式碼庫中,應用程式經常會出現函式名或類名重疊問題,這會引起其他問題。使用名稱空間可以幫助識別程式碼提供的函式或實用程式,甚至可以幫助指定其來源。一個例子就是 C# 中的 System 名稱空間,它包含有 .NET 框架提供的所有函式和類。

在其他未提供正式名稱空間的語言中(比如 PHP V5.2 以及更早版本),人們常常通過在類或函式名中使用特定的命名約定來發揮名稱空間的作用。比如 Zend Framework,其中每個類名以 Zend 開頭,並且每個子名稱空間使用下劃線分隔開。比如,類定義Zend_Db_Table 表示 Zend Framework 中的一個類並且提供資料庫功能。這種方法的一個缺點就是產生的程式碼非常繁瑣,尤其是那些包含好幾層的類或函式(Zend Framework 中的 Zend_Cache_Backend_Apc 就是一個例子)。另一個問題就是所有程式碼必須遵循這種風格,因此如果在應用程式中整合了不遵循這種命名約定的第三方程式碼後,問題就複雜了。

PHP 名稱空間的發展也並非一帆風順。它們最初計劃引入到 PHP V5 中,但是由於無法獲得恰當的實現,因此在開發階段被放棄。最後決定將它們併入到 PHP V6 中,在 2007 年決定將所有 nonunicode 增強移到另一個 PHP V5.x 發行版後,名稱空間隨後被移入到 PHP V5.3 中。儘管自最初的設計之後絕大部分名稱空間行為沒有發生變化,但是使用哪一種操作符卻成了最大的問題,並且社群成員對這個問題有不同的看法。2008 年 10 月最終決定使用反斜槓作為操作符,從而解決了所有在語言設計和適用性方面使用各種其他操作符的問題。

PHP 名稱空間

PHP 從其他語言中借鑑了很多名稱空間的語法和設計 — 最突出的是 C++。然而,PHP 名稱空間在某些方面具有自己的獨特性,這對於希望像在其他語言中那樣使用名稱空間的使用者來說是一個挑戰。在本節中,我們將研究 PHP 名稱空間的工作方式。

定義一個名稱空間

定義一個新的名稱空間非常簡單。要定義新名稱空間,在一個檔案中新增清單 1 中的程式碼作為第一個命令或輸出。

清單 1. 定義名稱空間

				 
<?php 
namespace Foo; 
class Example {} 
?> 

 

注意,以上 namespace 的宣告必須是檔案中的第一個命令或輸出。在它的前面新增任何內容都會導致一個致命的錯誤。清單 2 展示了有關這方面的一些例子。

清單 2. 定義名稱空間的錯誤方法

				 
/* File1.php */ 
<?php 
echo "hello world!"; 
namespace Bad; 
class NotGood {}
?> 

/* File2.php */ 
 <?php 
namespace Bad; 
class NotGood {} 
?> 

 

在清單 2 的第 1 部分中,我們嘗試在名稱空間定義之前回傳到控制檯,這導致產生一個致命錯誤。在清單的第 2 部分中,我們在 <?php 開啟標記的前面多加了一個空格,這樣也導致一個致命錯誤。在編寫自己的程式碼時一定要注意這種情況,因為這是 PHP 名稱空間中很常見的一種錯誤。

但是,上面的兩個例子都可以重新編寫,將名稱空間定義和將在名稱空間宣告中放入的程式碼放到獨立的檔案中,然後再將此檔案包含到原始檔案中。清單 3 演示了這一點。

清單 3. 修正定義名稱空間的錯誤方法

				
/* Good.php */ 
<?php 
namespace Good; 
class IsGood() {} 
?> 

/* File1.php */ 
<?php 
echo "hello world!"; 
include `./good.php`; 
?>

/* File2.php */ 
 <?php 
include `./good.php`; 
?> 

 

現在我們已經瞭解瞭如何在一個檔案中定義程式碼的名稱空間,接下來讓我們看看如何在應用程式中利用這個使用名稱空間的程式碼。

使用帶有名稱空間的程式碼

定義了名稱空間並在其中放入程式碼後,我們就可以在應用程式中方便地使用它。可以使用很多種方法呼叫帶有名稱空間的函式、類或常量。一種方式是顯式地將名稱空間引用為呼叫的字首。另一種方法是為名稱空間定義一個別名並使用該別名作為呼叫的字首,這樣做的目的是簡化名稱空間字首。最後,我們可以只在程式碼中使用名稱空間,這就使它成為預設名稱空間,並且在預設情況下,使所有程式碼都引用預設名稱空間。清單 4 演示了呼叫之間的不同之處。

清單 4. 在名稱空間內呼叫函式

				 
/* Foo.php */ 
<?php 
namespace Foo; 
function bar() 
{ 
    echo "calling bar....";
} 
?> 

/* File1.php */ 
<?php 
include `./Foo.php`; 
Foo/bar(); // outputs "calling bar...."; 
?> 

/* File2.php */ 
<?php 
include `./Foo.php`;
use Foo as ns; 
ns/bar(); // outputs "calling bar...."; 
?> 

/* File3.php */ 
<?php 
include `./Foo.php`; 
use Foo; 
bar(); // outputs "calling bar...."; 
?> 

 

清單 4 演示了在名稱空間 Foo 內呼叫函式 bar() 的不同方法。在 File1.php 內,我們看到了如何進行顯式呼叫,使用名稱空間的名稱作為呼叫字首。File2.php 使用名稱空間名稱的別名,因此我們使用別名代替名稱空間的名稱。最後,File3.php 僅使用名稱空間,這允許我們不需要使用任何字首來呼叫 bar()

我們還可以在一個檔案內定義多個名稱空間,只需要在檔案中新增更多 namespace 呼叫。清單 5 演示了這一點。

清單 5. 檔案中的多個名稱空間

				 
<?php 
namespace Foo; 
class Test {} 

namespace Bar; 
class Test {} 

$a = new FooTest; 
$b = new BarTest; 

var_dump($a, $b); 

Output: 
object(FooTest)#1 (0) {  
}  
object(BarTest)#2 (0) {  
} 

 

現在我們已經基本瞭解瞭如何在名稱空間內進行呼叫,讓我們瞭解一些更復雜的名稱空間呼叫以及它們如何工作。

名稱空間解析

要熟悉名稱空間的使用,其中一個難點就是了解如何進行範圍解析。儘管清單 4 所示的簡單例子是合理的,但是當我們開始對名稱空間進行彼此巢狀時,或者在一個名稱空間中試圖針對全域性空間發出呼叫是,就會出現問題。PHP V5.3 提供了可以以合理的方式自動解決這些問題的規則。

讓我們建立一些包含(include)檔案,每個檔案都定義了函式 hello()

清單 6. 在不同名稱空間中定義的 hello() 函式

				 
/* global.php */ 
<?php 
function hello() 
{ 
    echo `hello from the global scope!`;
} 
?> 

/* Foo.php */ 
<?php 
namespace Foo; 
function hello() 
{ 
    echo `hello from the Foo namespace!`;
} 
?> 

/* Foo_Bar.php */ 
<?php 
namespace Foo/Bar; 
function hello() 
{ 
    echo `hello from the Foo/Bar namespace!`;
} 
?> 

 

清單 6 在三個不同範圍內對 hello() 函式定義了三次:在全域性範圍內,在 Foo 名稱空間中,在 Foo/Bar 名稱空間中。根據發出hello() 函式呼叫的範圍,決定對哪個 hello() 函式執行呼叫。下面展示了這些呼叫的例子。在這裡,我們將使用 Foo 名稱空間檢視如何在另一個名稱空間中呼叫 hello() 函式。

清單 7. 從 Foo 名稱空間呼叫所有 hello() 函式

				 
<?php 
include `./global.php`; 
include `./Foo.php`;
include `./Foo_Bar.php`;

use Foo; 

hello();         // outputs `hello from the Foo namespace!` 
Barhello();   // outputs `hello from the Foo/Bar namespace!` 
hello();       // outputs `hello from the global scope!` 
?> 

 

可以看到,在當前名稱空間內引用子名稱空間時,可以縮短名稱空間字首(Foo/Bar/hello() 呼叫可被縮短為 Bar/hello())。並且我們看到如何指定以在全域性空間內呼叫方法:只需使用名稱空間操作符作為呼叫的字首。

現在,我們已經瞭解了名稱空間的工作機制,下面我們將檢視如何在自己的程式碼中使用它們。

 

回頁首

PHP 名稱空間用例

名稱空間的總體目標就是幫助我們更好地組織程式碼,減少全域性空間內的定義數量。在本節中,我們將檢視一些例子,看看名稱空間如何幫助我們輕鬆地實現這些目標。

使用名稱空間的第三方程式碼

許多 PHP 應用程式使用來自不同來源的程式碼,包括像 PEAR 庫那樣經過精心設計的程式碼,或者來自 CakePHP 或 Zend Framework 等各種框架的程式碼,或是來自 Internet 上不同位置的程式碼。在整合這些程式碼時,最主要的問題之一就是這些程式碼可能無法恰當地融合到已有程式碼中;函式或類名可能與應用程式中已經在使用的內容衝?。

其中一個例子就是 PEAR Date 包。它使用類名 Date,這是一個非常通用的類名,並且可以很好地切入到程式碼中的其他位置。因此,一個良好的解決方法就是在包內部的 Date.php 檔案的頂部新增一個簡單的名稱空間命令。現在,當希望使用 PEAR Date 類而不是我們自己的 PEAR Date 類時,就不會感到迷惑。

清單 8. 按照名稱空間的定義使用 PEAR Date 類

				
<?php 

require_once(`PEAR/Date.php`); 

use PEARDate;    // the name of the namespace we`ve specified in PEAR/Date.php

// since the current namespace is PEARDate, we don`t need to prefix the namespace name
$now = new Date(); 
echo $now->getDate();  // outputs the ISO formatted date 

// this example shows the full namespace specified as part of the class name 
$now = new PEARDateDate(); 
echo $now->getDate();  // outputs the ISO formatted date  
?>

 

我們已經在 PEAR/Date.php 檔案的 PEAR/Date 名稱空間內定義了 PEAR Date 類,因此現在只需在我們的檔案中包含程式碼並使用名稱空間,或使用名稱空間的名稱作為類或函式名的字首。通過這種方法,我們就可以安全地在應用程式中包含第三方程式碼。

名稱衝突不僅僅是第三方程式碼才有的問題。如果大型程式碼庫的各個部分永遠不會互相靠近,那麼也會出現此問題。在下一小節中,我們將瞭解名稱空間如何應對這個情況。

 

回頁首

避免實用函式名衝突

幾乎所有 PHP 應用程式都具有大量實用方法。雖然並非應用程式的任意物件都包含實用方法,並且也不一定存在於應用程式的所有部分,但是總的來說它在應用程式中確實發揮著作用。但是,隨著應用程式不斷壯大,實用方法會引起維護問題。

其中一個產生問題的位置就是單元測試,我們編寫程式碼來測試執行應用程式的程式碼。大多數單元測試套件被設計為執行整個測試套件中的所有測試。比如,我們有兩個永遠不會包含在一起的實用方法檔案,但是在測試套件中,它們就會包含在一起,因為我們會一次性測試整個應用程式。儘管使用這種方式設計應用程式不利於長期維護,但是它確實存在於大型遺留程式碼庫中。

清單 9 展示瞭如何避免這一問題。我們有兩個檔案 utils_left.php 和 utils_right.php,這是面向主要使用右手的使用者和主要使用左手的使用者的實用函式集合。對於每個檔案,我們在其各自的名稱空間內分別定義。

清單 9. utils_left.php 和 utils_right.php

				 
/* utils_left.php */
<?php 
namespace UtilsLeft;

function whichHand() 
{ 
    echo "I`m using my left hand!";
} 
?> 

/* utils_right.php */
<?php 
namespace UtilsRight; 

function whichHand() 
{ 
    echo "I`m using my right hand!";
} 
?> 

 

我們定義了一個 whichHand() 函式,函式的輸出表示我們使用哪一隻手。在清單 10 中,我們看到可以方便地包含兩個檔案並在希望呼叫的名稱空間之間進行切換。

清單 10. 同時使用 utils_left.php 和 utils_right.php 的示例

				 
<?php 
include(`./utils_left.php`); 
include(`./utils_right.php`);

UtilsLeftwhichHand();    // outputs "I`m using my left hand!"
UtilsRightwhichHand();  // outputs "I`m using my right hand!" 

use UtilsLeft; 
whichHand();                 // outputs "I`m using my left hand!" 

use UtilsRight; 
whichHand();                 // outputs "I`m using my right hand!"

 

現在,兩個檔案可以安全地包含在一起,並且我們指定了處理函式呼叫所需使用的名稱空間。而且,對現有程式碼的影響很小,因為重構功能只需要我們在檔案的頂部新增 use 語句,表示要使用的名稱空間。

可以對我們定義的 PHP 程式碼進一步擴充套件。在下一小節,我們將瞭解如何在名稱空間內覆蓋內部函式。

 

回頁首

覆蓋內部函式名稱

雖然 PHP 的內部函式經常可以提供非常棒的實用方法,但有時它們不能按照我們期望的那樣執行。我們需要增強它們的行為,以使函式符合我們的期望,但是我們也需要使用另一個名字重新定義函式,從而避免進一步混淆範圍。

檔案系統函式就需要我們執行這些操作。假設我們需要確保 file_put_contents() 建立的任何檔案具有某些許可權集。比如,假設我們希望這些檔案的許可權為只讀;我們可以使用一個新的名稱空間重新定義函式,如下所示。

清單 11. 在名稱空間內定義 file_put_contents() 

				
<?php 
namespace Foo; 

function file_put_contents( $filename, $data, $flags = 0, $context = null ) 
{ 
    $return = file_put_contents( $filename, $data, $flags, $context );
    
    chmod($filename, 0444);

    return $return;
} 
?>

 

我們在函式內呼叫內部 file_put_contents() 函式並使用一個反斜槓作為函式名的字首,表示該函式應當在全域性範圍內處理,這表示將呼叫內部函式。呼叫了內部函式後,我們隨後對檔案執行 chmod() 命令來設定相應的許可權。

還有許多例子可以演示如何使用名稱空間增強程式碼。在任何情況下,我們應避免執行不恰當的修改,比如將函式名或類名作為字首以生成獨特的名稱。我們現在還了解了如何使用名稱空間在大型應用程式中更加安全地包含第三方程式碼,同時不需要擔心名稱衝突。

 

回頁首

結束語

PHP V5.3 的名稱空間是該語言中一個非常受歡迎的新增特性,可以幫助開發人員合理地組織應用程式的程式碼。該特性使您能夠避免使用標準來處理名稱空間,允許您編寫更高效的程式碼。儘管名稱空間的出現經歷了很長時間,但對於受名稱衝突困擾的大型 PHP 應用程式來說,它是一個非常受歡迎的特性。


相關文章