深入探討PHP 5.3的Namespace觀念與用法

Namespace是物件導向語言很基本的功能,主要在解決class名稱衝突的問題。C++與C#也稱為namespace,Java則稱為package,雖然名稱不一樣,但精神都一樣,不過在語法部分PHP有自己的主張,與其他語言稍有不同。

Namespace最大的貢獻是使得framework與package能被大量地流通使用,是PHP現代化中最重要的一個基石。

Version


PHP 5.3

定義


Namespace觀念在2010年末的PHP 5.3被引進,它讓我們能將PHP的class, interface, function, constant再用namespace分類一次,如同檔案系統的目錄結構一樣可以分階層管理。也就是說,每個framework與package都有自己的namespace,這樣就可以解決framework與package的class名稱可能互相衝突,或與自己專案的class名稱相衝突的問題。

為什麼要使用Namespace?


以Laravel 5.1的Response class為例 :

1
2
3
4
5
6
7
8
9
10
11
namespace Illuminate\Http;

use ArrayObject;
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Contracts\Support\Renderable;
use Symfony\Component\HttpFoundation\Response as BaseResponse;

class Response extends BaseResponse
{

...
}

第8行

1
class Response extends BaseResponse

我們可以發現Laravel建立了Response class,這是一個很普通的class名稱,很可能其他package會用到,甚至我們自己的專案也會用到Response這個class名稱,若因為用了其他framework或package而導致class名稱相衝,需要重新改code換class名稱,這就失去了快速開發的意義了。

第1行

1
namespace Illuminate\Http;

我們可以發現Response class是被定義在Illuminate\Http這個namespace下,也就是說,class名稱可以互相衝突,但只要躲在不相衝突的namespace下即可。因為namespace的緣故,class已經被包在namespace下,因此class名稱可以互相衝突。

假如是一個人從頭寫到尾的PHP專案,就不會遇到class名稱互相衝突的問題,但如果大量使用別人的framework或package,就很可能遇到class名稱相衝突的問題,所以才說namespace是PHP現代化最重要的基石。1 1現代PHP與傳統PHP的最大差別就是引入framework與package。

宣告Namespace


1
2
3
4
5
<?php
namespace Oreilly;

...
?>

Namespace的宣告會緊接著在<?php的下一行,命名規則建議最頂層的namespace名稱以vendor命名(公司名稱或組織名稱),必須是世界上唯一的名稱,以免class名稱雖然不衝突,但namespace名稱卻衝突了。

之後所有的class, interface, function, constant都是活在Oreilly這個namespace下。

1
2
3
4
5
<?php
namespace Oreilly\ModernPHP;

...
?>

Namespace允許我們如同檔案系統的目錄結構般對namespace加以分類,Oreilly有很多書,與ModernPHP這本書相關的class, interface, function, constant應該放在ModernPHP這個subnamespace較恰當。

就理論上而言,只要每個class所宣告的namespace相同,對於PHP而言就視為處在相同namespace,與PHP檔案放在檔案系統哪個目錄無關;但在實務上,為了符合PSR-4 autoloader標準,建議namespace與subnamespace的關係與實際上的檔案系統的目錄階層一樣。事實上,Laravel的namespace與subnamespace的關係也是依照這個建議所實踐。

PHP內部實踐Namespace方式

Namespace聽起來很玄,但PHP實踐namespace的方式卻很簡單,以Laravel的Response class而言,事實上在PHP內部會將namespace名稱與class名稱加起來一起識別,也就是內部是以Illuminate\Http\Response這個class名稱運作,如此就可以保證class名稱永遠不會衝突。

Use關鍵字


若我們要使用Laravel的Response class,原本PHP要這樣寫

1
2
$response = new Illuminate\Http\Response('Oops', 400);
$response->send();

由於使用了namespace,造成使用class時必須引用其完整namespace路徑,否則PHP會找不到class。

這種寫法假如寫一次就算了,若每次要使用Response class都要這樣寫一定會瘋掉。

PHP新加入了use關鍵字,讓我們只要輸入完整的namespace路徑一次就好。

1
2
3
use Illuminate\Http\Response;
$response = new Response('Oops', 400);
$response->send();

我們只要透過use輸入Illuminate\Http\Response一次就好,接下來要使用Response class時,只要打Response即可,不用再輸入完整namespace路徑。

實務上使用Use


以Laravel 5.1的Response class為例 :

1
2
3
4
5
6
7
8
9
10
11
namespace Illuminate\Http;

use ArrayObject;
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Contracts\Support\Renderable;
use Symfony\Component\HttpFoundation\Response as BaseResponse;

class Response extends BaseResponse
{

...
}

<?php下,我們會馬上宣告namespace,接下來馬上使用use,把目前這個class會用到其他namespace的class全部use進來,這樣接下來的code就可以很簡單的使用那些外部的class。

在PHP 5.6,PHP增加了2個新功能,讓我們不只可以use class,還可以use function與constant。

Use Function


使用use func關鍵字。

1
2
use func Namespace\functionName;
functionName();

Use Constant


使用use constant關鍵字。

1
2
use constant Namespace\CONST_NAME;
echo CONST_NAME;

As關鍵字


以Laravel 5.1的Response class為例 :

1
2
3
4
5
6
7
8
9
10
11
namespace Illuminate\Http;

use ArrayObject;
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Contracts\Support\Renderable;
use Symfony\Component\HttpFoundation\Response as BaseResponse;

class Response extends BaseResponse
{

...
}

第6行

1
use Symfony\Component\HttpFoundation\Response as BaseResponse;

由於Symfony也叫Response class,而Laravel也叫Response class,我們可以先將Symfony的Response class取別名為BaseResponse,以免與Laravel的class名稱相同。

as純粹是為了讓人識別用,避免寫出class Response extends Response的程式碼。

PHP內部並不會因為Symfony與Laravel都取名為Response class而出問題,事實上在PHP內部,Symfony的class為Symfony\Component\HttpFoundation\Response,而Laravel的class為Illuminate\Http\Response

as也常拿來縮短namespace名稱。

Global Namespace


Global namespace是PHP歷史留下來的產物,也是PHP所獨有的。在還沒有Namespace之前,PHP已經有大量的class與function,而這些沒有namespace的class與function,通通歸在global namespace底下。

當一個class, interface, function, constant出現時,PHP會依照以下順序搜尋 :

  1. 目前namespace。
  2. 使用use的其他namespace。
  3. 使用完整路徑的namespace。

所以如果是global namespace的class, interface, function, constant,PHP就會找不到而出現錯誤。

對於global namespace,PHP的解決方案是加上\

一個典型的例子是PHP的Exception class。

1
2
3
4
5
6
7
8
9
namespace My\App;

class Foo
{

public function doSomething()
{

$exception = new Exception();
}
}

這樣寫會導致PHP找不到Exception class而出錯,因為Exception是在global namespace。

要改成如下的寫法才正確 :

1
2
3
4
5
6
7
8
9
namespace My\App;

class Foo
{

public function doSomething()
{

throw new \Exception();
}
}

Exception前面加上\之後,告訴PHP Exception class是屬於global namespace。

Good Practice


一個use只搭配一個namespace
就語法上,PHP允許一個use關鍵字搭配很多namespace :

1
2
3
use Illuminate\Contracts\Support\Jsonable,
Illuminate\Contracts\Support\Renderable,
Illuminate\Http\Http\Request;

但實務上,建議每一行的use只搭配一個namespace,雖然會多打一點字,但比較不會造成困擾。2 2事實上,其他程式語言(C++, C#, Java)也都是每一行個use只搭配一個namespace。

1
2
3
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Http\Http\Request;

一個檔案只使用一個namespace
就語法上,PHP允許一個檔案內包含多個namespace,每個namespace以大括號刮起來,類似C++與C#的寫法 :

1
2
3
4
5
6
namespace Foo {
// Declare classes, interfaces, functions, and constants here
}
namespace Bar {
// Declare classes, interfaces, functions, and constants here
}

但實務上,基於一個class一個檔案(one class per file)原則,一個檔案也應該只包含一個namespace。3 3事實上,Laravel的source code也都是一個檔案只包含一個namespace。

與其他程式語言比較


C++

1
2
3
4
5
6
7
8
9
10
namespace MyName {
namespace SubName {
class MyClass {

};
}
};

using namespace MyName::SubName;
using MyName::SubName::MyClass;

宣告namespace :

  • 使用namespace關鍵字。
  • 巢狀namespace需在括號內。
  • class須放在namespace內。

引用namespace :

  • 使用using namespace關鍵字。
  • namespace與subnamespace之間用::,路徑不包含到class名稱,類似path概念。

引用class :

  • 使用using關鍵字,直接指定class的完整路徑。

C#

1
2
3
4
5
6
7
8
namespace MyName.SubName {
class MyClass {
...
}
}

using MyName.SubName;
using MyName.SubName.MyClass;

宣告namespace :

  • 使用namespace關鍵字。
  • 巢狀namespace不須使用括號。
  • class須放在namespace內。

引用namespace :

  • 使用using關鍵字。
  • namespace與subnamespace之間用.,路徑不包含到class名稱,類似path概念。

引用class :

  • 使用using關鍵字,直接指定class的完整路徑。

Java

1
2
3
4
5
6
7
8
package MyName.SubName;

public class MyClass {
...
}

import MyName.SubName.*;
import MyName.SubName.MyClass;

宣告namespace :

  • 使用package關鍵字。
  • 巢狀namespace不須使用括號。
  • class不須放在namespace內。

引用namespace :

  • 使用import關鍵字。

  • namespace與subnamespace之間用.,最後須加上*,類似path概念。

引用class :

  • 使用import關鍵字,直接指定class的完整路徑。

Ruby

1
2
3
4
5
6
7
module MyName
module SubName
class MyClass
...
end
end
end

宣告namespace :

  • 使用module關鍵字。
  • 巢狀module需在括號內
  • class須放在module內。

引用namespace :

  • 不提供。

引用class :

  • 不提供。

PHP

1
2
3
4
5
6
7
8
namespace MyName\SubName;

class MyClass
{

...
}

use MyName\SubName\MyClass;

宣告namespace :

  • 使用namespace關鍵字。
  • 巢狀namespace不須使用括號。
  • class不須放在namespace內。

引用namespace :

  • 不提供。

引用class :

  • namespace與subnamespace之間用\
  • 使用use關鍵字,直接指定class的完整路徑。

Conclusion

  • namespace是class的再一次分類,主要在解決class名稱衝突的問題。
  • namespace最頂層的名稱最重要,必須是世界唯一,subnamespace則只要容易辨識理解即可。
  • PHP的use的完整路徑須包含到class,與其他程式語言不太一樣。
  • PHP的namespace與subnamespace之間用\分隔,與其他程式語言不太一樣。
  • Global namespace是PHP所獨有的,在class前面加上\,為了相容沒有namespace時代的傳統PHP。
2015-08-29