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 MVC模式在网站架构中的实现分析
Mar 04 PHP
php二维数组排序与默认自然排序的方法介绍
Apr 27 PHP
php中判断数组是一维,二维,还是多维的解决方法
May 04 PHP
PHP中读取照片exif信息的方法
Aug 20 PHP
百度实时推送api接口应用示例
Oct 21 PHP
ThinkPHP中RBAC类的四种用法分析
Nov 24 PHP
php+mysqli使用面向对象方式更新数据库实例
Jan 29 PHP
php使用substr()和strpos()联合查找字符串中某一特定字符的方法
May 12 PHP
浅谈php错误提示及查错方法
Jul 14 PHP
PHP简单实现二维数组赋值与遍历功能示例
Oct 19 PHP
用php定义一个数组最简单的方法
Oct 04 PHP
PHP的重载使用魔术方法代码实例详解
Feb 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中使用asort进行中文排序失效的问题处理
2014/08/18 PHP
ThinkPHP中的三大自动简介
2014/08/22 PHP
浅谈关于PHP解决图片无损压缩的问题
2017/09/01 PHP
用JavaScript调用WebService的示例
2008/04/07 Javascript
jquery.combobox中文api和例子,修复了上面的小bug
2011/03/28 Javascript
js bind 函数 使用闭包保存执行上下文
2011/12/26 Javascript
javascript回车完美实现tab切换功能
2014/03/13 Javascript
js数组常见操作及数组与字符串相互转化实例详解
2015/11/10 Javascript
JavaScript转换与解析JSON方法实例详解
2015/11/24 Javascript
AngularJS基础 ng-keyup 指令简单示例
2016/08/02 Javascript
jQuery 特性操作详解及实例代码
2016/09/29 Javascript
使用json来定义函数,在里面可以定义多个函数的实现方法
2016/10/28 Javascript
Javascript中字符串和数字的操作方法整理
2017/01/22 Javascript
使用Vue自定义指令实现Select组件
2018/05/24 Javascript
详解webpack模块加载器兼打包工具
2018/09/11 Javascript
js嵌套的数组扁平化:将多维数组变成一维数组以及push()与concat()区别的讲解
2019/01/19 Javascript
vue.js实现回到顶部动画效果
2019/07/31 Javascript
[15:58]DOTA2国际邀请赛采访专栏:Tongfu.Sansheng&KingJ,DK.rOtk
2013/08/08 DOTA
[49:42]DOTA2上海特级锦标赛主赛事日 - 3 胜者组第二轮#2Secret VS EG第一局
2016/03/04 DOTA
python排序方法实例分析
2015/04/30 Python
Python的Django框架中TEMPLATES项的设置教程
2015/05/29 Python
深入理解Python 关于supper 的 用法和原理
2018/02/28 Python
Python-Tkinter Text输入内容在界面显示的实例
2019/07/12 Python
python实现连连看辅助(图像识别)
2020/03/25 Python
3种python调用其他脚本的方法
2020/01/06 Python
Python调用C语言程序方法解析
2020/07/07 Python
发现世界上最好的珠宝设计师:JewelStreet
2017/12/17 全球购物
Wiggle新西兰:自行车、跑步、游泳
2020/05/06 全球购物
电钳专业个人求职信
2014/01/04 职场文书
校本教研工作制度
2014/01/22 职场文书
教学改革实施方案
2014/03/31 职场文书
先进事迹材料范文
2014/12/29 职场文书
故意伤害罪辩护词
2015/05/21 职场文书
女方家长婚礼致辞
2015/07/27 职场文书
幼儿园心得体会范文
2016/01/21 职场文书
创业计划书之孕婴生活馆
2019/11/11 职场文书