php的名稱空間和自動載入實現
類的自動載入
引子
當我們在php程式碼中載入類時,我們必須要include或者require 某個類檔案。
但遇到類似的情況,例如:
require "Class1.php";
require "Class2.php";
$boy = $_GET['sex'] = 0?true:false;
if($boy)
{
$class1 = new Class1();
}else{
$class2 = new Class2();
}
假如我們需要判斷一個人的性別,如果是男的就例項化class1這個類,如果是女的就例項化class2這個類
那麼問題來了,這段程式碼,每次我只需要執行一個例項化物件。然而我卻一直載入兩個類檔案。
這樣豈不是浪費了一定的時間去載入這一個類檔案?
php對於這種問題提出瞭解決方案
spl_auto_register()
這個概念在 在php5.1中提出
spl_auto_register($autoload_function = null, $throw = true, $prepend = false)
函式包含3個引數
①autoload_function 這是一個函式【方法】名稱,可以是字串或者陣列(呼叫類方法使用)。這個函式(方法)的功能就是,來把需要new 的類檔案包含include(requeire)進來,這樣new的時候就不會找不到檔案了。其實就是封裝整個專案的include和require功能。
② $throw 該引數指定當autoload_function無法註冊時,spl_autoload_register()是否應引發異常。
③ 如果為true,那麼spl_autoload_register()將在自動載入到檔案前面,而不時在它後面。
用法
那麼有了這個函式之後向這樣寫了
function load($class)
{
require "./{$class}.php";
}
spl_autoload_register('load');
if($boy)
{
$class1 = new Class1();
}else{
$class2 = new Class2();
}
程式執行過程如下:
graph LR
A(new一個物件)-->B(找不到物件)
B-->C(馬上要報錯了)
C-->D(spl_autoload_register對我們說來載入我試試)
載入之後我們執行了load這個函式,通過class的拼接,我們完成了載入函式的過程
__autoload()
類的自動載入在前面我們講spl_autoload_register的時候已經和大家講過了。今天我們講另一種
__autoload() 在php7中已經不建議使用了
php的__autoload函式是一個魔術函式,在這個函數出現之前,如果一個php檔案裡引用了100個物件,那麼這個檔案就需要使用include或require引進100個類檔案,這將導致該php檔案無比龐大。於是就有了這個 __autoload函式。
__autoload函式在什麼時候呼叫呢?當php檔案中使用了new關鍵字例項化一個物件時,如果該類沒有在本php檔案中被定義,將會觸發__autoload函式,此時,就可以引進定義該類的php檔案,爾後,就能例項化成功了。(注意:如果需要例項化的物件,在本檔案中已經找到該類的定義的話,就不會觸發__autoload函式)
他和spl_autoload_register的區別就在於
當檔案中同時出現__autoload和spl_autoload_register時,以spl_autoload_register為準
名稱空間
我們先前講過類的自動載入,然後我就在思索。
我們寫程式碼的時候,每在另一個檔案中呼叫其他類時
我們並沒有寫spl_autoload_register
這個方法啊?那我們時怎麼實現的呢?
原理
原來啊,我們php在5.3時引入了名稱空間的概念(這也是為什麼大多數的框架不支援5.3之前的版本原因之一)
名稱空間大家多少還是瞭解的吧:不知道的去牆角面壁思過
名稱空間簡而言之就是一種標識,它的主要目的是解決命名衝突的問題。
就像在日常生活中,有很多姓名相同的人,如何區分這些人呢?那就需要加上一些額外的標識。
把工作單位當成標識似乎不錯,這樣就不用擔心 “撞名” 的尷尬了。
名稱空間分類
- 完全限定名稱空間
- 限定名稱空間
new 成都\徐大帥(); // 限定類名
new \成都\徐大帥(); // 完全限定類名
在當前名稱空間沒有宣告的情況下,限定類名和完全限定類名是等價的。因為如果不指定空間,則預設為全域性(\)。
namespace 美國;
new 成都\徐大帥(); // 美國\成都\徐大帥(實際結果)
new \成都\徐大帥(); // 成都\徐大帥(實際結果)
這個例子展示了在名稱空間下,使用限定類名和完全限定類名的區別。(完全限定類名 = 當前名稱空間 + 限定類名)
/* 匯入名稱空間 */
use 成都\徐大帥;
new 徐大帥(); // 成都\徐大帥(實際結果)
/* 設定別名 */
use 成都\徐大帥 AS CEO;
new CEO(); // 成都\徐大帥(實際結果)
/* 任何情況 */
new \成都\徐大帥();// 成都\徐大帥(實際結果)
使用名稱空間只是讓類名有了字首,不容易發生衝突,系統仍然不會進行自動匯入。
如果不引入檔案,系統會在丟擲 “Class Not Found” 錯誤之前觸發 __autoload()
或者spl_autoload_register
函式,並將限定類名傳入作為引數。
所以上面的例子都是基於你已經將相關檔案手動引入的情況下實現的,否則系統會丟擲 ” Class ‘成都\徐大帥’ not found”。
所以在引入名稱空間以後又引入了自動載入
接下來,我們就在用名稱空間載入我們的 類
一個使用名稱空間自動載入類的小實驗
首先,我們在一個新檔案中定義
//School.php
namespace top;
class School
{
function __construct()
{
echo '這是'.__CLASS__.'類的實現';
}
}
這當然不是重要的,重要的是我們呼叫他的函式。我們在同一個目錄建立一個index.php檔案(不同檔案也行,只要你寫好對映關係)
//index.php
spl_autoload_register(function ($class){
//從我們的 class名稱中找,有沒有對應的路徑
$map = [
'top\\School'=>'./School.php'
];
$file = $map[$class];
//檢視對應的檔案是否存在
if (file_exists($file))
include $file;
});
echo "開始<br/>";
new top\School();
結果
開始
這是top\School類的實現
我們使用了 類名和類地址的對映關係,實現了我們的自動載入。然而這也意味著我們每次新增檔案,就必須去更新我們的對映檔案。在一個大型系統中這樣陣列維持的對映關係無疑很麻煩。那麼有沒有好一點的做法呢?
PSR4 自動載入規範
不知道的童鞋,可以看這裡
PSR4 中文文件
PSR4 的具體解釋
下面摘自上面連結,我覺得此博主已經講得很透徹了
\<NamespaceName>(\<SubNamespaceNames>)*\<ClassName>
PSR-4 規範中必須要有一個頂級名稱空間,它的意義在於表示某一個特殊的目錄(檔案基目錄)。子名稱空間代表的是類檔案相對於檔案基目錄的這一段路徑(相對路徑),類名則與檔名保持一致(注意大小寫的區別)。
舉個例子:在全限定類名 \app\view\news\Index 中,如果 app 代表 C:\Baidu,那麼這個類的路徑則是 C:\Baidu\view\news\Index.php
我們就以解析 \app\view\news\Index 為例,編寫一個簡單的 Demo:
$class = 'app\view\news\Index';
/* 頂級名稱空間路徑對映 */
$vendor_map = array(
'app' => 'C:\Baidu',
);
/* 解析類名為檔案路徑 */
$vendor = substr($class, 0, strpos($class, '\\')); // 取出頂級名稱空間[app]
$vendor_dir = $vendor_map[$vendor]; // 檔案基目錄[C:\Baidu]
$rel_path = dirname(substr($class, strlen($vendor))); // 相對路徑[/view/news]
$file_name = basename($class) . '.php'; // 檔名[Index.php]
/* 輸出檔案所在路徑 */
echo $vendor_dir . $rel_path . DIRECTORY_SEPARATOR . $file_name;
通過這個 Demo 可以看出限定類名轉換為路徑的過程。那麼現在就讓我們用規範的面向物件方式去實現自動載入器吧。
首先我們建立一個檔案 Index.php,它處於 \app\mvc\view\home 目錄中:
namespace app\mvc\view\home;
class Index
{
function __construct()
{
echo '<h1> Welcome To Home </h1>';
}
}
接著我們在建立一個載入類(不需要名稱空間),它處於 \ 目錄中:
class Loader
{
/* 路徑對映 */
public static $vendorMap = array(
'app' => __DIR__ . DIRECTORY_SEPARATOR . 'app',
);
/**
* 自動載入器
*/
public static function autoload($class)
{
$file = self::findFile($class);
if (file_exists($file)) {
self::includeFile($file);
}
}
/**
* 解析檔案路徑
*/
private static function findFile($class)
{
$vendor = substr($class, 0, strpos($class, '\\')); // 頂級名稱空間
$vendorDir = self::$vendorMap[$vendor]; // 檔案基目錄
$filePath = substr($class, strlen($vendor)) . '.php'; // 檔案相對路徑
return strtr($vendorDir . $filePath, '\\', DIRECTORY_SEPARATOR); // 檔案標準路徑
}
/**
* 引入檔案
*/
private static function includeFile($file)
{
if (is_file($file)) {
include $file;
}
}
}
最後,將 Loader 類中的 autoload 註冊到 spl_autoload_register 函式中:
include 'Loader.php'; // 引入載入器
spl_autoload_register('Loader::autoload'); // 註冊自動載入
new \app\mvc\view\home\Index(); // 例項化未引用的類
/**
* 輸出: <h1> Welcome To Home </h1>
*/
示例中的程式碼其實就是 ThinkPHP 自動載入器原始碼的精簡版,它是 ThinkPHP 5 能實現惰性載入的關鍵。
我以後可能會分析一個 一款輕量級的框架 easyphp 的自動載入的原始碼部分。