Laravel第三方包报class not found的解决方法
出现的问题
公司开发使用PHP,技术框架使用Laravel。最近线上出现一个问题,就是上线之后,每次都会出错。查看出错原因,是composer安装的第三方出现classnotfound。因为这个问题,在线下使用Lumen框架的时候,遇到过,查找问题原因是因为依赖的composer包中composer.json中的”autoload”:{“psr-4”:{}}书写格式问题。解决方法使用命令:composerdump-autoload-o;
虽然知道问题的所在,但是有一个现象比较费解:这个第三方包已经使用很久了,为什么最近才开始报错呢?下面就开始查找出错原因
解决方案
如果确认第三方包已安装,并且正确使用use引用了,尝试执行composerdump-autoload-o
最终结果
因为可能篇幅会比较长,所以这里先说明一下最终问题处理结果:原因还未准确定位到,现推测发布服务器环境问题,但因为发布服务器监控服务较多,不允许进行测试,所以具体环境哪个配置导致的问题,还没有定位到。
下面主要介绍问题解决过程:
1.查看laravelautoload 2.查看composer源码; 3.重新编译composer打印日志; 4.分析composerinstall过程; 5.查看phpartisanoptimize源码
对分析查找问题的过程感兴趣的同学可以继续往下看。
问题分析及解决过程
1.查找classnotfound原因
分析
既然classnotfound,确认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'; //真正返回文件列表的操作 returnComposerAutoloaderInit3f39d071b2e74e04102a9c9b6f221123::getLoader();
进入getLoader()方法中
publicstaticfunctiongetLoader() { if(null!==self::$loader){ returnself::$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查了一下,解释为: //ReturnsTRUEifthecurrentfilewasencodedwithZendGuardorFALSEotherwise.IfFALSE,considerdisablingtheGuardLoader //又查了一下ZendGuard,貌似是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($mapas$namespace=>$path){ $loader->set($namespace,$path); } $map=require__DIR__.'/autoload_psr4.php'; foreach($mapas$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($includeFilesas$fileIdentifier=>$file){ composerRequire3f39d071b2e74e04102a9c9b6f221123($fileIdentifier,$file); } return$loader; }
ClassLoader的register方法
publicfunctionregister($prepend=false) { //调用ClassLoader类的loadClass方法 spl_autoload_register(array($this,'loadClass'),true,$prepend); }
ClassLoader类的loadClass方法
publicfunctionloadClass($class) { //查找文件,如果查找到文件,则加载文件 if($file=$this->findFile($class)){ includeFile($file); returntrue; } }
ClassLoader类的findFile方法
publicfunctionfindFile($class) { //classmaplookup //classmap加载方式,我的理解:是通过将类与对应路径生成一个对应表 //该方式优点:加载速度快,相当于查询字典; //缺点:无法实现自动加载,添加新类后,需要对应维护classmap if(isset($this->classMap[$class])){ return$this->classMap[$class]; } //$classMapAuthoritative默认值为false,流程到目前,没有设置过该值 //$missingClasses通过查看该方法最后几行,发现作用是记录自动加载过程中不存在的文件 //所以这里第一次加载会返回false if($this->classMapAuthoritative||isset($this->missingClasses[$class])){ returnfalse; } //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'); //SearchforHackfilesifwearerunningonHHVM if(false===$file&&defined('HHVM_VERSION')){ $file=$this->findFileWithExtension($class,'.hh'); } if(null!==$this->apcuPrefix){ apcu_add($this->apcuPrefix.$class,$file); } //记录无法找到的类,方便再次加载直接返回 if(false===$file){ //Rememberthatthisclassdoesnotexist. $this->missingClasses[$class]=true; } return$file; }
ClassLoader类中findFileWithExtension方法
privatefunctionfindFileWithExtension($class,$ext) { //终于看到加载psr-4了 //PSR-4lookup //对路径中的\转换为文件系统中对应路径分隔符并+后缀, //比如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安装命令对应composerrequirewan/lib //第三方包中autoloadpsr-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-4fallbackdirs foreach($this->fallbackDirsPsr4as$dir){ if(file_exists($file=$dir.DIRECTORY_SEPARATOR.$logicalPathPsr4)){ return$file; } } //PSR-0lookup if(false!==$pos=strrpos($class,'\\')){ //namespacedclassname $logicalPathPsr0=substr($logicalPathPsr4,0,$pos+1) .strtr(substr($logicalPathPsr4,$pos+1),'_',DIRECTORY_SEPARATOR); }else{ //PEAR-likeclassname $logicalPathPsr0=strtr($class,'_',DIRECTORY_SEPARATOR).$ext; } if(isset($this->prefixesPsr0[$first])){ foreach($this->prefixesPsr0[$first]as$prefix=>$dirs){ if(0===strpos($class,$prefix)){ foreach($dirsas$dir){ if(file_exists($file=$dir.DIRECTORY_SEPARATOR.$logicalPathPsr0)){ return$file; } } } } } //PSR-0fallbackdirs foreach($this->fallbackDirsPsr0as$dir){ if(file_exists($file=$dir.DIRECTORY_SEPARATOR.$logicalPathPsr0)){ return$file; } } //PSR-0includepaths. if($this->useIncludePath&&$file=stream_resolve_include_path($logicalPathPsr0)){ return$file; } returnfalse; }
总结
因为查找过程比较长,导致篇幅也比较长。所以决定拆分成多篇文章说明。
到这里,通过查找问题,把Laravel框架autoload机制源码分析了一遍,也学会了composer包中对应autoload信息中psr-4及classmap信息如何配置。
后续文章中会通过查看分析composer源码及phpartisan命令源码,分析为什么本地开发环境及测试环境没有出现classnotfound情况
以上这篇Laravel第三方包报classnotfound的解决方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。