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


最近关于apc.include_once_override的去留, 咱们作了几回讨论, 这个APC的配置项一直一来就没有被很好的实现过.php

在这里, 我想和你们在此分享下, 这个问题的缘由, 以及对咱们的一些启示.html

关于使用include仍是include_once(如下,都包含require_once), 这个讨论很长了, 结论也一直有, 就是尽可能使用include, 而不是include_once, 之前最多的理由的是, include_once须要查询一遍已加载的文件列表, 确认是否存在, 而后再加载.ide

诚然, 这个理由是对的, 不过, 我今天要说的, 是另一个的缘由.ui

咱们知道, PHP去判断一个文件是否被加载, 是须要获得这个文件的opened_path的, 意思是说, 好比:spa

 

<?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”..net

而后, 获得了这个opened_path之后, PHP去已加载的文件列表去查找, 是否已经包含, 若是没有包含, 那么就直接compile, 再也不须要open file了.指针

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

这个在大多数状况下, 不是问题, 然而问题出在当你使用APC的时候…code

在使用APC的时候, APC劫持了compile_file这个编译文件的指针, 从而直接从cache中获得编译结果, 避免了对实际文件的open, 避免了对open的system call.htm

然而, 当你在代码中使用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解决方案.get

可是, 很惋惜, 如我所说, 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