##一小段曆史##
在PHP5.3之前的版本(2009年以前),我們定義的所有類都在同一個全局性的層級下。
User類,Contact類,StripeBiller類,它們都在同一個全局命名空間下。
這看起來很簡單,但是這将讓代碼結構變得冗雜,是以PHPer開始使用在類名裡使用下劃線。例如:如果我寫了一個包叫做“Cacher”,我可能将包裡的一個類命名為Mattstauffer_Cacher,以區分出它和其他Cacher的異同——例如Mattstauffer_Database_Cacher,好表明它就是一個API緩存器。
為了看起來更舒服,甚至通過自動加載的方式,将類名以下劃線分割作為檔案夾名來存放檔案,例如 Mattstauffer_Database_Cacher存放在目錄Mattstauffer/Database/Cacher.php下自動加載是一段用于在整個php應用中代替require或者include方法的代碼,PHP有一個特别的機制去需找代碼的位置。在後文會有更詳盡的介紹
但是這仍然是相當混亂,類有被命名為叫做Zend_Db_Statement_Oracle_Exception,或者更糟糕的名字。值得慶幸的是,在PHP5.3版本中,引入了命名空間。
##命名空間的基礎##
在類的結構上,命名空間就像一個虛拟的字典。class Mattstauffer_Database_Cacher可以在Mattstauffer\Database的命名空間下寫作class Cacher:<?php
class Mattstauffer_Database_Cacher {}
而現在是<?php namespace Mattstauffer\Database;
class Cacher {}
而我們在應用的任何一個地方都能以Mattstauffer\Database\Cacher引用它。
##一個真實的例子##
讓我們來看看Karani——這是一個帶财務子產品的CRM系統,是以系統中其他任何地方都可能用到捐贈者donors和收據receipts
Karani被設定為最進階的命名空間,(就像頂級檔案夾,通常用你的應用或包的名字命名)。有些類可能與Contacts有關,有些可能與Billing有關,是以需要為二者都建立子命名空間:Karani\Billing和Karani\Contacts。
就像這樣<?php namespace Karani\Billing;
class Receipt {}<?php namespace Karani\Billing;
class Subscription{}<?php namespace Karani\Contacts;
class Donor {}
現在,我們可以畫出這樣一個字典結構:Karani
Billing
Receipt
Subscription
Contacts
Donor
##引用同一個命名空間下的類##
是以,這樣寫能讓“給捐贈(Subscription)開出收據(Receipt)”變得十分容易:<?php namespace Karani\Billing;
class Subscription{
public function sendReceipt()
{
$receipt = new Receipt;
}}
由于Receipt類和Subscription類在同一個命名空間裡,你可以直接像上面那樣寫,就像沒有使用過命名空間一樣。
##引用不同命名空間下的類##
好的,如果我想在捐贈者(Donor)裡通路收據(Receipt)呢<?php namespace Karani\Contacts;
class Donor{
public function sendReceipt()
{
// This won't work!
$receipt = new Receipt;
}}
你可能會想:這樣肯定會出錯。
現在代碼是在Karani\Contacts命名空間下,是以如果我們用new Receipt,PHP假設我們引用的是Karani\Contacts\Receipt,但是這個類卻是不存在的。這不是我們想要的結果
是以,你會得到一個Class Karani\Contacts\Receipt not found的錯誤。
你可能想到要改為$receipt = new Karani\Billing\Receipt,但是也不會生效。我們現在在Karani\Contacts的命名空間下,任何你寫的代碼都會預設處在這個命名空間下,那麼Karani\Billing\Receipt将作為名為Karani\Contacts\Karani\Billing\Receipt的類加載,是以這樣也是不對的。
##使用塊或者完全限定類名##
你可以有兩個選擇:
第一種方案,你可以把一個反斜杠加載類名的開頭,使用完全限定類名的方式(FQCN (Fully Qualified Class Name)): $receipt = new \Karani\Billing\Receipt;這将會在PHP查找類時發出一個信号,讓PHP不在目前的命名空間下查找。
如果你使用這種方案,你可以在你應用中任意一個地方使用而不必擔心你所在的命名空間的問題。
或者,第二種方案,你可以在類檔案的最開頭添加上use,隻要這樣寫就可以正常引用Receipt:<?php namespace Karani\Contacts;
use Karani\Billing\Receipt;
class Donor{
public function sendReceipt()
{
$receipt = new Receipt;
}}
在目前命名空間使用use引入一個不同命名空間下的類,這樣讓編寫代碼更加友善。隻要你寫了這個引用,你在這個類檔案裡任何時候使用Receipt,PHP都會認為你是指引入的那個類。
##别名##
但是,要是代碼中還有一個Receipt類在目前的命名空間下呢?要是你的類需要同時使用Karani\Contacts\Receipt和Karani\Billing\Receipt兩個類呢?
你不能隻引用Karani\Billing\Receipt類,這樣還是不能同時使用這兩個類——他們在類裡面的名字是相同的。
你可以使用别名實作。你可以将use這種聲明方式改為use Karani\Billing\Receipt as BillingReceipt;。對類起了一個别名,就可以在目前類中使用BillingReceipt代替Karani\Billing\Receipt了
##PSR-0/PSR-4中的自動加載(Autoloading)##
你想想上面我用檔案夾做的例子?
這很容易想到類的結構,但事實上在命名空間或檔案結構之間沒有任何内在聯系。如果你不使用了自動加載(autoloader),PHP是不會知道這些類存放在目錄結構的什麼位置。
幸運的是,自動加載的标準, PSR-0(現在已經過時啦)和PSR-4能夠是實作從命名空間比對到檔案夾位置。是以,如果你使用PSR-0或者PSR-4,就非常像你在使用Composer或者其他的現代架構,有一個相容性的自動加載器,你就能像你的所有類都在同一個檔案夾一樣使用它們,不用其他require或者include了。
##Composer和PSR-4自動加載##
現在我想讓Karani命名空間存在于我的src檔案夾下
例如一個普通的,獨立架構項目的檔案夾結構app
public
src
Billing
Contacts
vendor
從上面可以看出,src檔案夾代表Karani的頂級命名空間。隻要我使用composer作為自動加載器,我所需要做的隻是教會composer怎麼從檔案夾比對命名空間,就能夠實作在應用中自動加載。我們現在用PSR-4嘗試一下。
我打開composer.json配置檔案,然後添加上PSR-4自動加載節點{
"autoload": {
"psr-4": {
"Karani\\": "src/"
}
}}
你可以看到:左邊是我們定義的命名空間(注意你在這需要使用雙反斜杠),右邊是目錄。
##結語##
那還有很多内容,但它們都十分簡單:98%的時間你都會享受到PSR-4-structured帶來的工作便利,Composer自動實作加載,設定類。
在這98%的時間裡,你隻需修改composer.json,在裡面寫明頂級命名空間的根目錄,然後你可以有實作目錄中的命名空間和檔案夾/檔案之間有一對一的關系。
記住,下次再出現Class SOMETHING not found的錯誤,你很可能隻需要用use字段在檔案定引入這個類就可以了。