PHP 零基础教程

PHP 避免重复包含:include_once 与 require_once

虽然 includerequire 允许我们整合外部的 PHP 文件,但在实际开发中,经常会遇到这样的情况:为了防止错误、变量被重新声明以及意想不到的副作用,我们必须确保某个文件只能被包含一次。这时候,include_oncerequire_once 就变得不可或缺了。它们提供了一种安全机制,防止在单次脚本执行期间多次包含同一个文件。除了具备“检查文件是否已被包含”这一额外功能外,它们的行为逻辑与非 once 版本的对应语句完全一致。

1. 深入理解 include_once

include_once 语句在脚本执行期间包含并运行指定文件,这与 include 类似。最关键的区别在于: 如果该文件中的代码已经被 includeinclude_oncerequirerequire_once 包含过,则 include_once 不会再次包含它。这完美地防止了函数、类和常量的重复定义,从而避免了致命错误(Fatal Errors)。

include_once 尝试包含一个不存在的文件时,它会生成一个警告 (WARNING),然后脚本会继续执行。

让我们来看一个常见的场景:你有一个定义了数据库凭证或应用常量的配置文件。

1.1 基础示例:配置文件加载

首先,创建一个名为 config.php 的文件:

<?php
// config.php
define('DB_HOST', 'localhost');
define('APP_NAME', '我的应用程序');

function getAppName() {
    return APP_NAME;
}

echo "配置文件已加载。<br>";
?>

现在,让我们在主脚本中先尝试使用 include 多次包含它,然后再试试 include_once

首先,使用 include:

<?php
// index_include.php
echo "--- 使用 include ---<br>";
include 'config.php'; // 第一次包含
include 'config.php'; // 第二次包含
include 'config.php'; // 第三次包含

echo "应用名称: " . getAppName() . "<br>";
?>

运行 index_include.php 会产生以下报错:

--- 使用 include ---
配置文件已加载。
配置文件已加载。
配置文件已加载。
Fatal error: Cannot redeclare getAppName() (previously declared in /path/to/config.php:6) in /path/to/config.php on line 6

脚本因致命错误而终止。虽然某些常量在重复定义时只报警告,但重新声明函数绝对会触发致命错误。因为文件被加载了三次,函数自然也被定义了三次。

现在,使用 include_once:

<?php
// index_include_once.php
echo "--- 使用 include_once ---<br>";
include_once 'config.php'; // 第一次包含
include_once 'config.php'; // 第二次包含(被忽略)
include_once 'config.php'; // 第三次包含(被忽略)

echo "应用名称: " . getAppName() . "<br>";
?>

运行 index_include_once.php 的输出:

--- 使用 include_once ---
配置文件已加载。
应用名称: 我的应用程序

输出结果清楚地表明 config.php 仅仅被加载了一次,成功避开了致命错误。

1.2 进阶示例:共享的工具函数库

考虑一个拥有多个页面的 Web 应用,这些页面都需要一组通用的工具函数,比如 sanitizeInput()formatDate()。如果这些函数定义在 utilities.php 中,而多个组件(如 header.php, footer.php, main_content.php)都需要使用它们并各自 include 'utilities.php',如果没有 _once 机制,你马上就会遇到重定义错误。

让我们创建 utilities.php:

<?php
// utilities.php
function sanitizeInput($data) {
    return htmlspecialchars(stripslashes(trim($data)));
}
echo "工具库文件已加载。<br>";
?>

现在,假设 header.phpprofile.php 都需要这些工具,并且它们最终都会被包含在 index.php 中。

header.php:

<?php
include_once 'utilities.php';
echo "页眉内容加载完毕。<br>";
?>

profile.php:

<?php
// 由于 utilities.php 已经在 header.php 中加载过了,这次包含将被静默忽略
include_once 'utilities.php'; 
echo "用户资料: " . sanitizeInput("<script>alert('xss');</script>") . "<br>";
?>

index.php:

<?php
echo "--- 主页开始 ---<br>";
include_once 'header.php';
include_once 'profile.php';
echo "--- 主页结束 ---<br>";
?>

运行 index.php 的结果:

--- 主页开始 ---
工具库文件已加载。
页眉内容加载完毕。
用户资料: <script>alert('xss');</script>
--- 主页结束 ---

请注意,“工具库文件已加载。” 这句话只出现了一次,证实了 utilities.php 只在遇到 header.php 时被包含了一次。

2. 理解 require_once

require_once 语句与 require 完全相同,唯一的区别也是 PHP 会检查该文件是否已经被包含过,如果是,则不会再次包含它。就像 include_once 一样,这对于防止重定义错误至关重要。

require_onceinclude_once 之间最主要的分歧在于它们处理缺失文件的方式。如果 require_once 指定的文件不存在,它会引发一个致命错误 (FATAL ERROR)立即停止脚本执行。这使得 require_once 成为加载应用程序绝对依赖的核心文件(如框架核心类、数据库连接脚本)的不二之选。

2.1 基础示例:数据库连接

让我们创建一个数据库连接文件 db_connect.php:

<?php
// db_connect.php
echo "数据库连接成功。<br>";
$db_is_ready = true;
?>

现在,在 index_db.php 中使用 require_once:

<?php
// index_db.php
echo "--- 尝试连接数据库 ---<br>";

require_once 'db_connect.php'; // 第一次包含
require_once 'db_connect.php'; // 第二次包含(被忽略)

// 继续执行数据库操作
if (isset($db_is_ready)) {
    echo "可以执行查询操作了。<br>";
}
echo "--- 脚本继续 ---<br>";
?>

这确保了建立数据库连接的昂贵操作(或变量初始化)只执行一次。

2.2 处理缺失的关键文件

现在,让我们直观地对比一下当文件不存在时,两者的错误处理差异。假设 non_existent_file.php 根本不存在。

使用 include_once:

<?php
echo "include_once 之前。<br>";
include_once 'non_existent_file.php'; 
echo "include_once 之后。<br>"; // 这行代码会被执行!
?>

结果: 脚本抛出两行 Warning 警告后,依然打印出了 "include_once 之后。"。

使用 require_once:

<?php
echo "require_once 之前。<br>";
require_once 'non_existent_file.php'; 
echo "require_once 之后。<br>"; // 这行代码【永远不会】被执行!
?>

结果: 脚本抛出 Warning 随后紧接着抛出 Fatal error,执行被瞬间掐断,"require_once 之后。" 永远不会输出。对于关键组件来说,这种“宁死不屈”的行为正是我们想要的。

3. 实际应用与最佳实践

include_oncerequire_once 之间做出选择,取决于被包含文件的重要程度

  • 使用 require_once 加载核心组件: 用于那些如果缺失,脚本就绝对无法运行的文件。
    • 配置文件(包含密码、API 密钥)。
    • 数据库连接脚本。
    • 定义了核心类和接口的自动加载器或框架文件。
  • 使用 include_once 加载非核心组件: 用于那些如果缺失,只是影响部分功能,但不至于让整个应用崩溃的文件。
    • 可选的工具函数库。
    • 前端模板部件(比如侧边栏、不影响主体阅读的评论区模板)。
    • 多语言本地化文件(如果缺失,可以退而使用默认语言)。

3.1 减少冗余与提升性能

确保文件只被包含一次,不仅能防止错误,还有助于:

  • 资源效率: 避免对相同的代码进行不必要的重复解析和执行。虽然在小型脚本中性能提升可以忽略不计,但在拥有复杂依赖树的大型应用中,积少成多,效果显著。
  • 可维护性: 让你在管理依赖关系时更加从容,无需时刻提心吊胆地担心文件引入的先后顺序或是否被多次引入。

3.2 假设场景:电商商品详情页

想象一下正在构建一个电商网站的商品详情页 (product.php)。它可能需要以下文件:

  • config.php: 数据库凭证。(必需 -> require_once
  • db_connection.php: 建立 PDO 连接。(必需 -> require_once
  • product_model.php: 定义 Product 类。(必需 -> require_once
  • promotions.php: 检查促销活动。(非必需,即便没有促销,商品也能卖 -> include_once
  • customer_reviews.php: 渲染用户评价。(非必需,评价加载失败不应导致商品无法展示 -> include_once
<?php
// product.php
require_once 'config.php';
require_once 'db_connection.php';
require_once 'product_model.php';

// 如果 promotions.php 丢失,页面依然加载,只是不显示打折信息
include_once 'promotions.php';

// 如果 customer_reviews.php 丢失,页面依然加载,只是不显示评论
include_once 'customer_reviews.php';

// ... 渲染页面的核心业务逻辑 ...
?>

这种架构确保了核心组件缺失时能及时止损,而可选组件故障时能优雅降级。