入门 - 进一步了解引入文件

  • 作者:KK

  • 发表日期:2016.8.16


之前为了让读者快速了解PHP并做个例子体会它的运行,所以没太啰嗦去讲一些细节,关于引入文件,我在引入其它PHP脚本这里只是简单地提及了一下用include语句来实现

其实要引入其它PHP脚本运行,还有include_once,requirereuqire_once这三个语句(只有拼写不一样,但调用方式一模一样),本章节一一进行讲解


要点速读:

  • 越来越多经验丰富的PHP工程师(包括PHP核心开发组成员Laruence)都提倡只使用includerequire语句来引入文件,非迫不得已不要用include_oncerequire_once

  • 后缀带_once的语句就是引入过一次这个文件后,再执行一次的话就不会重新引入了

  • require找不到引入文件时会报Fatal Error级别错误并停止运行,include找不到文件时只报WARNING级别错误然后继续运行

  • 网上有好一部分include,require的解读文章存在说明不清晰造成误导的情况(不信你跟着那些文章做试验,至少PHP5.2以上试不出大部分区别)


include_once

有b.php,代码是:

echo '123';

有a.php,代码是:

include_once('b.php');
include_once('b.php');

运行a.php,结果只会输出"123"

include_once比include语句在拼写上就多了后面的_once后缀

它的作用就是:当一个脚本被引入过后,下次再用include_once引入同样的脚本时,它会进行内部检查是否引入过,如果已经引入过的话就不再引入了

所以这样可以确保一个脚本只被引入一次而不被重复引入

而如果a.php的代码是:

include('b.php');
include('b.php');

则输出结果就是"123123",就是一共2个123被输出了,因为b.php被运行了2次

  • 重复include会导致什么问题?

    通常include的文件都是一些公共文件,这些公共文件提供了一些公共函数给各个脚本使用,但在PHP里面,函数是不可以重复定义的,所以如果重复引入一个定义了函数的文件,那么就会报如下错误(无法重复定义函数A):

    Fatal error: Cannot redeclare 函数A()
    

  • 重复include_once一个带有返回值的脚本时,第2次include_once时只会得到返回值1

    b.php:

    return [1,2,3];
    

    a.php:

    print_r(include_once('b.php'));  //输出 array(1,2,3)
    print_r(include_once('b.php'));  //输出 1
    

    为什么第2次引入b.php会得1?

    我认为是第2次引入的时候,检测到已经引入过b.php了,于是不再去引入,所以b.php就没有被运行起来,并且返回了1表示引入成功(虽然没运行,但之前已经引入过了,也算成功,毕竟这个代码的意思就是:只要引入一次,第二次重复引入就返回1我觉得也合理,而不是报错)


require和include的比较

require和include一样是引入文件,但区别只有下面“文件不存在时直接停止运行”这一点,其它基本都是相同的

  • 文件不存在时直接停止运行

    include('yyy.php'); //假设这个yyy.php并不存在
    echo 1111111;
    

    尽管yyy.php不存在并输出了报错信息,但,但是依然会输出"1111111",所以include的特性之一就是就算引入不成功都会继续运行后面的代码


    如果将include换成require就不同了:

    require('yyy.php');
    echo 1111111;
    

    这里只会输出引入yyy.php失败的报错,但"1111111"不会被输出,所以require的特性之一就是引入不成功那后面的代码都不运行了

    在与技术群的网友讨论时网友亦清给了个不错的解析:因为 include 这个词就是包含的意思,把别的东西纳入自己作为自我的一部分,require字面意思是需要


  • 和include一样支持if判断引入

    上网搜索php include require会找到好多比较两者之间区别的文章,有一部分文章描述类似如下的意思:

    require会无条件包含,就算if(1 == 2){ require('b.php'); }这里if不成立,依然会执行require语句

    但是本人实测PHP5.2到7.0版本都是支持带条件require的,不知这些文章讲的是不是5.2以下的版本,至少这个时代我没有任何一份工作和遇到的招聘面试说人家会用PHP5.2以下的版本了,所以5.2以下的版本是不是无条件包含已经无关紧要,相信面试题也不会刁难到这个地步


  • 只会对脚本进行一次解析,下次不会再解析

    这个说法不从C语言角度了解源码可能难以求证,但暂时跟风相信吧(大部分文章都这么说)

    具体的意思是这样的:

    echo 11;
    for($i = 0; $i < 3; $i++){
    	require('b.php');
    }
    echo 33;
    

    如果b.php的内容是echo 22;的话,那么require所做的事就是将b.php的代码合并到这个文件里,于是代码最终是变成了

    echo 11;
    for($i = 0; $i < 3; $i++){
    	echo 22;
    }
    echo 33;
    

    这里for有3次循环,但并不是真的require了3次代码,可以认为工作逻辑是这样的:

    $requireCache = [];
    function require($file){
    	global $requireCache; //static $requireCache 也行,不过后面才讲这个static
    	if(!isset($requireCache[$file])){
    		$requireCache[$file] = file_get_contents($file); //当然,真正的PHP解析引擎背后应该还包含了opcode的转换生成工作,才不是这么几行代码
    	}
    	return $requireCache[$file];		
    }
    

    好了那这块就不废话了,只是顺带提一句,人家说include每次就是重新解析的,所以require的执行效率比include快,经过本人的一些代码实测确实如此

    其实这个是PHP底层实现的优化处理,但对于开发人员来说基本是感知不到的,所以不能列入使用效果的“不同点”里面,咱们只要知道谁高效就好吧,看情况用


  • 和include一样是有return的返回值的

    网上有文章说b.php的代码:

    return [1,2,3];
    

    a.php:

    $data = include('b.php');
    print_r($data);
    

    这样是有数据的,可是如果用require的话,$data会没有数据,因为require没有返回值

    估计又是针对低版本PHP讲的吧,我试了5.2到7.0+都没有出现这样的情况,放心require和include都能返回脚本的return值


require_once

和require的特性一样,只是require_once的时候会先检查之前是不是已经包含过了这个文件,如果已经包含了就不会再包含

而关于重复require_once一个带返回值的文件时也和include_once一样,第2次以后包含会返回1而已


尽量别用once加载

前面要点速读中已经略为提到过,现在正式讲一下:尽量不要使用 include_once 和 require_once 来加载文件

首先基于浅显的观点,我曾经也这么总结过:我们程序员明明可以控制好程序逻辑,让一个程序文件只被加载一次,如果存在重复加载的隐患而导致要防止重复加载,意味着这些重复加载的代码就是不正确的逻辑吧?而我们却要用once语句去包容这个错误,就是对代码的纵容;这其实就像写一个函数在程序周期只调用一次一样,但你却会不小心调用第二次?这就是个BUG吧,为什么不修正这个BUG,而是要靠一些底层的语言特性之类的功能去支撑咱们不小心的重复调用呢?咱们就不能把程序逻辑写优秀点,人为控制它不重复加载吗

可以拍胸口保证的是:我做项目到现在,从来没有控制不了的重复加载,从来不依靠once加载,就这么杠杠的,你也能做到,相信我,放弃once加载吧

更加深入的权威解析请参见PHP开发组核心成员Laruence的文章《再一次, 不要使用(include/require)_once