出現(xiàn)的問題
公司開發(fā)使用PHP,技術(shù)框架使用Laravel。最近線上出現(xiàn)一個(gè)問題,就是上線之后,每次都會出錯(cuò)。查看出錯(cuò)原因,是composer安裝的第三方出現(xiàn)class not found。因?yàn)檫@個(gè)問題,在線下使用Lumen框架的時(shí)候,遇到過,查找問題原因是因?yàn)橐蕾嚨腸omposer包中composer.json中的”autoload”:{“psr-4”:{}}書寫格式問題。解決方法使用命令:composer dump-autoload -o;
雖然知道問題的所在,但是有一個(gè)現(xiàn)象比較費(fèi)解:這個(gè)第三方包已經(jīng)使用很久了,為什么最近才開始報(bào)錯(cuò)呢?下面就開始查找出錯(cuò)原因
解決方案
如果確認(rèn)第三方包已安裝,并且正確使用use引用了,嘗試執(zhí)行composer dump-autoload -o
最終結(jié)果
因?yàn)榭赡芷鶗容^長,所以這里先說明一下最終問題處理結(jié)果:原因還未準(zhǔn)確定位到,現(xiàn)推測發(fā)布服務(wù)器環(huán)境問題,但因?yàn)榘l(fā)布服務(wù)器監(jiān)控服務(wù)較多,不允許進(jìn)行測試,所以具體環(huán)境哪個(gè)配置導(dǎo)致的問題,還沒有定位到。
下面主要介紹問題解決過程:
1. 查看laravel autoload
2. 查看composer源碼;
3. 重新編譯composer打印日志;
4. 分析composer install過程;
5. 查看php artisan optimize源碼
對分析查找問題的過程感興趣的同學(xué)可以繼續(xù)往下看。
問題分析及解決過程
1. 查找class not found原因
分析
既然class not found,確認(rèn)composer包已經(jīng)安裝。那問題就確定在autoload過程
查看源碼
首先自動加載入口 public/index.php 中
require __DIR__.'/../bootstrap/autoload.php';
然后繼續(xù)進(jìn)入 bootstrap/autoload.php 文件
require __DIR__.'/../vendor/autoload.php';
然后繼續(xù)進(jìn)入 vendor/autoload.php
// require 自動加載類
require_once __DIR__ . '/composer/autoload_real.php';
// 真正返回文件列表的操作
return ComposerAutoloaderInit3f39d071b2e74e04102a9c9b6f221123::getLoader();
進(jìn)入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代碼加密并提高執(zhí)行效率的,提高有限,比較雞肋
// 打印了一下,發(fā)現(xiàn)不存在這個(gè)方法,即!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) {
// 程序在這里執(zhí)行
// 引用ComposerStaticInit類
require_once __DIR__ . '/autoload_static.php';
// 調(diào)用ComposerStaticInit類中的getInitializer方法
// 主要作用是使用ComposerStaticInit類中的值初始化上面創(chuàng)建的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);
}
}
// 重點(diǎn)在這個(gè)方法
$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)
{
// 調(diào)用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加載方式,我的理解:是通過將類與對應(yīng)路徑生成一個(gè)對應(yīng)表
// 該方式優(yōu)點(diǎn):加載速度快,相當(dāng)于查詢字典;
// 缺點(diǎn):無法實(shí)現(xiàn)自動加載,添加新類后,需要對應(yīng)維護(hù)class map
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
// $classMapAuthoritative默認(rèn)值為false,流程到目前,沒有設(shè)置過該值
// $missingClasses通過查看該方法最后幾行,發(fā)現(xiàn)作用是記錄自動加載過程中不存在的文件
// 所以這里第一次加載會返回false
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
// APCu 是老牌 PHP 字節(jié)碼和對象緩存,緩存器 APC 的分支(PS:我也是查的,不懂呀~大家感興趣可以自己深研究)
// 經(jīng)測試,$this->apcuPrefix=null
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
// 最后一層方法(保證是最后一個(gè)方法)
$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
// 對路徑中的\轉(zhuǎn)換為文件系統(tǒng)中對應(yīng)路徑分隔符并+后綴,
// 比如wan\test類,最后處理為wan/test.php(linux下)
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
// 獲得類名中第一個(gè)字母,主要用于在ClassLoader中prefixLengthsPsr4快速檢索包,并找到對應(yīng)包前綴長度,后面截取時(shí)使用
// 對比autoload_static.php中的$prefixLengthsPsr4即可明白作用
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
// 從右往左一層層循環(huán)類名中的路徑
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath.'\\';
// 找到對應(yīng)composer包前綴后,取出對應(yīng)路徑,將包前綴截取后,替換成對應(yīng)的目錄路徑,即為class所對應(yīng)文件
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格式錯(cuò)誤
// 比如包名為wan/lib,即composer安裝命令對應(yīng)composer require wan/lib
// 第三方包中autoload psr-4配置為 "psr-4" : { "wan\\" : "src" }
// (**警告:上面是錯(cuò)誤配置,為了舉例說明;正確應(yīng)該是"psr-4" : { "wan\\lib\\" : "src" })
// 最終生成的$prefixLengthsPsr4為{'w' =>array ('wan\\' => 5,),}
// 生成$prefixDirsPsr4為'wan\\' => array (0 => __DIR__ . '/..' . '/wan/lib/src',),
// 對應(yīng)上面代碼,在最后$file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length)
// $file拼接出來的路徑是vendor/wan/lib/src/lib/$className.php,導(dǎo)致最后無法拼接出正確路徑
// 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;
}
總結(jié)
因?yàn)椴檎疫^程比較長,導(dǎo)致篇幅也比較長。所以決定拆分成多篇文章說明。
到這里,通過查找問題,把Laravel框架autoload機(jī)制源碼分析了一遍,也學(xué)會了composer包中對應(yīng)autoload信息中psr-4及classmap信息如何配置。
后續(xù)文章中會通過查看分析composer源碼及php artisan命令源碼,分析為什么本地開發(fā)環(huán)境及測試環(huán)境沒有出現(xiàn)class not found情況
以上這篇Laravel第三方包報(bào)class not found的解決方法就是小編分享給大家的全部內(nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
您可能感興趣的文章:- Laravel 加載第三方類庫的方法
- php使用ZipArchive提示Fatal error: Class ZipArchive not found in的解決方法