- 本文地址: https://www.laruence.com/2010/05/04/1450.html
- 转载请注明出处
在大型的Web项目中, include_path是一个模块化设计的根本中的根本(当然,现在也有很多基于autoload的设计, 这个不影响本文的探讨), 但是正是因为include_path, 经常会让我们遇到一些因为没有找到正确的文件而导致的看似"诡异"的问题.
也就有了如下的疑问:
include_path是怎么起作用的?
如果有多个include_path顺序是怎么样的?
什么情况下include_path不起作用?
今天, 我就全面的介绍下这个问题, 先从一个例子开始吧.
如下的目录结构:
root ├ 1.php ├ 3.php └ subdir ├ 2.php └ 3.php
在1.php中:
<?php ini_set("include_path", ".:path_to_subdir"); require("2.php"); ?>
而在2.php中:
<?php require("3.php"); ?>
而在root目录下的3.php打印出"root", 在subdir目录下的3.php打印出"subdir";
现在, 我的问题来了:
1. 当在root目录下运行1.php, 会得到什么输出?
2. 在subdir下运行上一级目录的1.php, 有会得到什么输出?
3. 当取消include_path中的当前目录path(也就是include_path="path_to_subdir"), 上面俩个问题又会是什么输出?
PHP中的include_path
PHP在遇到require(_once)/include(_once)的指令的时候, 首先会做如下的判断:
要包含的文件路径是绝对路径么? 如果是, 则直接包含, 并结束. 如果不是, 进入另外的逻辑(经过多次调用, 宏展开后进入_php_stream_fopen_with_path)寻找此文件.
接下来, 在_php_stream_fopen_with_path中, 会做如下判断:
要包含的文件路径是相对路径么(形如./file, ../dir/file, 以下用"目录相对路径代替")? 如果是, 则跳过include_path的作用逻辑, 直接解析相对路径(随后单独介绍).
会根据include_path,和当前执行文件的path组成一个待选的目录列表, 比如对于文章前面的例子来说, 会形成一个如下的待选列表
".:path_to_subdir:current_script_dir"
然后, 依次从待选列表头部开始, 根据DEFAULT_DIR_SEPARATOR(本文的环境是":")取出待选列表中的一个路径, 然后把要包含的文件名附加在这个路径后面, 进行尝试. 如果成功包含, 则返回, 否则继续下一个待选路径.
到现在为止, 我们已经可以回答我开头提出的3个问题了.
1. 因为在root目录下执行, 所以在1.php中包含2.php的时候, include_path的第二个待选路径起了作用(path_to_subdir), 找到了path_to_subdir/2.php, 而在2.php包含3.php的时候, 当前工作目录是root下, 所以在包含3.php的时候, include_path的第一个待选路径"."(当前工作目录)下就找到的匹配的文件, 所以得到的输出是"root".
2. 同1, 只不过当前的路径是subdir, 所以得到的输出是"subdir".
3. 因为没有了当前路径为include_path, 所以在root目录下运行的时候2.php中包含3.php的时候, 是path_to_subdir起了作用, 所以无论在root还是subdir都将得到"subdir"的输出.
而如果在2.php中清空include_path,
<?php ini_set("include_path", ''); require("3.php"); ?>
那么将会是current_script_dir起作用, 而这个时候current_script_dir是2.php的路径, 所以还是会得到"subdir"的输出.
目录相对路径
在使用目录相对路径的情况下, 相对路径的基点, 永远都是当前工作目录.
为了说明在目录相对路径下的情况,我们再看个列子, 还是上面的目录结构, 只不过1.php变成了:
<?php ini_set("include_path", "/"); require("./subdir/2.php"); ?>
2.php变成了:
<?php require("./3.php"); ?>
如果在root目录下执行, 2.php中寻找3.php将会在当前目录的相对路径下寻找, 所以得到的输出是"root", 而如果是在subdir下执行上一级目录的1.php(php -f ../1.php), 将会因为在subdir下找不到"./subdir/2.php"而异常退出.
后记
1. 因为使用include_path和相对路径的情况下, 性能会和寻找的次数有关, 最坏的情况下, 如果你有10个include_path, 那么最多可能会重试11次才能找到要包含的文件, 所以, 在能使用绝对路径的情况下最好使用绝对路径.
2. 因为目录相对路径的basedir, 永远都是当前工作路径, 如果要使用, 需要和实际部署路径相关, 所以实际使用的很少(当然, 也有借助chdir来完成的模块).
3. 在模块化的系统设计中, 一般应该在模块内, 通过获取模块的部署路径(dirname(__FILE__), php5.3以后更是提供了__DIR__常量)从而使用绝对路径.
有点没看懂,
[…] 深入理解PHP之require/include顺序 […]
[…] Here is an article by Laruence for reference.http://www.laruence.com/2010/05/04/14 … […]
[…] 深入理解PHP之require/include顺序 […]
[…] 从php核心代码看require和include的区别 […]
[…] 鸟哥也有篇文章,讲包含文件时查找顺序。传送门 […]
I think everything posted was actually very reasonable.
However, what about this? what if you were to create a awesome post title?
I mean, I don’t want to tell you how to run your website, however what if you added
a headline that grabbed folk’s attention? I mean 深入理解PHP之require/include顺序 | 风雪之隅 is
kinda boring. You ought to look at Yahoo’s front page and note how they create post
titles to get viewers interested. You might add a video or a related picture or two to get people excited about everything’ve written. Just my opinion, it might make your website a
little livelier.
[…] 《深入理解PHP之require/include顺序》 from Laruence. […]
[…] 鸟哥有个很好的文章介绍了require/include的时候查找文件的顺序: […]
[…] : http://www.laruence.com/2010/05/04/1450.html 标签 上一篇: 让“@” […]
我最近遇到一个很奇怪的问题
例如 1.php 中 include ‘common/2.php’;
1.php和目录处于同一级目录。
如上写,在我测试的两台电脑都可以用,这应该是由于include_path=’.;c:\php52′ 的作用。
但是我这样写 include ‘./common/2.php’;就无法找到文件2.php了(两台电脑,一台可以,另外一台就不行)
根据手册说的如果以. 或..开头,会直接在当前目录开始查找,为什么其中一台电脑就不行了呢?
希望鸟哥看一看这个奇怪的问题。
如果可以的话,能否发到我邮箱 308840239#qq.com
再次先谢过了
[…] 见到鸟哥的一文:深入理解PHP之require/include顺序 http://www.laruence.com/2010/05/04/1450.html […]
[…] 见到鸟哥的一文:深入理解PHP之require/include顺序 http://www.laruence.com/2010/05/04/1450.html 忍不住继续再深入了一下下,在此记录一下深入的过程,以供以后查阅。 […]
[…] 本文地址: http://www.laruence.com/2010/05/04/1450.html […]
@liujin834 你是想做什么呢? 应用场景, 还是只是说对这段代码本身的改进?
您好!我想请教一个问题,我现在的代码是这样写的
if ($_SERVER[“DOCUMENT_ROOT”]) {
$Root = $_SERVER[“DOCUMENT_ROOT”];
$Root = rtrim($Root,”/\\”);
}
else {
$Root = str_replace(DIRECTORY_SEPARATOR . “include”, “”, dirname(__FILE__));
}
define(“ROOT”, $Root);
$includePaths = array(
ROOT . “/libs”,
get_include_path()
);
set_include_path(implode(PATH_SEPARATOR, $includePaths));
怎么改进才能使效率更高一些?
我做了个简单的测试,有两个页面a.php和b.php,并且a.php中require了b.php, 代码如下:
a.php
test();
require ‘b.php’;
function test()
{
echo ‘a.php test’;
}
===========================================
b.php
function test()
{
//重复定义函数
}
程序运行结果是:
a.php test
并且抛出一个函数重复定义的致命错误,我的疑问是:
1. a.php页面在b.php页面抛出了一个致命错误的前提下为什么还能正确输出本页面的test函数结果?
2. 包含页面和被包含页面之间的编译、执行顺序的原理是怎么样的?
非常希望能得到你的解惑,谢谢!
非常抱歉,第三点是我遗漏了代码,在增加了top函数后,a.php页面还是可以正常运行test和top函数的,假设应该是成立的
“3. 从运行的结果来看,本页的函数test应该是在执行包含文件操作之前已经被预处理过了,但是我在a.php和b.php页面中都增加了一个函数top,代码如下:
function top()
{
echo ‘top’;
}
这个时候a.php页面还是只能运行test函数的结果,不管top函数是在b.php页面中test函数定义之前还是之后,a.php都无法运行top函数,这和之前的假设又矛盾了,很困惑”
我做了个简单的测试,有两个页面a.php和b.php,并且a.php中require了b.php, 代码如下:
a.php
test();
require ‘b.php’;
function test()
{
echo ‘a.php test’;
}
===========================================
b.php
function test()
{
//重复定义函数
}
程序运行结果是:
a.php test
并且抛出一个函数重复定义的致命错误,我的疑问是:
1. a.php页面在b.php页面抛出了一个致命错误的前提下为什么还能正确输出本页面的test函数结果?
2. 包含页面和被包含页面之间的编译、执行顺序的原理是怎么样的?
3. 从运行的结果来看,本页的函数test应该是在执行包含文件操作之前已经被预处理过了,但是我在a.php和b.php页面中都增加了一个函数top,代码如下:
function top()
{
echo ‘top’;
}
这个时候a.php页面还是只能运行test函数的结果,不管top函数是在b.php页面中test函数定义之前还是之后,a.php都无法运行top函数,这和之前的假设又矛盾了,很困惑,非常希望能得到你的解惑,谢谢!
@小兴 include/require都是语句, 是在执行时刻才把被包含的文件纳入进来,编译执行。
您好,请教一个问题,以前在网上看到过一种说法,说 include其实就是将要被包含的文件全部读入包含文件中,在这个过程中并不解析这些被包含的文件,而是将它们组成了一个大文件后再统一进行解析,我自己测试时发现并不完全是这样,请教您怎样理解include、require和PHP解析流程之间的关系呢,多谢!
学习了,收益匪浅啊
[…] 本文地址: http://www.laruence.com/2010/05/04/1450.html […]
[…] 本文地址: http://www.laruence.com/2010/05/04/1450.html […]
[…] 本文地址: http://www.laruence.com/2010/05/04/1450.html […]
[…] 见到鸟哥的一文:深入理解PHP之require/include顺序 http://www.laruence.com/2010/05/04/1450.html […]
[…] 见到鸟哥的一文:深入理解PHP之require/include顺序 http://www.laruence.com/2010/05/04/1450.html […]
[…] 见到鸟哥的一文:深入理解PHP之require/include顺序 http://www.laruence.com/2010/05/04/1450.html […]
一直学习中。。…
[…] 本文地址: http://www.laruence.com/2010/05/04/1450.html […]
学习……这个问题本质上是个寻找文件优先级的问题吧,
基本上的顺序是:
1.绝对路径
2.遍历include_path中的路径
3.相对于current_script_dir的相对路径
而且,有两点总结:
1.只要找到了,就不再向下寻找了。
2.如果找不到,就一定会向下寻找。即使使用绝对路径,当文件不存在时,PHP依然会按这个次序进入include_path和current_script_dir的相对路径中寻找(当然更找不到了)。
[…] 见到鸟哥的一文:深入理解PHP之require/include顺序 http://www.laruence.com/2010/05/04/1450.html […]
@N 哦,对, 我设计的例子有问题, 其实是为了说明current_script_dir, 谢谢指正.
主要是在include嵌套的时候,都是以最开始执行的php文件所在的路径为cwd,这样就都理解了。
>>3. 因为没有了当前路径为include_path, 所以在2.php中包含3.php的时候, 是current_script_dir起了作用, 而current_script_dir是2.php的路径, 所以无论在root还是subdir都将得到”subdir”的输出.
2.php包含3.php时不应该是path_to_subdir起作用么?
感谢鸟老师,一直学习中。。
一直在读Laruence老师的博文,很受益。