Once Again: Do Not Use (include/require)_once

Recently we had several discussions about whether apc.include_once_override should stay or go. This APC configuration option has never really been implemented well. Here I would like to share with you the reason behind this problem, and what it teaches us. The discussion about whether to use include or include_once (which below also covers require_once) has gone on for a long time, and the conclusion has always been the same: use include as much as possible rather than include_once. The most common reason given in the past was that include_once has to scan the list of already-loaded files to confirm whether the file exists before loading it. That reason is indeed correct, but what I want to talk about today is a different one. As we know, for PHP to determine whether a file has been loaded, it needs to obtain the file’s opened_path, which means, for example:

1
2
3
4
<?php
set\_include\_path("/tmp/:/tmp2/");
include_once("2.php");
?>


When PHP sees include_once “2.php”, it does not know what the actual path of this file is, so it cannot tell from the list of loaded files whether it has already been loaded. Therefore, in the implementation of include_once, PHP first tries to resolve the real path of the file (for ordinary files this resolution is merely something like checking getcwd and the file path, so if it is a relative path, it generally will not succeed). If the resolution succeeds, it looks in EG(include_files); if the entry exists, that means the file has already been included, so it returns; otherwise it opens the file, thereby obtaining the file’s opened_path. In the example above, for instance, the file lives at “/tmp2/2.php”. Then, once the opened_path has been obtained, PHP searches the list of loaded files to see whether it has already been included. If it has not been included, then it compiles directly and no longer needs to open the file.

  1. Try to resolve the absolute path of the file. If it resolves successfully, check EG(included_files): if it exists, return; otherwise continue
  2. Open the file and get the file’s opened path
  3. Look up the opened path in EG(included_files): if it exists, return; otherwise continue
  4. Compile the file (compile_file)

In most cases this is not a problem, but the problem arises when you use APC… When APC is in use, APC hijacks the compile_file pointer that compiles files, so that the compiled result can be taken straight from the cache, avoiding opening the actual file and avoiding the system call to open. However, when you use include_once in your code, before compile_file PHP has already tried to open the file, and only then does it enter the compile file hijacked by APC, so an extra open operation is produced. It is precisely to solve this problem that APC introduced include_once_override. When include_once_override is enabled, APC hijacks PHP’s ZEND_INCLUDE_OR_EVAL opcode handler, uses stat to determine the absolute path of the file, and if it finds the file has not been loaded, rewrites the opcode to include — a tricky solution. Unfortunately, as I said, APC’s include_once_override has never been implemented well and causes some undefined problems, for example:

1
2
3
4
5
6
7
8
9
<?php
set\_include\_path("/tmp");
function a($arg = array()) {
    include_once("b.php");
}

a();
a();
?>

Then, our b.php is placed at “/tmp/b.php” with the following contents:
1
2
3
<?php
  class B {}
?>

So with apc.include_once_override enabled, consecutive accesses will produce the following error:

Fatal error - include() : Cannot redeclare class b

(Postscript 2012-09-15 02:07:20: I have already fixed this APC bug: #63070) Setting aside these technical factors, I have always believed that we should use include rather than include_once, because we are perfectly capable of planning things ourselves so that a file is loaded only once. You can also rely on autoloading to achieve this. If you use include_once, it only proves that you have no confidence in your own code. So, my advice: stop using include_once