Press "Enter" to skip to content

再一次, 不要使用(include/require)_once

最近关于apc.include_once_override的去留, 我们做了几次讨论, 这个APC的配置项一直一来就没有被很好的实现过.
在这里, 我想和大家在此分享下, 这个问题的原因, 以及对我们的一些启示.
关于使用include还是include_once(以下,都包含require_once), 这个讨论很长了, 结论也一直有, 就是尽量使用include, 而不是include_once, 以前最多的理由的是, include_once需要查询一遍已加载的文件列表, 确认是否存在, 然后再加载.
诚然, 这个理由是对的, 不过, 我今天要说的, 是另外一个的原因.
我们知道, PHP去判断一个文件是否被加载, 是需要得到这个文件的opened_path的, 意思是说, 比如:

<?php
set_include_path("/tmp/:/tmp2/");
include_once("2.php");
?>

当PHP看到include_once "2.php"的时候, 他并不知道这个文件的实际路径是什么, 也就无法从已加载的文件列表去判断是否已经加载, 所以在include_once的实现中, 会首先尝试解析这个文件的真实路径(对于普通文件这个解析仅仅类似是检查getcwd和文件路径, 所以如果是相对路径, 一般是不会成功), 如果解析成功, 则查找EG(include_files), 如果存在则说明包含过了, 返回, 否则open这个文件, 从而得到这个文件的opened_path. 比如上面的例子, 这个文件存在于 "/tmp2/2.php".
然后, 得到了这个opened_path以后, PHP去已加载的文件列表去查找, 是否已经包含, 如果没有包含, 那么就直接compile, 不再需要open file了.

1. 尝试解析文件的绝对路径, 如果能解析成功, 则检查EG(included_files), 存在则返回, 不存在继续
2. 打开文件, 得到文件的打开路径(opened path)
3. 拿opened path去EG(included_files)查找, 是否存在, 如果存在则返回, 不存在继续
4. 编译文件(compile_file)

这个在大多数情况下, 不是问题, 然而问题出在当你使用APC的时候...
在使用APC的时候, APC劫持了compile_file这个编译文件的指针, 从而直接从cache中得到编译结果, 避免了对实际文件的open, 避免了对open的system call.
然而, 当你在代码中使用include_once的时候, 在compile_file之前, PHP已经尝试去open file了, 然后才进入被APC劫持的compile file中, 这样一来, 就会产生一次额外的open操作. 而APC正是为了解决这个问题, 引入了include_once_override, 在include_once_override开启的情况下, APC会劫持PHP的ZEND_INCLUDE_OR_EVAL opcode handler, 通过stat来确定文件的绝对路径, 然后如果发现没有被加载, 就改写opcode为include, 做一个tricky解决方案.
但是, 很可惜, 如我所说, APC的include_once_override实现的一直不好, 会有一些未定义的问题, 比如:

<?php
set_include_path("/tmp");
function a($arg = array()) {
    include_once("b.php");
}
a();
a();
?>

然后, 我们的b.php放置在"/tmp/b.php", 内容如下:

<?php
  class B {}
?>

那么在打开apc.include_once_override的情况下, 连续访问就会得到如下错误:

Fatal error - include() : Cannot redeclare class b

(后记 2012-09-15 02:07:20: 这个APC的bug我已经修复: #63070)
排除这些技术因素, 我也一直认为, 我们应该使用include, 而不是include_once, 因为我们完全能做到自己规划, 一个文件只被加载一次. 还可以借助自动加载, 来做到这一点.
你使用include_once, 只能证明, 你对自己的代码没信心.
所以, 建议大家, 不要再使用include_once

94 Comments

  1. Java天堂
    Java天堂 August 20, 2020

    话说,如果是老的代码怎么办,要改成include/require吗

  2. Kavin
    Kavin August 16, 2019

    在include_once进程描述中:为什么我必须去include_files再次找到它?

  3. […] 1、不使用include(require)_once。原因见鸟哥博客。 2、引入没有条件限制必须的文件用require,放在程序的最前面;引入非必须的文件用include。(必须的标准就是文件存在与否决定程序启停) […]

  4. 西顿照明
    西顿照明 May 3, 2018

    现在基本都不用这个了吧

  5. 谈腾
    谈腾 August 12, 2017

    时过境迁,现在都用 composer 自动加载

  6. Gordon
    Gordon June 12, 2017

    請問, 對於現在的opcache, 是否還不建議使用include/require once?
    謝謝.

  7. Anonymous
    Anonymous October 28, 2015

    随着项目的复杂化,开发者进入了一个不断埋坑又填坑的循环。结果是能力没有提高,却花费大量的时间在查找上。看看现在网上的一些所谓开源PHP商城的代码,那个渣的程度已经不能直视了。

  8. cola
    cola October 15, 2015

    APC可以在PHP5.5下跑吗?

  9. RichardXu
    RichardXu July 24, 2015

    接上贴,补充几点:
    1、如果能保证文件只加载一次,那绝对选择 require/inlcude;
    2、如果无法保证文件只被加载一次,优先选择 (require/include)_once,因为定义语句也是要执行的,会给变量分配内存,构造数组等。
    鸟哥强调的是第一点

  10. RichardXu
    RichardXu July 23, 2015

    首先,总体来说,require_once 肯定要比 require 性能好。
    因为 require 某个文件等同于 “编译 + 执行” 这个文件;require_once 避免了对相同文件的重复 “编译” 和 “执行”。
    即使使用APC大大降低了“编译”阶段的消耗,但是“执行”阶段依然每次都会做重复工作。
    其次,鸟哥说的应该是在PHP5.2之前,require_once 的实现机制不健全,只有当参数传的是绝对路径,才会根据路径去确认该文件是否加载过,否则就会 open 这个文件,这显然很不合理。
    PHP5.3之后,开始支持相对路径;
    但即使是这种场景,也不是 require 绝对比 require_once 性能好,而只是对那些定义比较多的PHP文件是这样;执行代码比较多的场景就一定不是这样了。
    综上所述,require_once 从设计思路上来说是要比 require 性能高的;
    具体来说
    PHP5.2之前:
    1、如果使用的是绝对路径,使用 require_once;
    2、如果是相对路径的定义文件,使用了APC,选择 require;没有使用 APC,使用 require_once;
    3、如果是相对路径的偏执行文件,使用 require_once
    PHP5.3之后:
    1、对于定义性的文件,如果用了APC,使用 require_once 性能稍高一些;没有APC,当然是 require_once ;
    2、对于有执行代码的文件,当然是 require_once;

  11. 没落皇族
    没落皇族 April 9, 2015

    差不多就得了,我们的生命可以支持我们做多久的程序开发?无聊不?有限的时间里多陪陪家人吧,至于代码,能跑起来就OK了,纠结一些瓜皮之事,得不偿失!

    • sig
      sig November 14, 2019

      说的有道理

  12. Muhammad
    Muhammad February 8, 2015

    What’s up, this weekend is pleasant in support
    of me, as this time i am reading this great informative article here at my
    house.
    homepage, Orlando,

  13. consatan
    consatan July 20, 2014

    1. 尝试解析文件的绝对路径, 如果能解析成功, 则检查EG(included_files), 存在则返回, 不存在继续
    存在则返回的话,那为什么在function中调用include_once(‘/path/to/file.php’)后会导致外部引入同一个文件时失败呢?
    function a() {
    include_once(‘/path/to/file.php’);
    var_dump($var);
    }
    a(); // output success
    include_once(‘/path/to/file.php’);
    var_dump($var); // output NULL
    “存在则返回”仅仅是标记了下“引入的文件已经存在”然后就不引入了??

  14. Verses
    Verses May 14, 2014

    樓下說的用命名來載入什麼意思? 會比 include好嗎?

  15. 小谈博客
    小谈博客 January 6, 2014

    有了命名空间,是不是不用考虑include ,require了,自动导入啊

  16. soooldier
    soooldier November 28, 2013

    我对鸟哥关于include_once调用过程描述中的第3步不理解:为什么还需要去included_files里查找一次?

  17. soooldier
    soooldier November 28, 2013

    还有鸟哥对于include_once过程描述中的第3步不太理解:为什么还需要再去included_files查找一次?

  18. soooldier
    soooldier November 28, 2013

    我的理解与之相反,如果把重复文件包含的工作交给程序员去做的话避免不了会建一个类似“已加载”的类的集合。单拿判断“已加载”这段代码来说,我觉得由底层做更合适,毕竟程序员自己写一个的话那么最终是要通过各种解析成opcode,这样的效率应该会比底层的C实现低下。而至于APC对include_once(require_once)造成的影响那就另说了,这个属于APC的实现问题,而不应该把问题归结到对include_once(require_once)的使用上。

  19. 上海天气预报
    上海天气预报 September 25, 2013

    一再强调,队友死型不改,对于有洁癖的程序员来说有时真心累 。。

  20. wsq
    wsq July 11, 2013

    也许可以给require_once加上一个参数,限制open file的频率。

  21. xsir317
    xsir317 July 9, 2013

    怎么感觉APC用了好多HACK的手段。。。

  22. roger
    roger June 7, 2013

    其实有时候并不是对自己的代码没有信心,而是在接手别人的代码的时候,为了安全起见,不得已而为之

  23. Khmer Comedy » Somnangblogs I was proposed this internet site by my own cousin. I am not sure whether this post is published by him since nobody in addition know this sort of detailed with regards to my trouble. You’re incredible! Thanks! your write-up about Khmer Humourous » Somnangblogs All the best Lisa Veronica

  24. kknd li
    kknd li April 27, 2013

    之所以有once的主要原因是PHP在早期的开发实践中对开发者的约束很少项目又相对简单。
    当引入了面向对象后虽然有了自动加载的机制但还是依耐开发人员的的实现而且对早期函数式编程并没有改进和支持。对很多开发者来说这不能不说是个负担,实际上现在的PHP已经没有刚开始时的便捷轻便的特点开始变得复杂而有点脆弱。随着项目的复杂化对开发者的要求就进入一个陡峭提高的过程,当项目在你眼里慢慢走向靠拼凑来维持的时候,当你周围的人靠_once来保证不重复加载你又无能为力的时候不得不说对这种语言充满的是怎么样的感觉。

  25. […] require和require_once的区别,require包含一次文件,如果在后面的编程中 再次包含同一个文件,PHP还是会去查找文件,解析文件,执行文件,require_once 包含一次文件,在后面编程中如果再包含已经require_once的文件则会被忽略,不会再查找文件而是直接到已包含的内存信息中读取. 个人觉得在全局性质的地方一般可以使用require_once,较个性化的地方使用require,全局性的文件在多人开发的过程中可能会被包含多次,个人个性化的地方一般使用require,自己的程序 一般人都知道自己包含了哪些文件吧.php牛人laruence 推荐在使用apc缓存的情况下使用include或require,不推荐 _once操作. http://www.laruence.com/2012/09/12/2765.html […]

  26. Leric
    Leric March 9, 2013

    新的项目都应该用自动加载了,再到处写include只能说明很久没学习了,只是老项目用APC可能要注意点儿

  27. hello
    hello January 28, 2013

    看到这句话比较经典: 你使用include_once, 只能证明, 你对自己的代码没信心.
    顺便山寨一下经典: 你不使用绝对路径做include, 只能证明, 你对自己代码所在的环境不了解.

  28. 华定平
    华定平 January 9, 2013

    APC是什么 本人愚钝

  29. huming17
    huming17 January 7, 2013

    require_once 还是有必要的,当绝对路径加载文件时,查询一遍已加载的文件列表时间很短,而加载PHP文件对象体积很大耗时间时候, 觉得require_once优于require。

  30. […] 原文引用自:http://www.laruence.com/2012/09/12/2765.html 此条目发表在 PHP 分类目录,贴了 include, include_once, require, require_once 标签。将固定链接加入收藏夹。 ← CodeIgniter(CI)发邮件长标题中文乱码解决方案 […]

  31. 甄码农
    甄码农 November 20, 2012

    被震撼了,用了几年的include_once了,没出现过您说的这个问题。 存在的即是合理的吧。

  32. 小川
    小川 October 23, 2012

    技术的确很重要,但是不要把技术神化。

  33. anonymous
    anonymous October 10, 2012

    我也想说,看样子应该是apc的问题,不用apc就行了嘛

  34. zencart
    zencart September 29, 2012

    用 require 代替include 。。

  35. lamp小白
    lamp小白 September 27, 2012

    原来只知道不推荐用once,因为要多做次判断,但看了之后了解的更多了

  36. anru
    anru September 26, 2012

    这个理由实在是太牵强。
    第一个, include 比include_once 快,快多少?几毫秒吧。
    第二个, 这应该是APC的错,关include_once什麽事。
    include_once 使用绝对路径名时比include还快,
    请看这两个讨论,
    http://drupal.org/node/259623,
    http://blog.seeit.org/2010/06/php-the-include-include_once-performance-debate/
    这个是在实际应用的讨论。
    ZendFrame work, drupal, Sugarcrm, WordPress,symfony framework, 这些流行的PHP 应用都采用require_once/include_once
    ” 排除这些技术因素, 我也一直认为, 我们应该使用include, 而不是include_once, 因为我们完全能做到自己规划, 一个文件只被加载一次. 还可以借助自动加载, 来做到这一点”
    对于一个程序员来说,能偷懒,为什麽不偷懒(利用工具提高效率)?要是什麽都要自己规划,干脆自己造CPU得了。

  37. cevin
    cevin September 19, 2012

    我能说我严重同意么。
    千万别用include_once、require_once

  38. stone
    stone September 18, 2012

    呃,刚刚的代码被过滤了。。。
    function myrequire($file)
    {
    static $loaded = array();
    if ( in_array($file, $loaded) ) return;
    $loaded[] = $file;
    return require($file);
    }

  39. stone
    stone September 18, 2012

    一直使用下面的代码来替代require_once

  40. 了了也
    了了也 September 18, 2012

    当项目比较复杂又团队水平参差不齐时,once还是必要的吧。

  41. Kevin.yt90
    Kevin.yt90 September 14, 2012

    收到。以后会注意

  42. 安静
    安静 September 14, 2012

    关于 once 理论上 不会用到 实质上 存在即合理
    因为有很多 作者开源代码 思维 确实磕碜
    比如 phpcms 生成的时候 竟然 不断得去
    foreach($a as $b){
    include_once(“b.php”);
    }
    不得不用 include_once

  43. 海纳百川
    海纳百川 September 13, 2012

    学习啦。虽然不太懂呢。

  44. Chon
    Chon September 13, 2012

    include_once_override没有实现好,不应该成为不使用(include/require)_once, 这是include_once_override的问题呀.
    博主应该只能是: 建议include_once_override和once不要同时使用.
    PHP本身在模块化和命名空间方面支持就不强, 完全靠autoload基本很难解决.
    如果不使用只会让PHP只适用于做一些小站了.

  45. imbiss
    imbiss September 12, 2012

    如果使用Zend autoload呢?

  46. hutushen222
    hutushen222 September 12, 2012

    补充:
    开启apc.include_once_override后,我遇到的问题是:第一次请求有输出;第二次无响应,服务器端终止服务,后台没有错误日志。
    Chrome
    Error 324 (net::ERR_EMPTY_RESPONSE): The server closed the connection without sending any data.

  47. hutushen222
    hutushen222 September 12, 2012

    同样的代码我这里没有看到警告信息。
    PHP 5.3.2-1ubuntu4.17 with Suhosin-Patch (cli) (built: Jun 19 2012 01:35:33)
    APC Version: 3.1.3p1
    apc.enable_cli=On
    apc.include_once_override=On

  48. icecream
    icecream September 12, 2012

    这会造成性能的下降吗?还是无法说服我不用的原因。

  49. Alvind
    Alvind September 12, 2012

    这个有点因噎废食了

  50. 叶随风
    叶随风 September 12, 2012

    我觉得大部分使用这个include_once就是懒,懒得去判断。
    其实,如果一个项目是一个人开发,一个人维护,那么确实可以完全避免使用这个,如果要是一个团多多个人交叉开发,或者一段时间之后,换其他人维护,尤其是维护者的水平高低不同的时候,这个include_once的优势就出现了,毕竟很多较大的、文件较多大一套程序,很多文件都是包来包去的,想知道一个文件是否被包过,其实是很繁琐甚至是很痛苦的。有了这个语句,可就轻松多了。
    因人而异,因地制宜,根据情况酌情选择吧。

  51. wizardmin
    wizardmin September 12, 2012

    先mark下,找个机会测试下
    同意 @江湖大虾仁,很多项目是多人负责,而像yii框架的import(也是数组判断)好像很慢,特别是在循环使用某个类,每次都要遍历下import数组,一般都是用include/require_once解决

  52. littlemiaor
    littlemiaor September 12, 2012

    哈哈 一直不用 once

  53. fc_lamp
    fc_lamp September 12, 2012

    good~~mark了
    鸟哥转载署名了。

  54. Luke
    Luke September 12, 2012

    留名。。。
    受教了。。
    路过。。。

  55. Danger
    Danger September 12, 2012

    学习了,很好。

  56. 江湖大虾仁
    江湖大虾仁 September 12, 2012

    我依然不是非常理解为什么不用。最好的情况当然是通过合理的架构来避免重复加载,如果只是靠自己维护一个数组来判断是否加载过的话一个没有include_once快吧。我觉得 pysche 说得对,有的时候自己没法掌控整个项目的时候只能用include_once这么写。

  57. tiger
    tiger September 12, 2012

    同意修改once的机制或apc的对once的机制.

  58. zzjin
    zzjin September 12, 2012

    鸟哥…第三行的第二个include拼错了….额

    • 雪候鸟
      雪候鸟 September 12, 2012

      @zzjin 哈哈, 已经更正, 我是别字大王….:)

  59. heyli
    heyli September 12, 2012

    然而问题出在当你使用APC的时候
    也就是说我的程序没有使用 apc include_once就没问题?

  60. pysche
    pysche September 12, 2012

    这个对于自己有能力管控的代码是完全没问题的,可是像某些项目,个人觉得就很难。比如Wordpress的插件之类的,各个插件由不同的人开发,水平也不尽相同

  61. maker
    maker September 12, 2012

    学习了,这么靠前.

  62. test
    test September 12, 2012

    这只能说明应该更新 require_once 的机制或者APC。

  63. shiran
    shiran September 12, 2012

    include_once 使用绝对路径,是不是就可以避免举例中那个问题了?

  64. shaukei
    shaukei September 12, 2012

    鸟哥,受教了!

  65. 花生
    花生 September 12, 2012

    看完了,也懂了很多,还是看鸟哥的博客学习的快

  66. haoboys
    haoboys September 12, 2012

    一直不适用include/require_once 看来是明智的

Comments are closed.