经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » PHP » 查看文章
Laravel第三方包报class not found的解决方法
来源:jb51  时间:2019/10/14 9:38:50  对本文有异议

出现的问题

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

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

解决方案

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

最终结果

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

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

  1. 1. 查看laravel autoload
  2. 2. 查看composer源码;
  3. 3. 重新编译composer打印日志;
  4. 4. 分析composer install过程;
  5. 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. // require 自动加载类
  2. require_once __DIR__ . '/composer/autoload_real.php';
  3.  
  4. // 真正返回文件列表的操作
  5. return ComposerAutoloaderInit3f39d071b2e74e04102a9c9b6f221123::getLoader();

进入getLoader()方法中

  1. public static function getLoader()
  2. {
  3. if (null !== self::$loader) {
  4. return self::$loader;
  5. }
  6.  
  7. // 注册自动加载方法,用来后面初始化ClassLoader类
  8. spl_autoload_register(array('ComposerAutoloaderInit3f39d071b2e74e04102a9c9b6f221123', 'loadClassLoader'), true, true);
  9. // 初始化ClassLoarder
  10. self::$loader = $loader = new \Composer\Autoload\ClassLoader();
  11. spl_autoload_unregister(array('ComposerAutoloaderInit3f39d071b2e74e04102a9c9b6f221123', 'loadClassLoader'));
  12.  
  13. // 这里zend_loader_file_encoded查了一下,解释为:
  14. // Returns TRUE if the current file was encoded with Zend Guard or FALSE otherwise. If FALSE, consider disabling the Guard Loader
  15. // 又查了一下Zend Guard,貌似是php代码加密并提高执行效率的,提高有限,比较鸡肋
  16. // 打印了一下,发现不存在这个方法,即!function_exists('zend_loader_file_encoded')为true
  17. $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
  18. if ($useStaticLoader) {
  19. // 程序在这里执行
  20. // 引用ComposerStaticInit类
  21. require_once __DIR__ . '/autoload_static.php';
  22.  
  23. // 调用ComposerStaticInit类中的getInitializer方法
  24. // 主要作用是使用ComposerStaticInit类中的值初始化上面创建的ComposerAutoloader对象中的prefixLengthsPsr4、prefixDirsPsr4、prefixesPsr0、classMap等值
  25. call_user_func(\Composer\Autoload\ComposerStaticInit3f39d071b2e74e04102a9c9b6f221123::getInitializer($loader));
  26. } else {
  27. $map = require __DIR__ . '/autoload_namespaces.php';
  28. foreach ($map as $namespace => $path) {
  29. $loader->set($namespace, $path);
  30. }
  31.  
  32. $map = require __DIR__ . '/autoload_psr4.php';
  33. foreach ($map as $namespace => $path) {
  34. $loader->setPsr4($namespace, $path);
  35. }
  36.  
  37. $classMap = require __DIR__ . '/autoload_classmap.php';
  38. if ($classMap) {
  39. $loader->addClassMap($classMap);
  40. }
  41. }
  42.  
  43. // 重点在这个方法
  44. $loader->register(true);
  45.  
  46. if ($useStaticLoader) {
  47. $includeFiles = Composer\Autoload\ComposerStaticInit3f39d071b2e74e04102a9c9b6f221123::$files;
  48. } else {
  49. $includeFiles = require __DIR__ . '/autoload_files.php';
  50. }
  51. foreach ($includeFiles as $fileIdentifier => $file) {
  52. composerRequire3f39d071b2e74e04102a9c9b6f221123($fileIdentifier, $file);
  53. }
  54.  
  55. return $loader;
  56. }

ClassLoader的register方法

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

ClassLoader类的loadClass方法

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

ClassLoader类的findFile方法

  1. public function findFile($class)
  2. {
  3. // class map lookup
  4. // class map加载方式,我的理解:是通过将类与对应路径生成一个对应表
  5. // 该方式优点:加载速度快,相当于查询字典;
  6. // 缺点:无法实现自动加载,添加新类后,需要对应维护class map
  7. if (isset($this->classMap[$class])) {
  8. return $this->classMap[$class];
  9. }
  10.  
  11. // $classMapAuthoritative默认值为false,流程到目前,没有设置过该值
  12. // $missingClasses通过查看该方法最后几行,发现作用是记录自动加载过程中不存在的文件
  13. // 所以这里第一次加载会返回false
  14. if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
  15. return false;
  16. }
  17.  
  18. // APCu 是老牌 PHP 字节码和对象缓存,缓存器 APC 的分支(PS:我也是查的,不懂呀~大家感兴趣可以自己深研究)
  19. // 经测试,$this->apcuPrefix=null
  20. if (null !== $this->apcuPrefix) {
  21. $file = apcu_fetch($this->apcuPrefix.$class, $hit);
  22. if ($hit) {
  23. return $file;
  24. }
  25. }
  26.  
  27. // 最后一层方法(保证是最后一个方法)
  28. $file = $this->findFileWithExtension($class, '.php');
  29.  
  30. // Search for Hack files if we are running on HHVM
  31. if (false === $file && defined('HHVM_VERSION')) {
  32. $file = $this->findFileWithExtension($class, '.hh');
  33. }
  34.  
  35. if (null !== $this->apcuPrefix) {
  36. apcu_add($this->apcuPrefix.$class, $file);
  37. }
  38.  
  39. // 记录无法找到的类,方便再次加载直接返回
  40. if (false === $file) {
  41. // Remember that this class does not exist.
  42. $this->missingClasses[$class] = true;
  43. }
  44.  
  45. return $file;
  46. }

ClassLoader类中findFileWithExtension方法

  1. private function findFileWithExtension($class, $ext)
  2. {
  3. // 终于看到加载psr-4了
  4. // PSR-4 lookup
  5. // 对路径中的\转换为文件系统中对应路径分隔符并+后缀,
  6. // 比如wan\test类,最后处理为wan/test.php(linux下)
  7. $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
  8.  
  9. // 获得类名中第一个字母,主要用于在ClassLoader中prefixLengthsPsr4快速检索包,并找到对应包前缀长度,后面截取时使用
  10. // 对比autoload_static.php中的$prefixLengthsPsr4即可明白作用
  11. $first = $class[0];
  12. if (isset($this->prefixLengthsPsr4[$first])) {
  13. $subPath = $class;
  14. while (false !== $lastPos = strrpos($subPath, '\\')) {
  15. // 从右往左一层层循环类名中的路径
  16. $subPath = substr($subPath, 0, $lastPos);
  17. $search = $subPath.'\\';
  18. // 找到对应composer包前缀后,取出对应路径,将包前缀截取后,替换成对应的目录路径,即为class所对应文件
  19. if (isset($this->prefixDirsPsr4[$search])) {
  20. foreach ($this->prefixDirsPsr4[$search] as $dir) {
  21. $length = $this->prefixLengthsPsr4[$first][$search];
  22. if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
  23. return $file;
  24. }
  25. }
  26. }
  27. }
  28. }
  29.  
  30. // 到这里psr-4文件就加载完了,后面是psr-0等其他文件加载,这里就不分析了。
  31. // 这里分析一下为什么是第三方包psr-4格式错误
  32. // 比如包名为wan/lib,即composer安装命令对应composer require wan/lib
  33. // 第三方包中autoload psr-4配置为 "psr-4" : { "wan\\" : "src" }
  34. // (**警告:上面是错误配置,为了举例说明;正确应该是"psr-4" : { "wan\\lib\\" : "src" })
  35. // 最终生成的$prefixLengthsPsr4为{'w' =>array ('wan\\' => 5,),}
  36. // 生成$prefixDirsPsr4为'wan\\' => array (0 => __DIR__ . '/..' . '/wan/lib/src',),
  37. // 对应上面代码,在最后$file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length)
  38. // $file拼接出来的路径是vendor/wan/lib/src/lib/$className.php,导致最后无法拼接出正确路径
  39.  
  40. // PSR-4 fallback dirs
  41. foreach ($this->fallbackDirsPsr4 as $dir) {
  42. if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
  43. return $file;
  44. }
  45. }
  46.  
  47. // PSR-0 lookup
  48. if (false !== $pos = strrpos($class, '\\')) {
  49. // namespaced class name
  50. $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
  51. . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
  52. } else {
  53. // PEAR-like class name
  54. $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
  55. }
  56.  
  57. if (isset($this->prefixesPsr0[$first])) {
  58. foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
  59. if (0 === strpos($class, $prefix)) {
  60. foreach ($dirs as $dir) {
  61. if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
  62. return $file;
  63. }
  64. }
  65. }
  66. }
  67. }
  68.  
  69. // PSR-0 fallback dirs
  70. foreach ($this->fallbackDirsPsr0 as $dir) {
  71. if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
  72. return $file;
  73. }
  74. }
  75.  
  76. // PSR-0 include paths.
  77. if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
  78. return $file;
  79. }
  80.  
  81. return false;
  82. }

总结

因为查找过程比较长,导致篇幅也比较长。所以决定拆分成多篇文章说明。

到这里,通过查找问题,把Laravel框架autoload机制源码分析了一遍,也学会了composer包中对应autoload信息中psr-4及classmap信息如何配置。

后续文章中会通过查看分析composer源码及php artisan命令源码,分析为什么本地开发环境及测试环境没有出现class not found情况

以上这篇Laravel第三方包报class not found的解决方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持w3xue。

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号