如何使用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
11namespace 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 | <?php |
Namespace的宣告會緊接著在<?php
的下一行,命名規則建議最頂層的namespace名稱以vendor命名(公司名稱或組織名稱),必須是世界上唯一的名稱,以免class名稱雖然不衝突,但namespace名稱卻衝突了。
之後所有的class, interface, function, constant都是活在Oreilly這個namespace下。
1 | <?php |
Namespace允許我們如同檔案系統的目錄結構般對namespace加以分類,Oreilly有很多書,與ModernPHP
這本書相關的class, interface, function, constant應該放在ModernPHP
這個subnamespace較恰當。
就理論上而言,只要每個class所宣告的namespace相同,對於PHP而言就視為處在相同namespace,與PHP檔案放在檔案系統哪個目錄無關;但在實務上,為了符合PSR-4 autoloader標準,建議namespace與subnamespace的關係與實際上的檔案系統的目錄階層一樣。事實上,Laravel的namespace與subnamespace的關係也是依照這個建議所實踐。
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
3use 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 | namespace Illuminate\Http; |
在<?php
下,我們會馬上宣告namespace,接下來馬上使用use,把目前這個class會用到其他namespace的class全部use進來,這樣接下來的code就可以很簡單的使用那些外部的class。
在PHP 5.6,PHP增加了2個新功能,讓我們不只可以use class,還可以use function與constant。
Use Function
使用use func
關鍵字。1
2use func Namespace\functionName;
functionName();
Use Constant
使用use constant
關鍵字。1
2use constant Namespace\CONST_NAME;
echo CONST_NAME;
As關鍵字
以Laravel 5.1的Response class為例 :
1 | namespace Illuminate\Http; |
第6行1
use Symfony\Component\HttpFoundation\Response as BaseResponse;
由於Symfony也叫Response class,而Laravel也叫Response class,我們可以先將Symfony的Response class取別名
為BaseResponse,以免與Laravel的class名稱相同。
PHP內部並不會因為Symfony與Laravel都取名為Response class而出問題,事實上在PHP內部,Symfony的class為Symfony\Component\HttpFoundation\Response
,而Laravel的class為Illuminate\Http\Response
。
Global Namespace
Global namespace是PHP歷史留下來的產物,也是PHP所獨有的。在還沒有Namespace之前,PHP已經有大量的class與function,而這些沒有namespace的class與function,通通歸在global namespace底下。
當一個class, interface, function, constant出現時,PHP會依照以下順序搜尋 :
- 目前namespace。
- 使用use的其他namespace。
- 使用完整路徑的namespace。
所以如果是global namespace的class, interface, function, constant,PHP就會找不到而出現錯誤。
對於global namespace,PHP的解決方案是加上\
。
一個典型的例子是PHP的Exception class。1
2
3
4
5
6
7
8
9namespace My\App;
class Foo
{
public function doSomething()
{
$exception = new Exception();
}
}
這樣寫會導致PHP找不到Exception class而出錯,因為Exception是在global namespace。
要改成如下的寫法才正確 :1
2
3
4
5
6
7
8
9namespace 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
3use Illuminate\Contracts\Support\Jsonable,
Illuminate\Contracts\Support\Renderable,
Illuminate\Http\Http\Request;
但實務上,建議每一行的use只搭配一個namespace,雖然會多打一點字,但比較不會造成困擾。2 2事實上,其他程式語言(C++, C#, Java)也都是每一行個use只搭配一個namespace。1
2
3use 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
6namespace 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 | namespace MyName { |
宣告namespace :
- 使用
namespace
關鍵字。 - 巢狀namespace需在括號內。
- class須放在namespace內。
引用namespace :
- 使用
using namespace
關鍵字。 - namespace與subnamespace之間用
::
,路徑不包含到class名稱,類似path概念。
引用class :
- 使用
using
關鍵字,直接指定class的完整路徑。
C#
1 | namespace MyName.SubName { |
宣告namespace :
- 使用
namespace
關鍵字。 - 巢狀namespace不須使用括號。
- class須放在namespace內。
引用namespace :
- 使用
using
關鍵字。 - namespace與subnamespace之間用
.
,路徑不包含到class名稱,類似path概念。
引用class :
- 使用
using
關鍵字,直接指定class的完整路徑。
Java
1 | package MyName.SubName; |
宣告namespace :
- 使用
package
關鍵字。 - 巢狀namespace不須使用括號。
- class不須放在namespace內。
引用namespace :
使用
import
關鍵字。namespace與subnamespace之間用
.
,最後須加上*
,類似path概念。
引用class :
- 使用import關鍵字,直接指定class的完整路徑。
Ruby
1 | module MyName |
宣告namespace :
- 使用
module
關鍵字。 - 巢狀module需在括號內
- class須放在module內。
引用namespace :
- 不提供。
引用class :
- 不提供。
PHP
1 | namespace MyName\SubName; |
宣告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。