淺析PHP類的自動加載和命名空間

php是使用require(require_once)include(include_once)關鍵字加載類文件。可是在實際的開發工程中咱們基本上不會去使用這些關鍵字去加載類。 由於這樣作會使得代碼的維護至關的困難。實際的開發中咱們會在文件的開始位置用use關鍵字使用類,而後直接new這個類就能夠了. 至於類是怎麼加載的,通常都是框架或者composer去實現的。php

<?php

use Illuminate\Container\Container;

$container = new Container();

自動加載

咱們能夠經過一段僞代碼來模擬一下在類的實例化工程中類是如何工做的laravel

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.phpPerson.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介紹一個叫張三的人 ,他在財務部門工做。咱們能夠這樣描述。app

namespace 財務部門;
 
class 張三
{
    function __construct()
    {
        echo '財務部門的張三';
    }
}

這就是張三的基本資料 , namespace是他的部門標識,class是他的名稱. 這樣你們就能夠知道他是財務部門張三而不是工程部門張三composer

非限定名稱,限定名稱和徹底限定名稱框架

1.非限定名稱,或不包含前綴的類名稱,例如 $comment = new Comment(); 若是當前命名空間是Blog\ArticleComment將被解析爲、\Blog\Article\Comment。若是使用Comment的代碼不包含在任何命名空間中的代碼(全局空間中),則Comment會被解析爲\Comment函數

注意: 若是文件的開頭有使用use關鍵字 use one\two\Comment;Comment會被解析爲 **one\two\Comment**。ui

2.限定名稱,包含前綴的名稱,例如 $comment = new Article\Comment(); 若是當前的命名空間是Blog,則Comment會被解析爲\Blog\Article\Comment。若是使用Comment的代碼不包含在任何命名空間中的代碼(全局空間中),則Comment會被解析爲\Article\Commentspa

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;

微信公衆號-php修煉之路

相關文章
相關標籤/搜索