PHP应该如何加载文件

342 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第29天,点击查看活动详情

php文件加载有四种语句:

  1. include
  2. require
  3. include_once
  4. require_once

方法详解:

  1. include 语句包含并运行指定文件。
  2. require 和 include 几乎完全一样,除了处理失败的方式不同之外。require 在出错时产生 E_COMPILE_ERROR(编译致命错误 64 编译时致命性错。这就像由Zend脚本引擎生成了一个 E_ERROR。) 级别的错误。换句话说将导致脚本中止而 include 只产生警告(E_WARNING),脚本会继续运行。
  3. include_once 语句在脚本执行期间包含并运行指定文件。此行为和 include 语句类似,唯一区别是如果该文件中已经被包含过,则不会再次包含。如同此语句名字暗示的那样,只会包含一次。可以用于在脚本执行期间同一个文件有可能被包含超过一次的情况下,想确保它只被包含一次以避免函数重定义,变量重新赋值等问题。
  4. require_once 语句和 require 语句完全相同,唯一区别是 PHP 会检查该文件是否已经被包含过,如果是则不会再次包含。

总结:

  1. include 和 require 语句都是包含并运行指定文件,不同的是处理失败的方式不同,所以一般 require 用于加载重要文件,比如加载框架的引导文件,如果出错将终止程序,include 一般用于加载配置文件或者第三方扩展库等,比如 composer 就是用的 include 加载文件的。
  2. include_once 和 require_once 语句与 include 和 require 语句的不同就是,如果该文件已经包含过就不会再次包含了,这在避免文件重复加载,函数重定义,变量重新赋值等问题上有用,但是一定程度上会有性能损耗问题,毕竟需要检查是否加载过。所以一般确定的情况下会优先使用 include 和 require ,而不是 include_once 和 require_once 语句。

实验

扩展知识:

包含文件的语句会受php.ini 和 include_path(;include_path = ".:/php/includes")配置项影响,相关函数:

  • ini_set() - 为一个配置选项设置值
  • set_include_path - 设置当前的 include_path 配置选项
  • get_include_path() - 获取当前的 include_path 配置选项
  • restore_include_path() - 还原 include_path 配置选项的值
  1. 直接引入(包含)文件默认是以上面set_include_path设置的目录为环境目录,环境目录找不到再从当前目录找
  2. /表示服务器根目录,linux上面直接表示磁盘根目录,Windows下面表示某个磁盘根目录例如D:\,realpath()可以返回绝对路径。
  3. ./表示当前运行文件的同级目录(入口),而不是当前脚本所在的目录(include 语句所在脚本文件的目录)
  4. 如果以相对于当前脚本文件引用文件请用__DIR__常量

注意:第一种情况,请小心了!这个一般表示从当前目录找,但是什么是当前目录呢?这个不同情况是不同的,它会先把当前运行文件的目录当成当前目录,如果找不到,再把当前脚本所在的目录当成当前目录,从当前脚本所在目录开始找,如果还找不到就报错,引入文件不存在。

所以如果意图清楚,最好是使用最后两种方式引入文件,这样能减少不必要的麻烦,另外使用绝对路径比使用相对路径加载文件性能更高,这样能避免系统去解析相对路径而耗费一些时间。

当前运行文件当前脚本,请注意仔细理解它们的差别,前者作为入口运行,后者则被脚本通过引用的方式调用。魔术常亮__FILE____DIR__ 就是后者的文件名(包含完整的绝对路径)和目录。


关于路径的其他引申

PHP脚本这种文件相互引用包含的关系可和网页中静态文件css中的../路径不同,css中的路径之和所在css文件url路径相关,和网页没有半毛钱关系,网页只是加载它而已,但是如果css中使用/那情况就不同了,一般/为根域名,再说一遍,静态文件中除了/其他的路径都是相对于当前静态文件的,和网页地址没有关系。网页中的.//则是相对于当前url路径(对于pathinfo也没有关系,不会受影响,认不出是假目录的)和根域。

还可以通过注册自动加载方法来实现自动加载,框架大部分都实现了自动加载,不然手动写包含语句不利于维护,太麻烦。


包含变量作用域问题


// a.php

<?php

$a = 1;

function fa()
{
    global $b;
    echo 'fa.b: ' . $b . PHP_EOL;
}

// include 'b.php';

function i()
{
    $c = 1;
    include 'b.php';
}
i();

function fa2()
{
    global $b;
    echo 'fa2.b: ' . $b . PHP_EOL;
}

echo 'a.a: ' . $a . PHP_EOL;

echo 'a.b: ' . $b . PHP_EOL;

fa();

fa2();

fb2();

// 输出结果:
// b.b: 2
// b.a: 1
// fb.b: 2
// fb2.a: 1
// a.a: 1
// a.b: 2
// fa.b: 2
// fa2.b: 2
// fb2.a: 1

// b.php

<?php

$b = 2;

function fb()
{
    global $b;
    echo 'fb.b: ' . $b . PHP_EOL;
}

function fb2()
{
    global $a;
    echo 'fb2.a: ' . $a . PHP_EOL;
}

echo 'b.b: ' . $b . PHP_EOL;

echo 'b.a: ' . $a . PHP_EOL;

echo 'b.c: ' . $c . PHP_EOL;

fb();

fb2();

结论

如果包含发生在函数内部,则 包含文件中的 变量作用域处于 该函数内部,函数不受影响 还是全局的。
如果包含不是发生在函数内部,则 包含文件中的 变量作用域处于 全局,这点和预期一致。

扩展思考

到目前为止我们发现,所有程序都会有一个唯一的启动入口,也就是只会有一个启动入口文件(注意这不是指web应用的单入口文件,而是指程序启动的首个入口文件,并不泛指某个应用入口文件),不会有两个,程序是从一个入口开始启动的,不会由两个入口共同启动程序。我们猜想进程是不是也是这样的,任何程序最开始都是从一个程序入口启动的,哪怕它是多进程多线程的程序,最开始也都是从一个入口启动的。

结论

如果包含发生在函数内部,则 包含文件中的 变量作用域处于 该函数内部,函数不受影响 还是全局的。
如果包含不是发生在函数内部,则 包含文件中的 变量作用域处于 全局,这点和预期一致。

扩展思考

到目前为止我们发现,所有程序都会有一个唯一的启动入口,也就是只会有一个启动入口文件(注意这不是指web应用的单入口文件,而是指程序启动的首个入口文件,并不泛指某个应用入口文件),不会有两个,程序是从一个入口开始启动的,不会由两个入口共同启动程序。我们猜想进程是不是也是这样的,任何程序最开始都是从一个程序入口启动的,哪怕它是多进程多线程的程序,最开始也都是从一个入口启动的。