Laravel第三⽅包报classnotfound的解决⽅法
出现的问题
公司开发使⽤PHP,技术框架使⽤Laravel。最近线上出现⼀个问题,就是上线之后,每次都会出错。查看出错原因,是composer安装的第三⽅出现class not found。因为这个问题,在线下使⽤Lumen框架的时候,遇到过,查问题原因是因为依赖的composer包中composer.json中的”autoload”:{“psr-4”:{}}书写格式问题。解决⽅法使⽤命令:composer dump-autoload -o;
虽然知道问题的所在,但是有⼀个现象⽐较费解:这个第三⽅包已经使⽤很久了,为什么最近才开始报错呢?下⾯就开始查出错原
因
laravel框架下载解决⽅案
如果确认第三⽅包已安装,并且正确使⽤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的解决⽅法就是⼩编分享给⼤家的全部内容了,希望能给⼤家⼀个参考,也希望⼤家多多⽀持。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论