php是使用require(require_once)和include(include_once)關鍵字加載類文件。但是在實際的開發工程中我們基本上不會去使用這些關鍵字去加載類。 因為這樣做會使得代碼的維護相當的困難。實際的開發中我們會在文件的開始位置用use關鍵字使用類,然后直接new這個類就可以了. 至於類是怎么加載的,一般都是框架或者composer去實現的。
<?php
use Illuminate\Container\Container;
$container = new Container();
自動加載
我們可以通過一段偽代碼來模擬一下在類的實例化工程中類是如何工作的
function instance($class)
{
// 如果類已加載則返回其實例
if (class_exists($class, false)) {
return new $class();
}
// 查看 autoload 函數是否被用戶定義
if (function_exists('__autoload')) {
__autoload($class); // 最后一次加載類的機會
}
// 再次檢查類是否存在
if (class_exists($class, false)) {
return new $class();
} else { // 系統:我實在沒轍了
throw new Exception('Class Not Found');
}
}
php在語言層面提供了__autoload 魔術方法給用戶來實現自己的自動加載邏輯。當用戶去new一個類的時候,如果該類沒有被加載,php會在拋出錯誤前調用__autoload方法去加載類。下面的例子中的__autoload方法只是簡單的輸出要加載類的名稱, 並沒有去實際的加載對應的類, 所以會拋出錯誤。
<?php
use Illuminate\Container\Container;
$container = new Container();
function __autoload($class)
{
/* 具體處理邏輯 */
echo $class;// 簡單的輸出要加載類的名稱
}
/**
*
運行結果
Illuminate\Container\Container
Fatal error: Uncaught Error: Class 'Illuminate\Container\Container' not found in D:\project\php\laravel_for_ci_cd\test\ClassLoader.php:5
Stack trace:
#0 {main}
thrown in D:\project\php\laravel_for_ci_cd\test\ClassLoader.php on line 5
*/
明白了 __autoload 函數的工作原理之后,我們來用它去實現一個最簡單自動加載。我們會有index.php和Person.php兩個文件在同一個目錄下。
//index.php
<?php
function __autoload($class)
{
// 根據類名確定文件名
$file = './'.$class . '.php';
if (file_exists($file)) {
include $file; // 引入PHP文件
}
}
new Person();
/*---------------------分割線-------------------------------------*/
//Person.php
class Person
{
// 對象實例化時輸出當前類名
function __construct()
{
echo '<h1>' . __CLASS__ . '</h1>';
}
}
/**運行結果
* 輸出 <h1>Person</h1>
*/
命名空間
命名空間並不是什么新鮮的事務,很多語言都早就支持了這個特性(只是叫法不相同),它主要解決的一個問題就是命名沖突! 就好像日常生活中很多人都會重名,我們必須要通過一些標識來區分他們的不同。比如說現在我們要用php介紹一個叫張三的人 ,他在財務部門工作。我們可以這樣描述。
namespace 財務部門;
class 張三
{
function __construct()
{
echo '財務部門的張三';
}
}
這就是張三的基本資料 , namespace是他的部門標識,class是他的名稱. 這樣大家就可以知道他是財務部門的張三而不是工程部門的張三。
非限定名稱,限定名稱和完全限定名稱
1.非限定名稱,或不包含前綴的類名稱,例如 $comment = new Comment(); 如果當前命名空間是Blog\Article,Comment將被解析為、\Blog\Article\Comment。如果使用Comment的代碼不包含在任何命名空間中的代碼(全局空間中),則Comment會被解析為\Comment。
注意: 如果文件的開頭有使用use關鍵字 use one\two\Comment; 則Comment會被解析為 *one\two\Comment*。
2.限定名稱,或包含前綴的名稱,例如 $comment = new Article\Comment(); 如果當前的命名空間是Blog,則Comment會被解析為\Blog\Article\Comment。如果使用Comment的代碼不包含在任何命名空間中的代碼(全局空間中),則Comment會被解析為\Article\Comment。
3.完全限定名稱,或包含了全局前綴操作符的名稱,例如 $comment = new \Article\Comment(); 在這種情況下,Comment總是被解析為\Article\Comment。
spl_autoload
接下來讓我們要在含有命名空間的情況下去實現類的自動加載。我們使用 spl_autoload_register() 函數來實現,這需要你的 PHP 版本號大於 5.12。spl_autoload_register函數的功能就是把傳入的函數(參數可以為回調函數或函數名稱形式)注冊到 SPL __autoload 函數隊列中,並移除系統默認的 __autoload() 函數。一旦調用 spl_autoload_register() 函數,當調用未定義類時,系統就會按順序調用注冊到 spl_autoload_register() 函數的所有函數,而不是自動調用 __autoload() 函數。
現在, 我們來創建一個 Linux 類,它使用 os 作為它的命名空間(建議文件名與類名保持一致):
<?php
namespace os; // 命名空間
class Linux // 類名
{
function __construct()
{
echo '<h1>' . __CLASS__ . '</h1>';
}
}
接着,在同一個目錄下新建一個 index.php文件,使用 spl_autoload_register 以函數回調的方式實現自動加載:
<?php
spl_autoload_register(function ($class) { // class = os\Linux
/* 限定類名路徑映射 */
$class_map = array(
// 限定類名 => 文件路徑
'os\\Linux' => './Linux.php',
);
/* 根據類名確定文件路徑 */
$file = $class_map[$class];
/* 引入相關文件 */
if (file_exists($file)) {
include $file;
}
});
new \os\Linux();
這里我們使用了一個數組去保存類名與文件路徑的關系,這樣當類名傳入時,自動加載器就知道該引入哪個文件去加載這個類了。但是一旦文件多起來的話,映射數組會變得很長,這樣的話維護起來會相當麻煩。如果命名能遵守統一的約定,就可以讓自動加載器自動解析判斷類文件所在的路徑。接下來要介紹的PSR-4 就是一種被廣泛采用的約定方式
PSR-4規范
PSR-4 是關於由文件路徑自動載入對應類的相關規范,規范規定了一個完全限定類名需要具有以下結構:
<頂級命名空間>(<子命名空間>)*<類名>
PSR-4 規范中必須要有一個頂級命名空間,它的意義在於表示某一個特殊的目錄(文件基目錄)。子命名空間代表的是類文件相對於文件基目錄的這一段路徑(相對路徑),類名則與文件名保持一致(注意大小寫的區別)。
舉個例子:在全限定類名 \app\view\news\Index 中,如果 app 代表 C:\Baidu,那么這個類的路徑則是 C:\Baidu\view\news\Index.php.我們就以解析 \app\view\news\Index 為例,編寫一個簡單的 Demo:
<?php
$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;