Laravel第三方包報class not found的解決方法
出現的問題
公司開發使用PHP,技術框架使用Laravel。最近線上出現一個問題,就是上線之後,每次都會出錯。查看出錯原因,是composer安裝的第三方出現class not found。因為這個問題,線上下使用Lumen框架的時候,遇到過,查詢問題原因是因為依賴的composer包中composer.json中的”autoload”:{“psr-4”:{}}書寫格式問題。解決方法使用命令:composer dump-autoload -o;
雖然知道問題的所在,但是有一個現象比較費解:這個第三方包已經使用很久了,為什麼最近才開始報錯呢?下面就開始查找出錯原因
解決方案
如果確認第三方包已安裝,並且正確使用use引用了,嘗試執行composer dump-autoload -o
最終結果
因為可能篇幅會比較長,所以這裡先說明一下最終問題處理結果:原因還未準確定位到,現推測釋出伺服器環境問題,但因為釋出伺服器監控服務較多,不允許進行測試,所以具體環境哪個配置導致的問題,還沒有定位到。
下面主要介紹問題解決過程:
1. 檢視laravel autoload 2. 檢視composer原始碼; 3. 重新編譯composer列印日誌; 4. 分析composer install過程; 5. 檢視php artisan optimize原始碼
對分析查詢問題的過程感興趣的同學可以繼續往下看。
問題分析及解決過程
1. 查詢class not found原因
分析
既然class not found,確認composer包已經安裝。那問題就確定在autoload過程
檢視原始碼
首先自動載入入口 public/index.php 中
require __DIR__.'/../bootstrap/autoload.php';
然後繼續進入 bootstrap/autoload.php 檔案
require __DIR__.'/../vendor/autoload.php';
然後繼續進入 vendor/autoload.php
// require 自動載入類 require_once __DIR__ . '/composer/autoload_real.php'; // 真正返回檔案列表的操作 return ComposerAutoloaderInit3f39d071b2e74e04102a9c9b6f221123::getLoader();
進入getLoader()方法中
public static function getLoader() { if (null !== self::$loader) { return self::$loader; } // 註冊自動載入方法,用來後面初始化ClassLoader類 spl_autoload_register(array('ComposerAutoloaderInit3f39d071b2e74e04102a9c9b6f221123', 'loadClassLoader'), true, true); // 初始化ClassLoarder self::$loader = $loader = new \Composer\Autoload\ClassLoader(); spl_autoload_unregister(array('ComposerAutoloaderInit3f39d071b2e74e04102a9c9b6f221123', 'loadClassLoader')); // 這裡zend_loader_file_encoded查了一下,解釋為: // Returns TRUE if the current file was encoded with Zend Guard or FALSE otherwise. If FALSE, consider disabling the Guard Loader // 又查了一下Zend Guard,貌似是php程式碼加密並提高執行效率的,提高有限,比較雞肋 // 列印了一下,發現不存在這個方法,即!function_exists('zend_loader_file_encoded')為true $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { // 程式在這裡執行 // 引用ComposerStaticInit類 require_once __DIR__ . '/autoload_static.php'; // 呼叫ComposerStaticInit類中的getInitializer方法 // 主要作用是使用ComposerStaticInit類中的值初始化上面建立的ComposerAutoloader物件中的prefixLengthsPsr4、prefixDirsPsr4、prefixesPsr0、classMap等值 call_user_func(\Composer\Autoload\ComposerStaticInit3f39d071b2e74e04102a9c9b6f221123::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 = Composer\Autoload\ComposerStaticInit3f39d071b2e74e04102a9c9b6f221123::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { composerRequire3f39d071b2e74e04102a9c9b6f221123($fileIdentifier, $file); } return $loader; }
ClassLoader的register方法
public function register($prepend = false) { // 呼叫ClassLoader類的loadClass方法 spl_autoload_register(array($this, 'loadClass'), true, $prepend); }
ClassLoader類的loadClass方法
public function loadClass($class) { // 查詢檔案,如果查詢到檔案,則載入檔案 if ($file = $this->findFile($class)) { includeFile($file); return true; } }
ClassLoader類的findFile方法
public function findFile($class) { // class map lookup // class map載入方式,我的理解:是通過將類與對應路徑生成一個對應表 // 該方式優點:載入速度快,相當於查詢字典; // 缺點:無法實現自動載入,新增新類後,需要對應維護class map if (isset($this->classMap[$class])) { return $this->classMap[$class]; } // $classMapAuthoritative預設值為false,流程到目前,沒有設定過該值 // $missingClasses通過檢視該方法最後幾行,發現作用是記錄自動載入過程中不存在的檔案 // 所以這裡第一次載入會返回false if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return false; } // APCu 是老牌 PHP 位元組碼和物件快取,快取器 APC 的分支(PS:我也是查的,不懂呀~大家感興趣可以自己深研究) // 經測試,$this->apcuPrefix=null 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; }
ClassLoader類中findFileWithExtension方法
private function findFileWithExtension($class, $ext) { // 終於看到載入psr-4了 // PSR-4 lookup // 對路徑中的\轉換為檔案系統中對應路徑分隔符並+字尾, // 比如wan\test類,最後處理為wan/test.php(linux下) $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; // 獲得類名中第一個字母,主要用於在ClassLoader中prefixLengthsPsr4快速檢索包,並找到對應包字首長度,後面擷取時使用 // 對比autoload_static.php中的$prefixLengthsPsr4即可明白作用 $first = $class[0]; if (isset($this->prefixLengthsPsr4[$first])) { $subPath = $class; while (false !== $lastPos = strrpos($subPath, '\\')) { // 從右往左一層層迴圈類名中的路徑 $subPath = substr($subPath, 0, $lastPos); $search = $subPath.'\\'; // 找到對應composer包字首後,取出對應路徑,將包字首擷取後,替換成對應的目錄路徑,即為class所對應檔案 if (isset($this->prefixDirsPsr4[$search])) { foreach ($this->prefixDirsPsr4[$search] as $dir) { $length = $this->prefixLengthsPsr4[$first][$search]; if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { return $file; } } } } } // 到這裡psr-4檔案就載入完了,後面是psr-0等其他檔案載入,這裡就不分析了。 // 這裡分析一下為什麼是第三方包psr-4格式錯誤 // 比如包名為wan/lib,即composer安裝命令對應composer require wan/lib // 第三方包中autoload psr-4配置為 "psr-4" : { "wan\\" : "src" } // (**警告:上面是錯誤配置,為了舉例說明;正確應該是"psr-4" : { "wan\\lib\\" : "src" }) // 最終生成的$prefixLengthsPsr4為{'w' =>array ('wan\\' => 5,),} // 生成$prefixDirsPsr4為'wan\\' => array (0 => __DIR__ . '/..' . '/wan/lib/src',), // 對應上面程式碼,在最後$file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length) // $file拼接出來的路徑是vendor/wan/lib/src/lib/$className.php,導致最後無法拼接出正確路徑 // 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; }
總結
因為查詢過程比較長,導致篇幅也比較長。所以決定拆分成多篇文章說明。
到這裡,通過查詢問題,把Laravel框架autoload機制原始碼分析了一遍,也學會了composer包中對應autoload資訊中psr-4及classmap資訊如何配置。
後續文章中會通過檢視分析composer原始碼及php artisan命令原始碼,分析為什麼本地開發環境及測試環境沒有出現class not found情況
以上這篇Laravel第三方包報class not found的解決方法就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援碼農教程。