Press "Enter" to skip to content

PHP Internals — The require/include Lookup Order

In large Web projects, include_path is the very foundation of a modular design (of course, there are plenty of autoload-based designs nowadays too, but that doesn't affect what we discuss here). Precisely because of include_path, though, we often run into seemingly "bizarre" problems caused by not finding the right file.
Which gives rise to the following questions:
How does include_path actually work?
If there are multiple include_paths, in what order are they used?
Under what circumstances does include_path not take effect?
Today I'll walk through this question thoroughly, starting with an example.
The directory structure is as follows:

  root
    ├ 1.php
    ├ 3.php
    └ subdir
    	├ 2.php
		└ 3.php

In 1.php:

<?php
ini_set("include_path", ".:path_to_subdir");
require("2.php");
?>

And in 2.php:

<?php
require("3.php");
?>

And the 3.php under the root directory prints "root", while the 3.php under the subdir directory prints "subdir";
Now, here come my questions:
1. When 1.php is run from the root directory, what output do we get?
2. When 1.php in the parent directory is run from subdir, what output do we get?
3. When the current directory is removed from include_path (that is, include_path="path_to_subdir"), what are the outputs for the two questions above?
include_path in PHP
When PHP encounters a require(_once)/include(_once) directive, it first makes the following judgement:

Is the path of the file to be included an absolute path?
 If yes, then include it directly, and stop.
 If not, enter another branch of the logic (after several calls and macro expansion, this lands in _php_stream_fopen_with_path) to look for the file.

Next, inside _php_stream_fopen_with_path, the following judgement is made:

Is the path of the file to be included a relative path (of the form ./file, ../dir/file, referred to below as a "directory-relative path")?
 If yes, then skip the include_path logic entirely and resolve the relative path directly (covered separately later).

It builds a list of candidate directories from include_path together with the path of the currently executing file. For the example earlier in this article, for instance, the candidate list looks like this:

".:path_to_subdir:current_script_dir"

Then, starting from the head of the candidate list, it takes one path at a time according to DEFAULT_DIR_SEPARATOR (":" in the environment of this article), appends the file name to be included to that path, and makes an attempt. If the include succeeds, it returns; otherwise it moves on to the next candidate path.
By now we can answer the 3 questions I raised at the beginning.
1. Because it runs from the root directory, when 1.php includes 2.php the second candidate path in include_path (path_to_subdir) takes effect and finds path_to_subdir/2.php; and when 2.php includes 3.php the current working directory is root, so the first candidate path in include_path, "." (the current working directory), finds the matching file. Hence the output is "root".
2. Same as 1, except that the current path is subdir, so the output is "subdir".
3. Because the current path is no longer in include_path, when running from the root directory and 2.php includes 3.php, path_to_subdir is the one that takes effect, so whether we run from root or from subdir the output will be "subdir".
And if include_path is emptied inside 2.php,

<?php
ini_set("include_path", '');
require("3.php");
?>

then current_script_dir is what takes effect, and at this point current_script_dir is the path of 2.php, so we still get the output "subdir".
Directory-relative paths
When a directory-relative path is used, the base point of the relative path is always the current working directory.
To illustrate how things work with directory-relative paths, let's look at another example. Still the directory structure above, except that 1.php becomes:

<?php
ini_set("include_path", "/");
require("./subdir/2.php");
?>

and 2.php becomes:

<?php
require("./3.php");
?>

If it runs from the root directory, the lookup for 3.php inside 2.php will be done in the relative path of the current directory, so the output is "root". But if 1.php in the parent directory is run from subdir (php -f ../1.php), it will exit with an error because "./subdir/2.php" cannot be found from subdir.
Postscript
1. Because when include_path and relative paths are used, performance is tied to the number of attempts — in the worst case, if you have 10 include_paths, it can take up to 11 retries to find the file to include — so it's best to use an absolute path whenever you can.
2. Because the basedir of a directory-relative path is always the current working directory, if you want to use it, it has to be tied to the actual deployment path, so in practice it is rarely used (there are, of course, modules that accomplish this with the help of chdir).
3. In a modular system design, one should generally obtain the module's deployment path from within the module (dirname(__FILE__); PHP 5.3 and later even provides the __DIR__ constant), and thereby use an absolute path.

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.