1.先來介紹一下PSR規範
PHP-FIG,它的網站是:www.php-fig.org。就是這個聯盟組織發明和創造了PSR規範,其中自動加載涉及其中兩個規範,一個是PSR0,一個是PSR4, PSR0規範已經過時了,官方有提示,現在主要是用PSR4規範定義自動加載标準。
2.PRS4簡介
這個 PSR 描述的是通過檔案路徑自動載入類的指南;它作為對 PSR-0 的補充;根據這個 指導如何規範存放檔案來自動載入;
術語「類」是一個泛稱;它包含類,接口,traits 以及其他類似的結構;
完全限定類名應該類似如下範例:
()*
完全限定類名必須有一個頂級命名空間(Vendor Name);
完全限定類名可以有多個子命名空間;
完全限定類名應該有一個終止類名;
下劃線在完全限定類名中是沒有特殊含義的;
字母在完全限定類名中可以是任何大小寫的組合;
所有類名必須以大小寫敏感的方式引用;
當從完全限定類名載入檔案時:
在完全限定類名中,連續的一個或幾個子命名空間構成的命名空間字首(不包括頂級命名空間的分隔符),至少對應着至少一個基礎目錄。
在「命名空間字首」後的連續子命名空間名稱對應一個「基礎目錄」下的子目錄,其中的命名 空間分隔符表示目錄分隔符。子目錄名稱必須和子命名空間名大小寫比對;
終止類名對應一個以 .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.優化自動加載方法
上一節中封裝自動加載的方法比較簡單,無法自動加載帶命名空間的類
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;
}
});
但是上面的方法隻能适用固定的命名空間字首,不能通用。
4、再次優化通用自動加載方法
<?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;
}
}