PHP自動加載下——PSR4

1.先來介紹一下PSR規範

PHP-FIG,它的網站是:www.php-fig.org。就是這個聯盟組織發明和創造了PSR規範,其中自動加載涉及其中兩個規範,一個是PSR0,一個是PSR4, PSR0規範已通過時了,官方有提示,如今主要是用PSR4規範定義自動加載標準。php

2.PRS4簡介

這個 PSR 描述的是經過文件路徑自動載入類的指南;它做爲對 PSR-0 的補充;根據這個 指導如何規範存放文件來自動載入;
術語「類」是一個泛稱;它包含類,接口,traits 以及其餘相似的結構;web

徹底限定類名應該相似以下範例:數組

( )*
徹底限定類名必須有一個頂級命名空間(Vendor Name);
徹底限定類名能夠有多個子命名空間;
徹底限定類名應該有一個終止類名;
下劃線在徹底限定類名中是沒有特殊含義的;
字母在徹底限定類名中能夠是任何大小寫的組合;
全部類名必須以大小寫敏感的方式引用;
app

當從徹底限定類名載入文件時:函數

在徹底限定類名中,連續的一個或幾個子命名空間構成的命名空間前綴(不包括頂級命名空間的分隔符),至少對應着至少一個基礎目錄。
在「命名空間前綴」後的連續子命名空間名稱對應一個「基礎目錄」下的子目錄,其中的命名 空間分隔符表示目錄分隔符。子目錄名稱必須和子命名空間名大小寫匹配;
終止類名對應一個以 .php 結尾的文件。文件名必須和終止類名大小寫匹配;
自動載入器的實現不可拋出任何異常,不可引起任何等級的錯誤;也不該返回值;優化

徹底限定類名 命名空間前綴 基礎路徑 徹底路徑
\Acme\Log\Writer\File_Writer Acme\Log\Write ./acme-log-writer/lib/ ./acme-log-writer/lib/File_Writer.php
\Aura\Web\Response\Status Aura\Web /path/to/aura-web/src/ /path/to/aura-web/src/Response/Status.php
\Symfony\Core\Request Symfony\Core ./vendor/Symfony/Core/ ./vendor/Symfony/Core/Request.php
\Zend\Acl Zend /usr/includes/Zend/ /usr/includes/Zend/Acl.php

你們注意看第二列和第四列,命名空間前綴對應基礎路徑,命名空間前綴以後的子命名空間必須對應代碼目錄(類名必須是PHP文件)網站

3.優化自動加載方法

上一節中封裝自動加載的方法比較簡單,沒法自動加載帶命名空間的類ui

spl_autoload_register(function ($class) {

    // 命名空間前綴
    $prefix = 'Foo\\Bar\\';

    // 命名空間前綴對應的基礎目錄
    $base_dir = __DIR__ . '/src/';

    // 檢查new的類是否有命名空間前綴
    $len = strlen($prefix);
    if (strncmp($prefix, $class, $len) !== 0) {
        return;
    }

    // 獲取去掉命名空間前綴後的類名
    $relative_class = substr($class, $len);

    // 將命名空間的中的分隔符替換爲目錄分隔符,再加上基礎目錄和.php後綴,最終拼接成
    // 文件路徑
    $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';

    // 若是文件存在則require
    if (file_exists($file)) {
        require $file;
    }
});

可是上面的方法只能適用固定的命名空間前綴,不能通用。this

四、再次優化通用自動加載方法

<?php
namespace Example;

/**
 * 下面這個例子實現了一個命名空間前綴對應多個基礎目錄
 *
 * 如今咱們的目錄結構是下面這樣:
 *
 *     /demo/autoload/
 *          controller/
 *             DemoController.php              # Foo\Bar\DemoController
 *             Admin/
 *                 AdminController.php         # Foo\Bar\Admin\AdminController
 *          model/
 *             DemoModel.php                   # Foo\Bar\DemoModel
 *             Admin/
 *                 AdminModel.php              # Foo\Bar\Admin\AdminModel
 *
 * Foo\Bar分別對應基礎路徑 /demo/autoload/controller 和 /demo/autoload/model
 */
class Psr4AutoloaderClass
{
    /**
     * 一個數組,key爲命名空間前綴,值爲基礎路徑
     *
     * @var array
     */
    protected $prefixes = array();

    /**
     * 封裝自動加載函數
     *
     * @return void
     */
    public function register()
    {
        spl_autoload_register(array($this, 'loadClass'));
    }

    /**
     *
     * 添加一個基礎路徑對應一個命名空間前綴
     *
     * @param string $prefix 命名空間前綴.
     * @param string $base_dir 命名空間類文件的基礎路徑
     * @param bool true爲往數組頭部添加元素,false爲往數組尾部添加元素
     * @return void
     */
    public function addNamespace($prefix, $base_dir, $prepend = false)
    {
        // 去掉左邊的\
        $prefix = trim($prefix, '\\') . '\\';

        // 規範基礎路徑
        $base_dir = rtrim($base_dir, DIRECTORY_SEPARATOR) . '/';

        // 初始化數組
        if (isset($this->prefixes[$prefix]) === false) {
            $this->prefixes[$prefix] = array();
        }

        // 將命名空間前綴和基礎路徑存入數組
        if ($prepend) {
            array_unshift($this->prefixes[$prefix], $base_dir);
        } else {
            array_push($this->prefixes[$prefix], $base_dir);
        }
    }

    /**
     * 真正包含文件方法,將給到類名文件包含進來
     *
     * @param string $class 全限定類名(包含命名空間).
     * @return 成功將返回文件路徑,失敗則返回false
     */
    public function loadClass($class)
    {
        $prefix = $class;
        //查找$prefix最後一個\的位置,看看最後一個\以前的字符串是否在$this->prefixes中
        //若是不存在則繼續查詢上一個\的位置,獲取上一個\以前的字符串是否在$this->prefixes中
        //若是循環結束仍是沒有找到則返回false
        while (false !== $pos = strrpos($prefix, '\\')) {
            $prefix = substr($class, 0, $pos + 1);

            $relative_class = substr($class, $pos + 1);

            $mapped_file = $this->loadMappedFile($prefix, $relative_class);
            if ($mapped_file) {
                return $mapped_file;
            }

            //去掉右邊的\
            $prefix = rtrim($prefix, '\\');
        }

        return false;
    }

    /**
     * 若是參數中的$prefix在$this->prefixes中存在,那麼將循環$this->prefixes[$prefix]裏的value(基礎路徑)
     * 以後拼接文件路徑,若是文件存在將文件包含進來
     *
     * @param string $prefix 命名空間前綴.
     * @param string $relative_class 真正的類名(不包含命名空間路徑的類名).
     * @return mixed 包含成功返回文件路徑,不然返回false
     */
    protected function loadMappedFile($prefix, $relative_class)
    {
        // 檢查數組中是否有$prefix這個key
        if (isset($this->prefixes[$prefix]) === false) {
            return false;
        }

        // 將數組中全部的基礎路徑中的文件包含進來
        foreach ($this->prefixes[$prefix] as $base_dir) {

            // 拼接文件絕對路徑
            $file = $base_dir
                . str_replace('\\', '/', $relative_class)
                . '.php';

            // 若是文件存在則包含進來
            if ($this->requireFile($file)) {
                // 返回文件路徑
                return $file;
            }
        }

        // 沒有找到文件
        return false;
    }

    /**
     *若是文件存在則包含進來.
     *
     * @param string $file 文件路徑.
     * @return bool
     */
    protected function requireFile($file)
    {
        if (file_exists($file)) {
            require $file;
            return true;
        }
        return false;
    }
}
相關文章
相關標籤/搜索