Menu

PHP include 和 require:加载其他 PHP 文件

include 'header.php'; 会在当前位置运行另一个 PHP 文件,就像把它的代码粘贴到这里一样。require 的作用相同,但文件不存在时会以致命错误终止脚本,而 include 只发出警告并继续执行。include_once 和 require_once 只在第一次时加载文件。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

include 'header.php'; 会在脚本的当前位置运行另一个 PHP 文件的代码,就像把代码粘贴到这里一样。require 'header.php'; 的作用相同,但如果文件无法加载,require 会以致命错误终止脚本,而 include 只输出一条警告并继续执行。最常见的入门用法是让每个页面共用同一个页头和页脚。

这段代码先写入 header.php 和 footer.php,这样就有文件可以引入了。运行它,然后看看“页面”标签页:

被引入的文件就是普通的 PHP 文件:它们可以包含带有 <?= ?> 标签的 HTML,也能看到在 require 之前设置的 $title 变量。修改标题再运行一次;只要改动这一个文件,每个引入了页头的页面都会随之更新。

include 和 require 的区别

唯一的区别在于文件不存在或无法读取时会发生什么。include 会输出两条警告,值为 false,然后脚本继续执行:

Warning: include(sidebar.php): Failed to open stream: No such file or directory in /home/index.php on line 2

Warning: include(): Failed opening 'sidebar.php' for inclusion (include_path='.:/usr/share/php83') in /home/index.php on line 2

require 会输出同样的警告,然后抛出一个 Error,如果没有代码捕获它,脚本就会终止:

PHP Warning:  require(config.php): Failed to open stream: No such file or directory in /home/index.php on line 2
PHP Fatal error:  Uncaught Error: Failed opening required 'config.php' (include_path='.:/usr/share/php83') in /home/index.php:2

所以,页面离不开的东西(配置、函数库、类文件)用 require,可有可无的部分用 include。对于可能本来就不存在的部分,与其依赖警告,不如先检查它是否存在:

输出:

Spring sale: 20% off
banner.php included, include returned int(1)
sidebar.php not found, skipped

include_once 和 require_once

定义了函数或类的文件只能加载一次:第二次加载会导致致命错误,因为这个函数已经存在了。

Fatal error: Cannot redeclare greet() (previously declared in /home/helpers.php:2) in /home/helpers.php on line 2

只要有两个文件都写了 require 'helpers.php',就会出现这种情况。require_once 和 include_once 会记住哪些文件已经加载过,第二次时直接跳过并返回 true:

输出:

int(1)
bool(true)
bool(true)
Hello, Ada

声明了函数、类等内容的文件用 _once 形式,需要多次输出的模板用普通的 include。在使用 Composer 的项目中,自动加载器会替你加载类文件,所以针对类的 require_once 调用基本上就用不到了;见 Composer。

从被引入的文件返回值

被引入文件顶层的 return 会结束这个文件,并把一个值交回给 include 或 require 表达式。这样就能写出干净的配置文件:没有全局变量,只有一个值,你想赋给哪个变量名都行。

输出:

My Shop
shop
bool(false)

Laravel 的配置文件(config/app.php、config/database.php 等)就是这样工作的。

被引入文件中的变量和作用域

被引入的代码在引入它的那一行所在的作用域中运行。在脚本顶层,它能看到所有全局变量;在函数内部,它只能看到该函数的局部变量。第二条规则正是一个迷你模板渲染器的工作原理:用一个函数,让它的局部变量恰好就是模板应该看到的那些变量。

“页面”标签页显示了用同一个模板文件渲染出的两个列表。因为 include 是在 render() 内部运行的,所以模板能看到 $heading 和 $products,却看不到 $secret。模板中使用的 foreach ... endforeach 写法是 PHP 为 HTML 模板提供的替代语法。

引入路径和 DIR

像 'helper.php' 这样的路径并不是相对于包含 include 的那个文件来解析的。PHP 会先搜索 include_path 设置(通常以 . 开头,即当前工作目录),然后才搜索执行引入的文件自己所在的文件夹。所以在 partials/nav.php 中,include 'helper.php' 可能会加载项目根目录下一个完全不同的 helper.php。__DIR__ 表示当前文件所在的目录,用它就不用再猜了:

输出:

loaded the root helper.php
loaded partials/helper.php
__DIR__ in nav.php is partials

每个 include 都写成 __DIR__ . '/relative/path.php'。工作目录取决于脚本是怎么启动的(Web 服务器、cron 任务、从其他文件夹在命令行中运行),但 __DIR__ 永远不变。

绝不要引入由用户指定的文件

常见的第一个路由器会从 URL 中读取页面名:include $_GET['page'] . '.php';。这让访问者可以选择运行服务器上的哪个文件,例如 ?page=../../config(本地文件包含),在旧的配置下甚至能运行另一台服务器上的文件。正确的做法是把允许的名字映射到文件。运行这段代码并点击链接:每次点击都会用不同的 $_GET['page'] 重新运行脚本。

只有 $pages 中的三个名字才可能到达 require;../secret 链接得到的是“Not found”页面。安全读取请求值的方法见 GET和_GET 和 _POST。

常见问题

PHP 中 include 和 require 有什么区别?

两者都会加载并运行另一个 PHP 文件。如果文件无法加载,require 会以致命错误终止脚本,而 include 会输出警告、返回 false,并让脚本继续执行。页面离不开的文件,比如配置文件和函数库,要用 require。

什么时候应该用 require_once 而不是 require?

定义了函数、类或常量的文件要用 require_once。这类文件加载两次会导致致命错误(Cannot redeclare),而 require_once 会跳过已经加载过的文件。需要多次输出的模板则用普通的 include。

被引入的 PHP 文件可以返回值吗?

可以。被引入文件顶层的 return 会结束这个文件,并成为 include 或 require 表达式的值:$config = require 'config.php';,而 config.php 中写的是 return ['debug' => true];。没有 return 时,include 的值为 1。

被引入的文件能访问我的变量吗?

能。被引入的代码在引入它的那一行所在的作用域中运行:在脚本顶层,它能看到全局变量;在函数内部,它只能看到该函数的局部变量。它创建的变量也会出现在同一个作用域中。

应该写 include('file.php') 还是 include 'file.php'?

两种都可以,因为 include 和 require 是语言结构而不是函数,括号只是把路径括起来而已。大多数代码风格指南写成不带括号的 require 'file.php';。

Coddy 编程语言插图

和 Coddy 一起学编程

开始