msgbartop
PHP语言, PHP扩展, Zend引擎相关的研究,技术,新闻分享 – 左手代码 右手诗
msgbarbottom

04 May 10 深入理解PHP之require/include顺序

在大型的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__常量)从而使用绝对路径.


分享到:



Random Posts:

Tags: , ,

28 Responses to “深入理解PHP之require/include顺序”

  1. include、require的区别 – GameSife |

    [...] 鸟哥也有篇文章,讲包含文件时查找顺序。传送门 [...]

  2. business coaching training |

    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.

  3. 为什么PHP的主流框架和CMS采用绝对路径而不是相对路径? | segment-解决方案 |

    [...] 《深入理解PHP之require/include顺序》 from Laruence. [...]

  4. 敲代码的火柴 | require/include |

    [...] 鸟哥有个很好的文章介绍了require/include的时候查找文件的顺序: [...]

  5. [转] 深入理解PHP之require/include顺序 | 第四城技术团队 - 第四城社区_让城市更温暖 让生活更精彩 |

    [...] : http://www.laruence.com/2010/05/04/1450.html 标签 上一篇: 让“@” [...]

  6. Berc |

    我最近遇到一个很奇怪的问题
    例如 1.php 中 include ‘common/2.php’;
    1.php和目录处于同一级目录。
    如上写,在我测试的两台电脑都可以用,这应该是由于include_path=’.;c:\php52′ 的作用。
    但是我这样写 include ‘./common/2.php’;就无法找到文件2.php了(两台电脑,一台可以,另外一台就不行)
    根据手册说的如果以. 或..开头,会直接在当前目录开始查找,为什么其中一台电脑就不行了呢?

    希望鸟哥看一看这个奇怪的问题。
    如果可以的话,能否发到我邮箱 308840239#qq.com
    再次先谢过了

  7. php require和include的区别 | web开发 |

    [...] 见到鸟哥的一文:深入理解PHP之require/include顺序 http://www.laruence.com/2010/05/04/1450.html [...]

  8. 从php核心代码看require和include的区别 | 炮灰`Blog |

    [...] 见到鸟哥的一文:深入理解PHP之require/include顺序 http://www.laruence.com/2010/05/04/1450.html 忍不住继续再深入了一下下,在此记录一下深入的过程,以供以后查阅。 [...]

  9. 深入理解PHP之require/include顺序 | 万维网黑客联盟 |

    [...] 本文地址: http://www.laruence.com/2010/05/04/1450.html [...]

  10. 雪候鸟 |

    @liujin834 你是想做什么呢? 应用场景, 还是只是说对这段代码本身的改进?

  11. 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));

    怎么改进才能使效率更高一些?

  12. 小兴 |

    我做了个简单的测试,有两个页面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. 包含页面和被包含页面之间的编译、执行顺序的原理是怎么样的?

    非常希望能得到你的解惑,谢谢!

  13. 小兴 |

    非常抱歉,第三点是我遗漏了代码,在增加了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函数,这和之前的假设又矛盾了,很困惑”

  14. 小兴 |

    我做了个简单的测试,有两个页面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函数,这和之前的假设又矛盾了,很困惑,非常希望能得到你的解惑,谢谢!

  15. 雪候鸟 |

    @小兴 include/require都是语句, 是在执行时刻才把被包含的文件纳入进来,编译执行。

  16. 小兴 |

    您好,请教一个问题,以前在网上看到过一种说法,说 include其实就是将要被包含的文件全部读入包含文件中,在这个过程中并不解析这些被包含的文件,而是将它们组成了一个大文件后再统一进行解析,我自己测试时发现并不完全是这样,请教您怎样理解include、require和PHP解析流程之间的关系呢,多谢!

  17. heihusdm |

    学习了,收益匪浅啊

  18. 深入理解 PHP之require/include顺序 | haohtml's blog |

    [...] 本文地址: http://www.laruence.com/2010/05/04/1450.html [...]

  19. 从php核心代码看require和include的区别 | haohtml's blog |

    [...] 见到鸟哥的一文:深入理解PHP之require/include顺序 http://www.laruence.com/2010/05/04/1450.html [...]

  20. av |

    一直学习中。。…

  21. 深入理解PHP之require/include顺序-传播、沟通、分享-一直“有你” |

    [...] 本文地址: http://www.laruence.com/2010/05/04/1450.html [...]

  22. yufeng |

    学习……这个问题本质上是个寻找文件优先级的问题吧,
    基本上的顺序是:

    1.绝对路径
    2.遍历include_path中的路径
    3.相对于current_script_dir的相对路径

    而且,有两点总结:
    1.只要找到了,就不再向下寻找了。
    2.如果找不到,就一定会向下寻找。即使使用绝对路径,当文件不存在时,PHP依然会按这个次序进入include_path和current_script_dir的相对路径中寻找(当然更找不到了)。

  23. 从php核心代码看require和include的区别 - 五四陈科学院-php和java第一院 |

    [...] 见到鸟哥的一文:深入理解PHP之require/include顺序 http://www.laruence.com/2010/05/04/1450.html [...]

  24. 雪候鸟 |

    @N 哦,对, 我设计的例子有问题, 其实是为了说明current_script_dir, 谢谢指正.

  25. dd |

    主要是在include嵌套的时候,都是以最开始执行的php文件所在的路径为cwd,这样就都理解了。

  26. N |

    >>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起作用么?

  27. CFC4N |

    感谢鸟老师,一直学习中。。

  28. tomheng |

    一直在读Laruence老师的博文,很受益。

Leave a Reply

*