Laravel第三方包报class not found的解决方法


Posted in PHP onOctober 13, 2019

出现的问题

公司开发使用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的解决方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

PHP 相关文章推荐
PHP与SQL注入攻击[二]
Apr 17 PHP
PHP下escape解码函数的实现方法
Aug 08 PHP
url decode problem 解决方法
Dec 26 PHP
php安全开发 添加随机字符串验证,防止伪造跨站请求
Feb 14 PHP
PHP变量内存分配问题记录整理
Nov 27 PHP
体育彩票排列三组选三算法分享
Mar 07 PHP
ThinkPHP控制器间实现相互调用的方法
Oct 31 PHP
Yii中CGridView关联表搜索排序方法实例详解
Dec 03 PHP
php堆排序实现原理与应用方法
Jan 03 PHP
PHP使用pdo连接access数据库并循环显示数据操作示例
Jun 05 PHP
微信企业转账之入口类分装php代码
Oct 01 PHP
PHP自定义错误处理的方法分析
Dec 19 PHP
php7性能提升的原因详解
Oct 13 #PHP
php抽象方法和普通方法的区别点总结
Oct 13 #PHP
php use和include区别总结
Oct 13 #PHP
阿里对象存储OSS在laravel框架中的使用方法
Oct 13 #PHP
laravel框架 laravel-admin上传图片到oss的方法
Oct 13 #PHP
laravel实现一个上传图片的接口,并建立软链接,访问图片的方法
Oct 12 #PHP
laravel实现上传图片的两种方式小结
Oct 12 #PHP
You might like
php读取csv文件后,uft8 bom导致在页面上显示出现问题的解决方法
2013/08/10 PHP
php中HTTP_REFERER函数用法实例
2014/11/21 PHP
PHP读取zip文件的方法示例
2016/11/17 PHP
phpstudy隐藏index.php的方法
2020/09/21 PHP
初学js 新节点的创建 删除 的步骤
2011/07/04 Javascript
js change,propertychange,input事件小议
2011/12/20 Javascript
关于删除时的提示处理(确定删除吗)
2013/11/03 Javascript
javascript随机之洗牌算法深入分析
2014/06/07 Javascript
js实现浏览器窗口大小被改变时触发事件的方法
2015/02/02 Javascript
jQuery实现的类似淘宝网站搜索框样式代码分享
2015/08/24 Javascript
jQuery同步提交示例代码
2015/12/12 Javascript
基于jQuery实现放大镜特效
2020/10/19 Javascript
JavaScript中windows.open()、windows.close()方法详解
2016/07/28 Javascript
JavaScript设计模式之单例模式详解
2017/06/09 Javascript
nodejs前端模板引擎swig入门详解
2018/05/15 NodeJs
详解JavaScript的内存空间、赋值和深浅拷贝
2019/04/17 Javascript
layui 实现表单和文件上传一起传到后台的例子
2019/09/16 Javascript
json字符串对象转换代码实例
2019/09/28 Javascript
Vue 2.0双向绑定原理的实现方法
2019/10/23 Javascript
解决vue初始化项目时,一直卡在Project description上的问题
2019/10/31 Javascript
[03:56]DOTA2完美大师赛趣味视频之小鸽子和Mineski打台球
2017/11/24 DOTA
简单介绍Python中的JSON模块
2015/04/08 Python
Python 实现数据库(SQL)更新脚本的生成方法
2017/07/09 Python
Python简单读取json文件功能示例
2017/11/30 Python
Python学习笔记之抓取某只基金历史净值数据实战案例
2019/06/03 Python
python中树与树的表示知识点总结
2019/09/14 Python
如何解决django-celery启动后迅速关闭
2019/10/16 Python
python实现mean-shift聚类算法
2020/06/10 Python
python实现凯撒密码、凯撒加解密算法
2020/06/11 Python
浅析图片上传及canvas压缩的流程
2020/06/10 HTML / CSS
Staples加拿大官方网站:办公用品一站式采购
2016/09/25 全球购物
石油大学毕业生自荐信
2014/01/28 职场文书
双十佳事迹材料
2014/01/29 职场文书
2015年全国爱眼日活动方案
2015/05/05 职场文书
Python字符串格式化方式
2022/04/07 Python
前端canvas中物体边框和控制点的实现示例
2022/08/05 Javascript