認識命名空間
==========
理解命名空間
-----------
在很久很久以前, PHP的世界大致上是這樣的: 啟動一個php腳本后, 一個個函數, 一個個類就被載入到同一個屋檐下, 開始按照你設計的邏輯一條條執行就夠了.
在這種情況下, 為了讓程序順利執行, 你必須確保一點: 同一個屋檐下的所有函數或者類必須有互不相同唯一的名字. 否則當出現重名時, php就不知道該執行哪個, 因此遇到這種情況, php必然會報錯終止.
在那時, 這確實很容易做到, 因為那時的php的項目都很單純, 很小, 沒有野心, 一個項目中的函數和類數量有限, 它們可以相對和諧地共同生活在同一個屋檐下.
但是, 隨著互聯網行業發展, 行業對Web項目的要求也在迅速提高: 一個PHP項目可能有數人甚至數十人共同開發, 項目需求龐大復雜. 產品更新迭代的需求越來越快.
這時, 在一個屋檐下容納所有的函數和類導致這個屋檐越來越擁擠, 難以管理. 當屋檐倒塌(需求出現重大變更), 也許埋葬的就是整個項目(代碼難以復用) . 這時, 一系列迫切需要解決的問題出現了:
1. 項目不同的部件之間如何避免出現函數和類名的沖突?
2. 項目越來越大, 命名時單詞似乎不夠用了, 如何讓函數和類命名時更容易?
3. 面對一大堆的函數和類, 如何有條理地管理它們的載入過程?
4. 面對快速變化的市場和需求, 項目必須模塊化, 易分割, 易替換, 可重用.
命名空間(namespace)為解決以上問題提供重要支撐.
namespace 與邏輯無關, 它是一個管理概念, 通過命名空間你可以把函數和類管理的井井有條.
讓我們形象一點來闡述到底什么是"namespace".
中國有約70萬個行政村, 假如你的家鄉在"王村", 而中國叫"王村"的村子可能不下數百個. 而我想去訪問你的家鄉. 我該如何去?
你一定會這樣告訴我: **山西省 太原市 平遙縣 西六支鄉 王村**(假想地址). 如果王村就是一個class, 那么你正是通過namespace確保訪問到你的家鄉,而不是訪問另一個王村.
用程序語言的書寫習慣, 上面的地址可以換一種格式: **山西省\太原市\平遙縣\西六支鄉\王村**
好了, 中文太復雜, 讓我們換成拼音: **ShanXi\TaiYuan\PingYao\XiLiuZhi\WangCun**.
如果你初次接觸命名空間, 現在是不是有些恍然大悟, 原來這就是命名空間, 它可以讓我們輕松從70萬個村子(class)中精確找到自己真正要訪問的那個王村(class)而不會迷路.
使用命名空間
----------
在php中,聲明一個命名空間非常簡單,使用`namespace` 關鍵字:
```
namespace ShanXi\TaiYuan\PingYao\XiLiuZhi;
```
在聲明了命名空間之后, **這條聲明之后直到下一條命名空間聲明之前**的所有函數,類,const常量就隸屬于這個命名空間了.
```
<?php
namespace ShanXi\TaiYuan\PingYao\XiLiuZhi;
const WangCun="I am WangCun.";
function wang_cun(){
echo WangCun;
}
class WangCun {
public function getName(){
wang_cun();
}
}
namespace ShanXi\TaiYuan\PingYao\Other;
const WangCun="I am WangCun form Other.";
function wang_cun(){
echo WangCun;
}
class WangCun {
public function getName(){
wang_cun();
}
}
namespace Test;
$obj1=new \ShanXi\TaiYuan\PingYao\XiLiuZhi\WangCun;
$obj2=new \ShanXi\TaiYuan\PingYao\Other\WangCun;
$obj1->getName(); // 輸出: "I am WangCun."
$obj2->getName(); // 輸出: "I am WangCun from Other."
```
通常一個php文件中只聲明一個命名空間, 為了解釋命名空間的用法和特性, 上面的示例, 我故意在同一個文件中聲明了三個命名空間.
如你所見, 我們在同一文件中似乎聲明了兩次名稱"相同的"const常量, 兩個名稱"相同的"函數, 兩個名稱"相同的"class. 它們可以和諧共處,不會產生沖突,因為它們位于不同的屋檐(namespace)下.
如果你足夠細心, 觀察上面的代碼你已經能夠知曉如何訪問命名空間下的成員:
* 只要加上命名空間路徑, 就可以訪問其他命名空間的成員.
* 如果沒有加命名空間路徑, PHP就將從當前命名空間下尋找成員.
### 一點疑惑
在上面的代碼中, 為什么使用的是:
```
$obj1=new \ShanXi\TaiYuan\PingYao\XiLiuZhi\WangCun;
```
而不是
```
$obj1=new ShanXi\TaiYuan\PingYao\XiLiuZhi\WangCun;
```
這就像訪問目錄中的文件一樣, 如果你不加`\\`, php會認為你要訪問的是相對的命名空間, php會認為你想訪問的是`\Test\ShanXi\TaiYuan\PingYao\XiLiuZhi\WangCun`
深入命名空間
------------
### 全局命名空間
如果一個類或函數沒有定義在任何namespace之下, 那么它就屬于**全局命名空間**.
通常, 在一個命名空間下的代碼中訪問**類**或**const常量**時, php只會在當前命名空間下尋找. 如果未找到, 則會報錯,甚至退出. 不會進入全局空間搜索.
但對于函數訪問則特殊一點, 因為php是從函數式編程語言發展而來, 內置的數千函數現在都隸屬于了全局命名空間. 如果每次使用函數時, 都加上`\\`前綴無疑很容易讓人情緒崩潰.
因此, 如果在命名空間下沒有找到這個函數, php會試圖再去全局空間找一次. 在全局空間沒有找到才會報錯.
* 有`\\`符號, 則表示絕對命名空間路徑. 沒有`\\`符號, 則表示相對命名空間路徑.
* 全局空間習慣上也可以稱為"根空間"
### use 和 as
到現在, 我相信你已經理解并學會了命名空間的簡單使用. 讓我們繼續深入.
還是上面的示例代碼, 讓我們專注于 Test 命名空間:
```
namespace Test;
$obj1=new \ShanXi\TaiYuan\PingYao\XiLiuZhi\WangCun;
$obj2=new \ShanXi\TaiYuan\PingYao\Other\WangCun;
$obj1->getName(); // 輸出: "I am WangCun."
$obj2->getName(); // 輸出: "I am WangCun from Other."
```
在訪問其他命名空間成員時, 我指定了完整路徑, 只訪問一個成員還說得過去, 如果訪問多個成員, 每次寫完整路徑豈不是很煩人?
`use` 和 `as` 關鍵字解決你的煩惱, 下面是改造后的代碼示例:
```
namespace Test;
use ShanXi\TaiYuan\PingYao\XiLiuZhi\WangCun as WangCun;
use ShanXi\TaiYuan\PingYao\Other\WangCun as OtherWangCun;
$obj1=new WangCun;
$obj2=new OtherWangCun;
$obj1->getName(); // 輸出: "I am WangCun."
$obj2->getName(); // 輸出: "I am WangCun from Other."
```
嗯, 終于可以少敲很多次鍵盤了. `use` 和 `as` 做了什么?
* **use** 告訴php, 我要在Test命名空間下**直接**使用其他命名空間的某個類.
* **as** 則告訴php這個其他命名空間的類在當前命名空間下使用什么名稱去訪問.
* 當你不需要重新設定使用的名稱時, 而且不會引發重名問題時, **as** 可以省略
因此上面的代碼可以簡化為:
```
namespace Test;
use ShanXi\TaiYuan\PingYao\XiLiuZhi\WangCun;
use ShanXi\TaiYuan\PingYao\Other\WangCun as OtherWangCun;
...
```
### 用namespace管理代碼載入
曽幾何時, php項目中到處充斥著include. 你必須時時小心以下問題:
* 確保include的文件確實存在
* 確保include的文件的內容是你需要的
* 確保include的文件的內容不與以有代碼產生沖突
* 每次修改include的文件, 都要小心翼翼確保不會產生沖突
* 在一個被include的文件刪除后, 你必須在所有代碼中搜索一遍, 刪除每一處include
* include時還需要特別留意工作目錄和相對路徑的問題.
對程序員來說, 這簡直一場噩夢. 因為它們與邏輯幾乎毫無關系, 卻需要耗費你大量的時間心血去處理, 最重要的是, 這些問題真的太太傷感情了, 很容易讓你懷疑自己選擇程序員是否是個正確的決定.
namespace 助你解脫.
namespace 為你的代碼帶來了分明的層次感, 它幾乎就是我們電腦上目錄結構的翻版. 你的電腦上文件目錄再多, 我相信你也能輕松找到那個自己密不外宣的特殊目錄. 你越悉心管理自己的電腦目錄, 你使用電腦的工作效率就越高,出錯概率越低.
所以, 你的php項目的 namespace 規劃的越合理, 越層次分明, 你的項目就會越容易維護.
如果我們把命名空間與文件系統上的層次結構對應起來, 自動載入就成為了現實, include就成為了歷史:
目錄結構:
```
.\ShanXi
|- TaiYuan
|- PingYao
|- XiLiuZhi
| |- WangCun.php
|
|- Other
|- WangCun.php
```
現在你只需要定義一個自動載入函數注冊到autload函數棧, 為命名空間路徑和本地路徑的關系建立邏輯, 根據這個固定的邏輯, 就可以自動載入任何的符合該邏輯的類了!
最重要的是:
1. 自定義的自動載入函數可以注冊許多個, 意味著你可以設計多種自動載入邏輯. 在訪問類時, php會根據注冊時的順序, 依次執行, 直到找到需要載入的文件為止.
2. 只有在第一次訪問類時, 才會觸發自動載入, 如果一個類沒有用到, 那么這個類文件也就不會被載入.
在你設計好自動加載函數后, 調用`spl_autoload_register($callable)` 即可將你的自動加載器注冊到php的autoload棧中.
所有使用了命名空間的現代的PHP項目和框架, 都是使用這種機制實現了類文件的自動加載. 你只需要按照規則放置類文件, 就可以隨處使用它們.
介紹到這里, 你對命名空間的了解已經足夠多. 如果還想徹底精通, 可以訪問PHP文檔中命名空間部分:
<http://php.net/manual/zh/language.namespaces.php>
- 序
- 前言
- 內容簡介
- 目錄
- 基礎知識
- 起步
- 控制器
- 模型
- 模板
- 命名空間
- 進階知識
- 路由
- 配置
- 緩存
- 權限
- 擴展
- 國際化
- 安全
- 單元測試
- 拿來主義
- 調試方法
- 調試的步驟
- 調試工具
- 顯示trace信息
- 開啟調試和關閉調試的區別
- netbeans+xdebug
- Socketlog
- PHP常見錯誤
- 小黃鴨調試法,每個程序員都要知道的
- 應用場景
- 第三方登錄
- 圖片處理
- 博客
- SAE
- REST實踐
- Cli
- ajax分頁
- barcode條形碼
- excel
- 發郵件
- 漢字轉全拼和首字母,支持帶聲調
- 中文分詞
- 瀏覽器useragent解析
- freelog項目實戰
- 需求分析
- 數據庫設計
- 編碼實踐
- 前端實現
- rest接口
- 文章發布
- 文件上傳
- 視頻播放
- 音樂播放
- 圖片幻燈片展示
- 注冊和登錄
- 個人資料更新
- 第三方登錄的使用
- 后臺
- 微信的開發
- 首頁及個人主頁
- 列表
- 歸檔
- 搜索
- 分頁
- 總結經驗
- 自我提升
- 進行小項目的鍛煉
- 對現有輪子的重構和移植
- 寫技術博客
- 制作視頻教程
- 學習PHP的知識和新特性
- 和同行直接溝通、交流
- 學好英語,走向國際
- 如何參與
- 瀏覽官網和極思維還有看云
- 回答ThinkPHP新手的問題
- 嘗試發現ThinkPHP的bug,告訴官方人員或者push request
- 開發能提高效率的ThinkPHP工具
- 嘗試翻譯官方文檔
- 幫新手入門
- 創造基于ThinkPHP的產品,進行連帶推廣
- 展望未來
- OneThink
- ThinkPHP4
- 附錄