99久久国产露脸精品麻豆,欧美日韩精品小说,亚洲免费在线美女视频,国产三级中文字幕,91极品国产情侣高潮对白,国产亚洲一区二区三区不卡片,欧美jizz精品欧美性,久久国产精品久久国产片

PHP命名空間自動(dòng)加載原理

袁志蒙 5993次瀏覽

摘要:include() 和 require() 是PHP中引入文件的兩個(gè)原始方法,但如果在項(xiàng)目中大量使用這兩個(gè)函數(shù)的話,代碼既不優(yōu)雅,執(zhí)行效率也很低,而且維護(hù)起來(lái)也相當(dāng)困難,于是自動(dòng)加載由此而生。在使用PHP命名空間之前...

include() 和 require() 是PHP中引入文件的兩個(gè)原始方法,但如果在項(xiàng)目中大量使用這兩個(gè)函數(shù)的話,代碼既不優(yōu)雅,執(zhí)行效率也很低,而且維護(hù)起來(lái)也相當(dāng)困難,于是自動(dòng)加載由此而生。

使用 __autoload 實(shí)現(xiàn)文件自動(dòng)加載

在使用PHP命名空間之前(PHP 5.3以下),為了實(shí)現(xiàn)文件的自動(dòng)加載,大部分都是采用 __autoload() 魔術(shù)方法。當(dāng)需要使用的類沒有被引入時(shí),這個(gè)函數(shù)會(huì)在PHP報(bào)錯(cuò)前被觸發(fā),未定義的類名會(huì)被當(dāng)作參數(shù)傳入。至于函數(shù)具體的邏輯,這需要用戶自己去實(shí)現(xiàn)。

首先創(chuàng)建一個(gè) autoload.php 來(lái)做一個(gè)簡(jiǎn)單的測(cè)試:

// 類未定義時(shí),系統(tǒng)自動(dòng)調(diào)用
function __autoload($class){
    /* 具體處理邏輯 */
    echo $class;// 簡(jiǎn)單的輸出未定義的類名
}

new HelloWorld();

/**
 * 輸出 HelloWorld 與報(bào)錯(cuò)信息
 * Fatal error: Class 'HelloWorld' not found
 */

通過(guò)這個(gè)簡(jiǎn)單的例子可以發(fā)現(xiàn),在類的實(shí)例化過(guò)程中,系統(tǒng)所做的工作大致是這樣的:

/* 模擬系統(tǒng)實(shí)例化過(guò)程 */
function instance($class){
    // 如果類存在則返回其實(shí)例
    if (class_exists($class, false)) {
        return new $class();
    }
    // 查看 autoload 函數(shù)是否被用戶定義
    if (function_exists('__autoload')) {
        __autoload($class); // 最后一次引入的機(jī)會(huì)
    }
    // 再次檢查類是否存在
    if (class_exists($class, false)) {
        return new $class();
    } else { // 系統(tǒng):我實(shí)在沒轍了
        throw new Exception('Class Not Found');
    }
}

__autoload()自動(dòng)加載原理非常簡(jiǎn)單,這里我們只是拋磚引玉,我們主要來(lái)講 命名空間下的 自動(dòng)加載原理。

在PHP 5.3以后引入了命名空間的特性,命名空間簡(jiǎn)而言之就是一種標(biāo)識(shí),它的主要目的是解決命名沖突的問(wèn)題,就像在日常生活中,有很多姓名相同的人,如何區(qū)分這些人呢?那就需要加上一些額外的標(biāo)識(shí)。

把工作單位當(dāng)成標(biāo)識(shí)似乎不錯(cuò),這樣就不用擔(dān)心 “撞名” 的尷尬了。

這里我們來(lái)做一個(gè)小任務(wù),去介紹百度的CEO李彥宏:

namespace 百度;

class 李彥宏
{
    function __construct()
    {
        echo '百度創(chuàng)始人';
    }
}

↑ 這就是李彥宏的基本資料了,namespace 是他的單位標(biāo)識(shí),class 是他的姓名。

命名空間通過(guò)關(guān)鍵字 namespace 來(lái)聲明。如果一個(gè)文件中包含命名空間,它必須在其它所有代碼之前聲明命名空間。

new 百度\李彥宏(); // 限定類名
new \百度\李彥宏(); // 完全限定類名

↑ 在一般情況下,無(wú)論是向別人介紹 "百度 李彥宏" 還是 "百度公司 李彥宏",他們都能夠明白。

在當(dāng)前命名空間沒有聲明的情況下,限定類名和完全限定類名是等價(jià)的。因?yàn)槿绻恢付臻g,則默認(rèn)為全局(\)。

namespace 谷歌;

new 百度\李彥宏(); // 谷歌\百度\李彥宏(實(shí)際結(jié)果)
new \百度\李彥宏(); // 百度\李彥宏(實(shí)際結(jié)果)

↑ 如果你在谷歌公司向他們的員工介紹李彥宏,一定要指明是 "百度公司的李彥宏"。否則他會(huì)認(rèn)為百度是谷歌的一個(gè)部門,而李彥宏只是其中的一位員工而已。

這個(gè)例子展示了在命名空間下,使用限定類名和完全限定類名的區(qū)別。(完全限定類名 = 當(dāng)前命名空間 + 限定類名)

/* 導(dǎo)入命名空間 */
use 百度\李彥宏;
new 李彥宏(); // 百度\李彥宏(實(shí)際結(jié)果)

/* 設(shè)置別名 */
use 百度\李彥宏 AS CEO;
new CEO(); // 百度\李彥宏(實(shí)際結(jié)果)

/* 任何情況 */
new \百度\李彥宏();// 百度\李彥宏(實(shí)際結(jié)果)

↑ 第一種情況是別人已經(jīng)認(rèn)識(shí)李彥宏了,你只需要直接說(shuō)名字,他就能知道你指的是誰(shuí)。第二種情況是李彥宏就是他們的CEO,你直接說(shuō)CEO,他可以立刻反應(yīng)過(guò)來(lái)。

使用命名空間只是讓類名有了前綴,不容易發(fā)生沖突,系統(tǒng)仍然不會(huì)進(jìn)行自動(dòng)導(dǎo)入。

如果不引入文件,系統(tǒng)會(huì)在拋出 "Class Not Found" 錯(cuò)誤之前觸發(fā) spl_autoload_register 函數(shù),并將限定類名傳入作為參數(shù)。

所以上面的例子都是基于你已經(jīng)將相關(guān)文件手動(dòng)引入的情況下實(shí)現(xiàn)的,否則系統(tǒng)會(huì)拋出 " Class '百度\李彥宏' not found"。

使用 spl_autoload_register 實(shí)現(xiàn)文件自動(dòng)加載

接下來(lái)讓我們要在含有命名空間的情況下去實(shí)現(xiàn)自動(dòng)加載,如果此時(shí)你在用 __autoload() 函數(shù)來(lái)實(shí)現(xiàn)的話,是沒有效果的,可能是在命名空間下__autoload不能用吧,這里我們用 spl_autoload_register() 函數(shù)來(lái)實(shí)現(xiàn):

spl_autoload_register 函數(shù)的功能就是把傳入的函數(shù)(參數(shù)可以為回調(diào)函數(shù)或函數(shù)名稱形式)注冊(cè)到 SPL __autoload 函數(shù)隊(duì)列中,并移除系統(tǒng)默認(rèn)的 __autoload() 函數(shù)。

一旦調(diào)用 spl_autoload_register() 函數(shù),當(dāng)調(diào)用未定義類時(shí),系統(tǒng)就會(huì)按順序調(diào)用注冊(cè)到 spl_autoload_register() 函數(shù)的所有函數(shù),而不是自動(dòng)調(diào)用 __autoload() 函數(shù)。

現(xiàn)在,我們來(lái)創(chuàng)建一個(gè) Linux 類,它使用 os 作為它的命名空間(建議文件名與類名保持一致):

namespace os; // 命名空間

class Linux // 類名
{
    function __construct()
    {
        echo '<h1>' . __CLASS__ . '</h1>';
    }
}

接著,在同一個(gè)目錄下新建一個(gè) PHP 文件,使用 spl_autoload_register 以函數(shù)回調(diào)的方式實(shí)現(xiàn)自動(dòng)加載:

spl_autoload_register(function ($class) { // class = os\Linux

    /* 限定類名路徑映射 */
    $class_map = array(
        // 限定類名 => 文件路徑
        'os\\Linux' => './Linux.php',
    );

    /* 根據(jù)類名確定文件名 */
    $file = $class_map[$class];

    /* 引入相關(guān)文件 */
    if (file_exists($file)) {
        include $file;
    }
});

new \os\Linux();

這里我們使用了一個(gè)數(shù)組去保存類名與文件路徑的關(guān)系,這樣當(dāng)類名傳入時(shí),自動(dòng)加載器就知道該引入哪個(gè)文件去加載這個(gè)類了。

但是一旦文件多起來(lái)的話,映射數(shù)組會(huì)變得很長(zhǎng),這樣的話維護(hù)起來(lái)會(huì)相當(dāng)麻煩。如果命名能遵守統(tǒng)一的約定,就可以讓自動(dòng)加載器自動(dòng)解析判斷類文件所在的路徑。接下來(lái)要介紹的PSR-4 就是一種被廣泛采用的約定方式。

PSR-4(上篇文章講過(guò)) 是關(guān)于由文件路徑自動(dòng)載入對(duì)應(yīng)類的相關(guān)規(guī)范,規(guī)范規(guī)定了一個(gè)完全限定類名需要具有以下結(jié)構(gòu):

\<頂級(jí)命名空間>(\<子命名空間>)*\<類名>

如果繼續(xù)拿上面的例子打比方的話,頂級(jí)命名空間相當(dāng)于公司,子命名空間相當(dāng)于職位,類名相當(dāng)于人名。那么李彥宏標(biāo)準(zhǔn)的稱呼為 "百度公司 CEO 李彥宏"。


PSR-4 規(guī)范中必須要有一個(gè)頂級(jí)命名空間,它的意義在于表示某一個(gè)特殊的目錄(文件基目錄)。子命名空間代表的是類文件相對(duì)于文件基目錄的這一段路徑(相對(duì)路徑),類名則與文件名保持一致(注意大小寫的區(qū)別)。

舉個(gè)例子:在全限定類名 \app\view\news\Index 中,如果 app 代表 C:\Baidu,那么這個(gè)類的路徑則是 C:\Baidu\view\news\Index.php

我們就以解析 \app\view\news\Index 為例,編寫一個(gè)簡(jiǎn)單的 Demo:

$class = 'app\view\news\Index';

/* 頂級(jí)命名空間路徑映射 */
$vendor_map = array(
    'app' => 'C:\Baidu',
);

/* 解析類名為文件路徑 */
$vendor = substr($class, 0, strpos($class, '\\')); // 取出頂級(jí)命名空間[app]
$vendor_dir = $vendor_map[$vendor]; // 文件基目錄[C:\Baidu]
$rel_path = dirname(substr($class, strlen($vendor))); // 相對(duì)路徑[\view\news]
$file_name = basename($class) . '.php'; // 文件名[Index.php]

/* 輸出文件所在路徑 */
echo $vendor_dir . $rel_path . DIRECTORY_SEPARATOR . $file_name;  // C:\Baidu\view\news\Index.php

通過(guò)這個(gè) Demo 可以看出限定類名轉(zhuǎn)換為路徑的過(guò)程。那么現(xiàn)在就讓我們用規(guī)范的面向?qū)ο蠓绞饺?shí)現(xiàn)自動(dòng)加載器吧。

首先我們創(chuàng)建一個(gè)文件 Index.php,它處于 \app\mvc\view\home 目錄中:

namespace app\mvc\view\home;

class Index
{
    function __construct()
    {
        echo '<h1> Welcome To YZMPHP </h1>';
    }
}

接著我們?cè)趧?chuàng)建一個(gè)加載類(不需要命名空間),它處于 \ 目錄中:

class Loader
{
    /* 路徑映射 */
    public static $vendorMap = array(
        'app' => __DIR__ . DIRECTORY_SEPARATOR . 'app',
    );

    /**
     * 自動(dòng)加載器
     */
    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, '\\')); // 頂級(jí)命名空間
        $vendorDir = self::$vendorMap[$vendor]; // 文件基目錄
        $filePath = substr($class, strlen($vendor)) . '.php'; // 文件相對(duì)路徑
        return strtr($vendorDir . $filePath, '\\', DIRECTORY_SEPARATOR); // 文件標(biāo)準(zhǔn)路徑
    }

    /**
     * 引入文件
     */
    private static function includeFile($file)
    {
        if (is_file($file)) {
            include $file;
        }
    }
}

最后,將 Loader 類中的 autoload 注冊(cè)到 spl_autoload_register 函數(shù)中:

include 'Loader.php'; // 引入加載器
spl_autoload_register('Loader::autoload'); // 注冊(cè)自動(dòng)加載

new \app\mvc\view\home\Index(); // 實(shí)例化未引用的類

/**
 * 輸出: <h1> Welcome To YZMPHP </h1>
 */

至此,PHP命名空間下的自動(dòng)加載的原理已經(jīng)全部講完了。

隨機(jī)內(nèi)容

表情

共1條評(píng)論
  • 網(wǎng)友評(píng)論:

    不錯(cuò)的文章

    2019-01-02 18:30:12 回復(fù)

    點(diǎn)擊加載