thinkphp系列:類的自動加載是如何設計的


 

在使用框架開發時,可以發現框架有很多核心類,卻很少看到顯示的引入某個文件的代碼,這是因為框架都采用了類的自動加載機制,即使用到類時,框架會自動找到該類所在文件的位置並引入該文件。

為了更容易看出代碼思路,下面在說明時,只抽取了相關的主要代碼。

在剖析thinkphp源碼之前,先說說我做的一個項目實現的自動加載思路。
根據文件命名特點來確定文件所在的位置。

入口文件代碼:

//入口文件index.php
require_once('base.php');

if(function_exists('spl_autoload_register')) {
spl_autoload_register(array('Base', 'autoload'));
} else {
function __autoload($class) {
return Base::autoload($class);
}
}

 

//base.php

final class Base{
public static function autoload($class){

$class = strtolower($class);        
if (ucwords(substr($class,0,5)) == 'Cache' && $class != 'cache'){

if (!@include_once(BASE_CORE_CACHE_PATH.DS.substr($class,0,5).'.'.substr($class,5).'.php')){
exit("Class Error: {$class}.isn't exists!");
}
}elseif ($class == 'db'){
if (!@include_once(BASE_CORE_PATH.DS.'db'.DS.strtolower(DBDRIVER).'.php')){
exit("Class Error: {$class}.isn't exists!");
}
}else{

if (!@include_once(BASE_LIB_PATH.DS.$class.'.php')){
exit("Class Error: {$class}.isn't exists!");
}
}
}
}

如代碼所示,所用的類帶Cache時,就從BASE_CORE_CACHE_PATH這里尋找類文件。默認就從BASE_LIB_PATH這里尋找。

 

 

現在再讓我們一起來看看thinkphp框架是怎么做的。

//start.php入口文件
namespace think;

// ThinkPHP 引導文件
// 加載基礎文件
require 'base.php';
// 執行應用
App::run()->send();
//base.php 文件

// 載入Loader類
require 'loader.php';
// 注冊自動加載
\think\Loader::register();
//loader.php文件
namespace think;
class Loader{    
public static function register($autoload = '')
{
spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true);
}

// 自動加載
public static function autoload($class)
{
echo 'enter autoload<br>';
echo $class[0].'<br>';
var_dump($class);
}
}

通過如上流程走下來,可以最終知道框架的自動加載功能實現主要在Loader類的autoload方法處。
以上代碼可以自建一個小項目運行,假如項目名為test,瀏覽器里訪問

http://localhost/test/start.php

可以得到如下結果:

enter autoload
t
string(9) "think\App" Fatal error: Class 'think\App' not found in E:\xampp\htdocs\test\start.php on line 9

從結果可以判斷出,當執行

App::run()->send();

此處代碼時,框架已成功進入實現自動加載機制的方法里去了。

然后就仔細了解下thinkphp框架是怎么引入這個App類所在的文件。
將如上文件代碼更加詳細化,如下所示:

//start.php入口文件
namespace think;
define('DS', DIRECTORY_SEPARATOR);
define('EXT', '.php');
define('LIB_PATH', __DIR__ .DS.'library'.DS);

// ThinkPHP 引導文件
// 加載基礎文件
require 'base.php';
// 執行應用
App::run()->send();


接着新建App類文件,如果成功進入run方法,則表示App類自動加載成功。

<?php
//library/think/App.php
namespace think;
class App
{
public static function run()
{
echo 'enter run<br>';
}
}

loader.php文件修改稍微多些,請看代碼:

<?php
//loader.php文件
namespace think;
class Loader{

// PSR-4
private static $prefixLengthsPsr4 = [];
private static $prefixDirsPsr4 = [];

public static function register($autoload = '')
{
spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true);

// 注冊命名空間定義
self::addNamespace([
'think' => LIB_PATH . 'think' . DS,
'behavior' => LIB_PATH . 'behavior' . DS,
'traits' => LIB_PATH . 'traits' . DS,
]);

var_dump(self::$prefixLengthsPsr4);
}

// 自動加載
public static function autoload($class)
{
echo 'enter autoload<br>';
// echo $class[0].'<br>';
// var_dump($class);

if ($file = self::findFile($class)) {
include($file);
return true;
}
}

/**
* 查找文件
* @param $class
* @return bool
*/
private static function findFile($class)
{
// 查找 PSR-4
$logicalPathPsr4 = strtr($class, '\\', DS) . EXT;

$first = $class[0];
if (isset(self::$prefixLengthsPsr4[$first])) {
foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) {
if (0 === strpos($class, $prefix)) {
foreach (self::$prefixDirsPsr4[$prefix] as $dir) {
if (is_file($file = $dir . DS . substr($logicalPathPsr4, $length))) {
return $file;
}
}
}
}
}
}


// 注冊命名空間
public static function addNamespace($namespace, $path = '')
{
if (is_array($namespace)) {
foreach ($namespace as $prefix => $paths) {
self::addPsr4($prefix . '\\', rtrim($paths, DS), true);
}
} else {
self::addPsr4($namespace . '\\', rtrim($path, DS), true);
}
}

// 添加Psr4空間
private static function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {

} elseif (!isset(self::$prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
self::$prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
self::$prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {

} else {

}
}

}


base.php文件內容不變。
現在我們梳理一下代碼執行順序(從上往下依次執行):

---\think\Loader::register()
---spl_autoload_register('think\\Loader::autoload')
---\think\Loader::addNamespace()
---\think\Loader::addPsr4()
---\think\Loader::autoload()
---\think\Loader::findFile()
---App::run()

其中addNamespace里addPsr4方法將部分命名空間對應的實際目錄存儲進了static數組變量中。
打印$prefixLengthsPsr4和$prefixDirsPsr4這兩個變量的內容,得到如下所示:

array(2) {
  ["t"]=>
  array(2) {
    ["think\"]=>int(6)
    ["traits\"]=>int(7)
  }
  ["b"]=>
  array(1) {
    ["behavior\"]=>int(9)
  }
}
array(3) {
  ["think\"]=>
  array(1) {
    [0]=>
string(34) "E:\xampp\htdocs\test\library\think"
  }
  ["behavior\"]=>
  array(1) {
    [0]=>
    string(37) "E:\xampp\htdocs\test\library\behavior"
  }
  ["traits\"]=>
  array(1) {
    [0]=>
    string(35) "E:\xampp\htdocs\test\library\traits"
  }
}

然后到了autoload里findFile這步,分析如下:

//之前測試autoload方法,得知$class = 'think\App'; $class[0] = 't';

//將$class字符串里反斜杠替換成文件分隔符,
//再接上文件后綴,變成'think/App.php'
//注意:$class變量里仍然是命名空間分隔符
$logicalPathPsr4 = strtr($class, '\\', DS) . EXT;

$first = $class[0];
//$class = 'think\App';
//$prefix = 'think\';
if (isset(self::$prefixLengthsPsr4[$first])) {
foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) {
if (0 === strpos($class, $prefix)) {
foreach (self::$prefixDirsPsr4[$prefix] as $dir) {
//E:\xampp\htdocs\test\library\think路徑前綴部分 + 文件分隔符
//+ think/App.php截取掉think/后余留下來的App.php
if (is_file($file = $dir . DS . substr($logicalPathPsr4, $length))) {
return $file;
}
}
}
}
}

這里注意2點:
1.這里prefix變量里反斜杠符號是在執行下面代碼時加進去的。

self::addPsr4($prefix . '\\', rtrim($paths, DS), true);

加反斜杠的目的,是更加精確的確定命名空間,代表命名空間其中一段的終止。按如上代碼邏輯,可防止與thinkme類似以think為前綴的命名空間發生混淆。

2.根據如上梳理總結尋找文件的主要思想:

文件位置 = 自定義命名空間對應的文件夾位置 + 文件分隔符 + 截取掉類的命名空間而余留下來的類名 + 文件后綴


以上梳理流程大大簡化了thinkphp框架的自動加載機制,只選取了其中一種情況來進行剖析說明的。
日后有更多的理解,將會在此處進一步更新。

文中代碼在github上有備份,歡迎下載。鏈接地址:https://github.com/bingodawson/tpautoload.git


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM