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 相关文章推荐
杏林同学录(九)
Oct 09 PHP
Smarty Foreach 使用说明
Mar 23 PHP
PHP中的array数组类型分析说明
Jul 27 PHP
《PHP编程最快明白》第六讲:Mysql数据库操作
Nov 01 PHP
两级联动select刷新后其值保持不变的实现方法
Jan 27 PHP
php中socket的用法详解
Oct 24 PHP
php递归删除指定文件夹的方法小结
Apr 20 PHP
学习php设计模式 php实现桥梁模式(bridge)
Dec 07 PHP
PHP基于cookie与session统计网站访问量并输出显示的方法
Jan 15 PHP
Symfony2学习笔记之插件格式分析
Mar 17 PHP
修改yii2.0用户登录使用的user表为其它的表实现方法(推荐)
Aug 01 PHP
PHP使用Redis长连接的方法详解
Feb 12 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制作的意见反馈表源码
2007/03/11 PHP
PHP优于Node.js的五大理由分享
2012/09/15 PHP
php删除左端与右端空格的方法
2014/11/29 PHP
PHP查看当前变量类型的方法
2015/07/31 PHP
记录一次排查PHP脚本执行卡住的问题
2016/12/27 PHP
Yii 2.0如何使用页面缓存方法示例
2017/05/23 PHP
javascript数组去重3种方法的性能测试与比较
2013/03/26 Javascript
JavaScript前端开发之实现二进制读写操作
2015/11/04 Javascript
JS组件Bootstrap按钮组与下拉按钮详解
2016/05/10 Javascript
表单中单选框添加选项和移除选项
2016/07/04 Javascript
值得学习的bootstrap fileinput文件上传工具
2016/11/08 Javascript
js实现短信发送倒计时功能(正则验证)
2017/02/10 Javascript
JavaScript队列函数和异步执行详解
2017/06/19 Javascript
浅谈Vue SSR 的 Cookies 问题
2017/11/20 Javascript
微信小程序tabBar模板用法实例分析【附demo源码下载】
2017/11/28 Javascript
Angular实现可删除并计算总金额的购物车功能示例
2017/12/26 Javascript
重新认识vue之事件阻止冒泡的实现
2018/08/02 Javascript
如何在微信小程序中实现Mixins方案
2019/06/20 Javascript
Vue $attrs & inheritAttr实现button禁用效果案例
2020/12/07 Vue.js
[53:13]DOTA2-DPC中国联赛 正赛 DLG vs PHOENIX BO3 第三场 1月18日
2021/03/11 DOTA
跟老齐学Python之编写类之三子类
2014/10/11 Python
Python正则抓取网易新闻的方法示例
2017/04/21 Python
python pytest进阶之xunit fixture详解
2019/06/27 Python
解决pycharm下os.system执行命令返回有中文乱码的问题
2019/07/07 Python
python 类之间的参数传递方式
2019/12/20 Python
python实现电子词典
2020/03/03 Python
阿里健康官方海外旗舰店:阿里健康国际自营
2017/11/24 全球购物
美国折扣地毯销售网站:Rugs.com
2020/03/27 全球购物
城市轨道专业个人求职信范文
2013/09/23 职场文书
法律专业推荐信范文
2013/11/29 职场文书
2014年医院个人工作总结
2014/12/09 职场文书
平安家庭事迹材料
2014/12/20 职场文书
实习单位指导教师评语
2014/12/30 职场文书
英文自荐信范文
2015/03/25 职场文书
python中对列表的删除和添加方法详解
2022/02/24 Python
redis protocol通信协议及使用详解
2022/07/15 Redis