下面由composer教程欄目給大家由淺入深的介紹composer自動加載機制,希望對需要的朋友有所幫助!
前言
由于對于composer自動加載機制的記憶只剩下了”spl_auto???”和”根據命名空間來推導出文件路徑”這兩個了。。。還是殘缺的。。
本想網上收藏一篇詳解,奈何,沒發現符合我覺得的”由淺入深”文章。
所以有了這篇筆記。
以下知識點即將趕來:
1.了解一下spl_autoload_register
2.composer update發生的故事
3.追蹤一下composer的自動加載
正文
1.了解一下spl_autoload_register
首先查一下php官方手冊:
(偷懶可以只看紅色部分即可)
是不是看著一知半解?
來用白話文來翻譯一下:
我們new一個類的話,必須先require或者include類的文件,如果沒有加載進來則會報錯。這產生一個問題:那這樣的話文件的頭部到處都是requies和include,明顯不符合程序員必須”偷懶”尿性。為了不需要require或者include類文件也能正常的new一個類,出現了自動加載機制。spl_autoload_register這個函數就專門干這個事的。
從截圖得知,此函數有三個參數:
參數 | 詳解 |
---|---|
autoload_function | 這里填的是一個***”函數”的名稱***,字符串或者數組,這個函數的功能就是把需要new的文件require或者include盡量,避免new的時候報錯。簡單的說就是要你封裝一個***自動加載文件的函數*** |
throw | 當自動加載的函數無法注冊的時候,是否拋異常 |
prepend | 是否添加函數到函數隊列之首,如果是true則為首,否則尾部 |
來一波代碼,印象深刻一些:
//文件 testClass.php ,即將new的類 class TestClass{ public function __construct() { echo '你已經成功new了我了'; } } //文件autoloadDemo.php文件 spl_autoload_register('autoLoad_function', true, true); function autoLoad_function($class_name){ echo "所有的require或者include文件工作都交給我吧! "; $class_filename = "./{$class_name}.php"; echo "我來加載{$class_filename}文件 "; require_once("./{$class_name}.php"); } $obj_demo = new TestClass();
輸出:
所有的require或者include文件工作都交給我吧! 我來加載testClass.php文件 你已經成功new了我了
明白了這個加載的原理,看下文就順利多了。
2.composer update發生的故事
將自動加載之前,必須要先說一下composer update,這里頭承載了自動加載的前提。
composer項目都包含一個composer.json的配置文件。
這里頭有一個關鍵的字段”autoload”,包含psr-4和files兩個字段。
psr-4:說明是基于psr-4規范的類庫,都支持自動加載,只要在后面的對象中以**“命名空間:路徑”**的方式寫入自己的類庫信息即可。
files:這就就更直接了,寫入路徑就自動加載。
按照以上配置每回composer update之后呢,都會更新一個很重要的文件:./vender/composer/autoload_psr4.php。
這個文件只做了一件事情:把命名空間和文件路徑對應起來,這樣后續自動加載就有映射根據了。
3.追蹤一下composer的自動加載
composer的故事從唯一的一個require說起:
require '../vendor/autoload.php'
這個腳本執行了一個函數:
ComposerAutoloaderInitd9b31141b114fcbee3cf55d0e97b7f87::getLoader()
繼續跟getloader函數做了什么?
public static function getLoader() { if (null !== self::$loader) { return self::$loader; } spl_autoload_register(array('ComposerAutoloaderInitd9b31141b114fcbee3cf55d0e97b7f87', 'loadClassLoader'), true, true); self::$loader = $loader = new ComposerAutoloadClassLoader(); spl_autoload_unregister(array('ComposerAutoloaderInitd9b31141b114fcbee3cf55d0e97b7f87', 'loadClassLoader')); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; call_user_func(ComposerAutoloadComposerStaticInitd9b31141b114fcbee3cf55d0e97b7f87::getInitializer($loader)); } else { $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { $loader->set($namespace, $path); } $map = require __DIR__ . '/autoload_psr4.php'; foreach ($map as $namespace => $path) { $loader->setPsr4($namespace, $path); } $classMap = require __DIR__ . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); } } $loader->register(true); if ($useStaticLoader) { $includeFiles = ComposerAutoloadComposerStaticInitd9b31141b114fcbee3cf55d0e97b7f87::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { composerRequired9b31141b114fcbee3cf55d0e97b7f87($fileIdentifier, $file); } return $loader; }
這個函數主要做了兩件事情:
1.將各種存有命名空間和文件映射關系的文件autoload_xxx.php加載了進來,并作了一些處理(比如:setPsr4將相關映射加載了進去,這個留意下,下文會有呼應。)。
2.注冊了函數register
繼續跟蹤register做了什么:
public function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); }
原來調用了spl_autoload_register函數,當類沒加載的時候使用loadClass來加載類。(這個前文講的很清楚了,應該很熟了)
繼續跟蹤loadClass實現:
public function loadClass($class) { if ($file = $this->findFile($class)) { includeFile($file); return true; } }
大概可以看出,是做了文件的include。
繼續跟蹤下是怎么查找文件的,看findFile函數:
public function findFile($class) { // class map lookup if (isset($this->classMap[$class])) { return $this->classMap[$class]; } if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return false; } if (null !== $this->apcuPrefix) { $file = apcu_fetch($this->apcuPrefix.$class, $hit); if ($hit) { return $file; } } $file = $this->findFileWithExtension($class, '.php'); // Search for Hack files if we are running on HHVM if (false === $file && defined('HHVM_VERSION')) { $file = $this->findFileWithExtension($class, '.hh'); } if (null !== $this->apcuPrefix) { apcu_add($this->apcuPrefix.$class, $file); } if (false === $file) { // Remember that this class does not exist. $this->missingClasses[$class] = true; } return $file; }
這個函數做了一件事:就是尋找類從上文的autoload_xxx.php初始化的數據中來尋找映射的文件路徑。
其中這個函數findFileWithExtension,適用于尋找psr-4規范的文件的映射信息的。
繼續跟蹤findFileWithExtension:
private function findFileWithExtension($class, $ext) { // PSR-4 lookup $logicalPathPsr4 = strtr($class, '', DIRECTORY_SEPARATOR) . $ext; $first = $class[0]; if (isset($this->prefixLengthsPsr4[$first])) { $subPath = $class; while (false !== $lastPos = strrpos($subPath, '')) { $subPath = substr($subPath, 0, $lastPos); $search = $subPath.''; if (isset($this->prefixDirsPsr4[$search])) { $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); foreach ($this->prefixDirsPsr4[$search] as $dir) { if (file_exists($file = $dir . $pathEnd)) { return $file; } } } } } // PSR-4 fallback dirs foreach ($this->fallbackDirsPsr4 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { return $file; } } // PSR-0 lookup if (false !== $pos = strrpos($class, '')) { // namespaced class name $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); } else { // PEAR-like class name $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; } if (isset($this->prefixesPsr0[$first])) { foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { if (0 === strpos($class, $prefix)) { foreach ($dirs as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } } } } // PSR-0 fallback dirs foreach ($this->fallbackDirsPsr0 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } // PSR-0 include paths. if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file; } return false; }
這個函數做了件事:將命名空間類這樣的類名,轉換成目錄名/類名.php這樣的路徑,再從前文setPsr4設置的映射信息中尋找映射信息,然后完成返回路徑。
至此composer的自動加載機制結束。