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 相关文章推荐
一个改进的UBB类
Oct 09 PHP
php简单实现多字节字符串翻转的方法
Mar 31 PHP
护卫神php套件 php版本升级方法(php5.5.24)
May 10 PHP
PHP CURL 多线程操作代码实例
May 13 PHP
php可扩展的验证类实例(可对邮件、手机号、URL等验证)
Jul 09 PHP
PHP中COOKIES使用示例
Jul 26 PHP
PHP快速推送微信模板消息
Apr 14 PHP
Yii框架实现记录日志到自定义文件的方法
May 23 PHP
thinkPHP5实现数据库添加内容的方法
Oct 25 PHP
浅谈PHP中如何实现Hook机制
Nov 14 PHP
PHP性能测试工具xhprof安装与使用方法详解
Apr 29 PHP
php实现JWT验证的实例教程
Nov 26 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 define()函数以及defined()函数的用法详解
2013/06/05 PHP
php遍历文件夹下的所有文件和子文件夹示例
2014/03/20 PHP
php批量修改表结构实例
2017/05/24 PHP
php类自动装载、链式操作、魔术方法实现代码
2017/07/23 PHP
浅谈PHP array_search 和 in_array 函数效率问题
2019/10/15 PHP
从零开始学习jQuery (十) jQueryUI常用功能实战
2011/02/23 Javascript
javascript针对DOM的应用分析(四)
2012/04/15 Javascript
JQuery中对Select的option项的添加、删除、取值
2013/08/25 Javascript
javascript正则表达式使用replace()替换手机号的方法
2015/01/19 Javascript
jQuery实现下拉框选择图片功能实例
2015/08/08 Javascript
js实现滚动条滚动到某个位置便自动定位某个tr
2021/01/20 Javascript
javascript字符串对象常用api函数小结(连接,替换,分割,转换等)
2016/09/20 Javascript
bootstrap table 表格中增加下拉菜单末行出现滚动条的快速解决方法
2017/01/05 Javascript
深入理解Vue 的条件渲染和列表渲染
2017/09/01 Javascript
vue的token刷新处理的方法
2018/07/17 Javascript
js设计模式之代理模式及订阅发布模式实例详解
2019/08/15 Javascript
layui点击按钮页面会自动刷新的解决方案
2019/10/25 Javascript
Python实现读取Properties配置文件的方法
2018/03/29 Python
Anaconda下安装mysql-python的包实例
2018/06/11 Python
解决Python安装后pip不能用的问题
2018/06/12 Python
python实现电脑自动关机
2018/06/20 Python
对pandas处理json数据的方法详解
2019/02/08 Python
对Django中的权限和分组管理实例讲解
2019/08/16 Python
使用python制作游戏下载进度条的代码(程序说明见注释)
2019/10/24 Python
HTML+CSS3 模仿Windows7 桌面效果
2010/06/17 HTML / CSS
纯css3制作的火影忍者写轮眼开眼至轮回眼及进化过程实例
2014/11/11 HTML / CSS
泰国国际航空公司官网:Thai Airways International
2019/12/04 全球购物
工业学校毕业生自荐书
2014/01/03 职场文书
酒店总经理职务说明书
2014/02/26 职场文书
村长贪污检举信
2014/04/04 职场文书
行政工作试用期自我评价
2014/09/14 职场文书
公务员学习习总书记“三严三实”思想汇报
2014/09/19 职场文书
2014年村委会工作总结
2014/11/24 职场文书
护士个人年终总结
2015/02/13 职场文书
有关三国演义的读书笔记
2015/06/25 职场文书
2016年党员承诺书范文
2016/03/24 职场文书