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 相关文章推荐
Ajax+PHP 边学边练 之二 实例
Nov 24 PHP
第七章 php自定义函数实现代码
Dec 30 PHP
PHP中几个常用的魔术常量
Feb 23 PHP
yii框架表单模型使用及以数组形式提交表单数据示例
Apr 30 PHP
php使用正则表达式进行字符串搜索的方法
Mar 23 PHP
解决php表单重复提交实现方法
Sep 29 PHP
PHP下SSL加密解密、验证、签名方法(很简单)
Jun 28 PHP
Linux安装配置php环境的方法
Jan 14 PHP
php微信公众号开发(2)百度BAE搭建和数据库使用
Dec 15 PHP
php实现文件预览功能
May 23 PHP
浅谈Laravel模板实体转义带来的坑
Oct 22 PHP
laravel添加角色和模糊搜索功能的实现代码
Jun 22 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实现十进制、二进制、八进制和十六进制转换相关函数用法分析
2017/04/25 PHP
动态创建的表格单元格中的事件实现代码
2008/12/30 Javascript
不使用XMLHttpRequest实现异步加载 Iframe和script
2012/10/29 Javascript
无缝滚动改进版支持上下左右滚动(封装成函数)
2012/12/04 Javascript
jquery通过a标签删除table中的一行的代码
2013/12/02 Javascript
一些老手都不一定知道的JavaScript技巧
2014/05/06 Javascript
JavaScript获取指定元素位置的方法
2015/04/08 Javascript
jQuery数据检索中根据关键字快速定位GridView指定行的实现方法
2016/06/08 Javascript
轻松理解JavaScript之AJAX
2017/03/15 Javascript
jQuery+Ajax实现用户名重名实时检测
2017/06/01 jQuery
jQuery实现html双向绑定功能示例
2017/10/09 jQuery
纯html+css+javascript实现楼层跳跃式的页面布局(实例代码)
2017/10/25 Javascript
Vue侧滑菜单组件——DrawerLayout
2017/12/18 Javascript
javascript头像上传代码实例
2019/09/28 Javascript
node crawler如何添加promise支持
2020/02/01 Javascript
JavaScript实现旋转木马轮播图
2020/03/16 Javascript
python实现将汉字转换成汉语拼音的库
2015/05/05 Python
小小聊天室Python代码实现
2016/08/17 Python
详解Python装饰器由浅入深
2016/12/09 Python
python使用Tesseract库识别验证
2018/03/21 Python
对numpy中的transpose和swapaxes函数详解
2018/08/02 Python
Python 二叉树的层序建立与三种遍历实现详解
2019/07/29 Python
Pycharm中安装Pygal并使用Pygal模拟掷骰子(推荐)
2020/04/08 Python
Python用Jira库来操作Jira
2020/12/28 Python
解决Pycharm 运行后没有输出的问题
2021/02/05 Python
CSS3中的@keyframes关键帧动画的选择器绑定
2016/06/13 HTML / CSS
英国街头品牌:Bee Inspired Clothing
2018/02/12 全球购物
香港最新科技与优质家居产品购物网站:J SELECT
2018/08/21 全球购物
毕业生找工作的求职信范文
2013/12/24 职场文书
《乌塔》教学反思
2014/02/17 职场文书
公务员政审个人鉴定
2014/02/25 职场文书
初中生庆国庆演讲稿范文2014
2014/09/25 职场文书
在教室放鞭炮的检讨书
2014/09/28 职场文书
校园安全主题班会
2015/08/12 职场文书
高考满分作文赏析(2篇)
2019/08/12 职场文书
Python深度学习之Pytorch初步使用
2021/05/20 Python