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

吾爱主题 阅读:192 2021-09-01 15:31:00 评论:0

出现的问题

公司开发使用PHP,技术框架使用Laravel。最近线上出现一个问题,就是上线之后,每次都会出错。查看出错原因,是composer安装的第三方出现class not found。因为这个问题,在线下使用Lumen框架的时候,遇到过,查找问题原因是因为依赖的composer包中composer.json中的”autoload”:{“psr-4”:{}}书写格式问题。解决方法使用命令:composer dump-autoload -o;

虽然知道问题的所在,但是有一个现象比较费解:这个第三方包已经使用很久了,为什么最近才开始报错呢?下面就开始查找出错原因

解决方案

如果确认第三方包已安装,并且正确使用use引用了,尝试执行composer dump-autoload -o

最终结果

因为可能篇幅会比较长,所以这里先说明一下最终问题处理结果:原因还未准确定位到,现推测发布服务器环境问题,但因为发布服务器监控服务较多,不允许进行测试,所以具体环境哪个配置导致的问题,还没有定位到。

下面主要介绍问题解决过程:

?
1 2 3 4 5 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 中

?
1 require __DIR__. '/../bootstrap/autoload.php' ;

然后继续进入 bootstrap/autoload.php 文件

?
1 require __DIR__. '/../vendor/autoload.php' ;

然后继续进入 vendor/autoload.php

?
1 2 3 4 5 // require 自动加载类 require_once __DIR__ . '/composer/autoload_real.php' ;   // 真正返回文件列表的操作 return ComposerAutoloaderInit3f39d071b2e74e04102a9c9b6f221123::getLoader();

进入getLoader()方法中

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 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方法

?
1 2 3 4 5 public function register( $prepend = false) {   // 调用ClassLoader类的loadClass方法   spl_autoload_register( array ( $this , 'loadClass' ), true, $prepend ); }

ClassLoader类的loadClass方法

?
1 2 3 4 5 6 7 8 9 public function loadClass( $class ) {   // 查找文件,如果查找到文件,则加载文件   if ( $file = $this ->findFile( $class )) {   includeFile( $file );     return true;   } }

ClassLoader类的findFile方法

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 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方法

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 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的解决方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/wan353694124/article/details/75453433

可以去百度分享获取分享代码输入这里。
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

【腾讯云】云服务器产品特惠热卖中
搜索
标签列表
    关注我们

    了解等多精彩内容