Skip to content

include 和 require

includerequire 是 PHP 中用于将一个文件的内容包含到当前脚本中的语言结构。它们是实现代码复用和模块化的基础机制。两者的核心区别在于:require 在文件不存在时产生致命错误(E_ERROR),脚本终止;include 在文件不存在时产生警告(E_WARNING),脚本继续执行。

前置知识

基础概念

特性includerequire
文件不存在E_WARNING(警告),脚本继续E_ERROR(致命错误),脚本终止
文件不可读E_WARNING(警告),脚本继续E_ERROR(致命错误),脚本终止
适用场景可选模块、模板、条件加载核心依赖、配置文件、必需组件
性能每次包含都重新解析每次包含都重新解析(首次)
重复包含可以多次包含同一文件可以多次包含同一文件

语言结构而非函数

includerequire 是语言结构(language construct),不是函数。因此它们不需要括号包裹参数(虽然加括号也不会报错)。推荐不加括号:include 'file.php' 而非 include('file.php')

语法结构

基本 include 用法

php
<?php
declare(strict_types=1);

// 包含一个文件
include 'helpers.php';

// 使用括号(不推荐,但合法)
// include('helpers.php');

基本 require 用法

php
<?php
declare(strict_types=1);

// 包含核心配置文件
require 'config/database.php';

// 包含核心库文件
require 'lib/Router.php';

include 在条件语句中使用

include 可以在条件语句中使用,根据条件决定是否包含文件。

php
<?php
declare(strict_types=1);

$debugMode = true;

// 只在调试模式下包含调试工具
if ($debugMode) {
    include 'tools/debugger.php';
}

// 根据平台包含不同的文件
$platform = PHP_OS_FAMILY;

if ($platform === 'Darwin') {
    include 'config/macos.php';
} elseif ($platform === 'Linux') {
    include 'config/linux.php';
} elseif ($platform === 'Windows') {
    include 'config/windows.php';
}

include 的返回值

被包含的文件如果使用了 return 语句,include 会返回该值。如果被包含的文件没有 return,则返回 1(成功包含)。

php
<?php
declare(strict_types=1);

// 创建临时配置文件
$tempFile = sys_get_temp_dir() . '/test_config.php';
file_put_contents($tempFile, '<?php return ["name" => "test", "version" => "1.0"];');

// 使用 include 获取返回值
$config = include $tempFile;

if (is_array($config)) {
    echo "应用:{$config['name']},版本:{$config['version']}\n";
}

// 没有 return 的文件返回 1
$simpleFile = sys_get_temp_dir() . '/simple.php';
file_put_contents($simpleFile, '<?php echo "Hello";');

$result = include $simpleFile;
echo "\ninclude 返回值:" . ($result === 1 ? '1' : $result) . "\n";

// 清理
unlink($tempFile);
unlink($simpleFile);

include 在循环中使用

php
<?php
declare(strict_types=1);

// 在循环中包含多个配置文件
$modules = ['users', 'products', 'orders'];

foreach ($modules as $module) {
    $filePath = sys_get_temp_dir() . "/config_{$module}.php";

    // 创建临时文件
    file_put_contents($filePath, "<?php return ['module' => '{$module}'];");

    $config = include $filePath;
    echo "加载模块:{$config['module']}\n";

    unlink($filePath);
}

详细说明

include vs require 的错误处理差异

php
<?php
declare(strict_types=1);

// include:文件不存在时产生警告,脚本继续
echo "尝试 include 不存在的文件\n";
@include 'nonexistent_file.php';  // 使用 @ 抑制警告
echo "脚本继续执行(include 后)\n";

// require:文件不存在时产生致命错误,脚本终止
// require 'nonexistent_file.php';  // Fatal error,后续代码不执行
// echo "这行不会执行\n";

何时使用 require

对于脚本的正常运作所必需的文件(如数据库配置、路由定义、核心类库),应使用 require。对于可选的或条件性的功能模块,应使用 include

include_path 配置

PHP 的 include_path 配置项定义了 includerequire 搜索文件的目录列表。当指定相对路径时,PHP 会先在当前目录查找,然后按 include_path 中的顺序搜索。

php
<?php
declare(strict_types=1);

// 查看当前 include_path
echo "当前 include_path:\n  " . get_include_path() . "\n\n";

// 添加自定义路径到 include_path
set_include_path(
    get_include_path() . PATH_SEPARATOR . __DIR__ . '/libs'
);

echo "修改后 include_path:\n  " . get_include_path() . "\n";

include 的性能考虑

性能建议

在现代 PHP 应用中,使用 OPcache 可以缓存编译后的 PHP 字节码,因此 include/require 的性能问题已被大幅缓解。对于现代框架项目,通常使用 Composer 的自动加载(autoloading)代替手动 include/require。

php
<?php
declare(strict_types=1);

// 传统方式:手动 include 每个类文件
// require 'classes/User.php';
// require 'classes/Order.php';
// require 'classes/Product.php';

// 现代方式:使用 Composer 自动加载
// require 'vendor/autoload.php';

// 现代方式:在框架中,自动加载由框架引导文件处理
// 例如 Laravel 的 bootstrap/app.php

文件包含与变量作用域

被包含的文件继承包含它的脚本中的变量作用域。在被包含的文件中定义的变量、函数、类等,在包含之后也可以在包含方中使用。

php
<?php
declare(strict_types=1);

// 主脚本中定义变量
$appName = 'MyApp';
$version = '2.0';

// 创建临时包含文件
$includedFile = sys_get_temp_dir() . '/vars.php';
file_put_contents($includedFile, '<?php
$loadedAt = date("Y-m-d H:i:s");
echo "在包含文件中:appName = " . $appName . "\n";
');

include $includedFile;

// 被包含文件中定义的变量在主脚本中也可用
echo "主脚本中:loadedAt = {$loadedAt}\n";

unlink($includedFile);

作为表达式使用

php
<?php
declare(strict_types=1);

// include 可以在表达式中使用
$tempFile = sys_get_temp_dir() . '/data.php';
file_put_contents($tempFile, '<?php return ["count" => 42];');

// 作为函数参数
$totalItems = (include $tempFile)['count'] ?? 0;
echo "总项目数:{$totalItems}\n";

unlink($tempFile);

实战示例

配置文件加载模式

php
<?php
declare(strict_types=1);

class ConfigLoader
{
    private array $config = [];

    public function load(string $filePath): self
    {
        if (!file_exists($filePath)) {
            throw new \RuntimeException("配置文件不存在:{$filePath}");
        }

        $data = require $filePath;

        if (!is_array($data)) {
            throw new \RuntimeException("配置文件必须返回数组");
        }

        $this->config = array_merge($this->config, $data);
        return $this;
    }

    public function get(string $key, mixed $default = null): mixed
    {
        return $this->config[$key] ?? $default;
    }

    public function all(): array
    {
        return $this->config;
    }
}

// 使用示例
$tempDir = sys_get_temp_dir();

$appConfig = $tempDir . '/app.php';
file_put_contents($appConfig, '<?php return ["debug" => true, "timezone" => "Asia/Shanghai"];');

$dbConfig = $tempDir . '/db.php';
file_put_contents($dbConfig, '<?php return ["host" => "localhost", "port" => 3306];');

$loader = new ConfigLoader();
$loader->load($appConfig)
       ->load($dbConfig);

echo "调试模式:" . ($loader->get('debug') ? '开启' : '关闭') . "\n";
echo "时区:" . $loader->get('timezone') . "\n";
echo "数据库端口:" . $loader->get('port') . "\n";

// 清理
unlink($appConfig);
unlink($dbConfig);

按需加载模块

php
<?php
declare(strict_types=1);

class ModuleLoader
{
    private array $loadedModules = [];
    private string $modulesPath;

    public function __construct(string $modulesPath)
    {
        $this->modulesPath = $modulesPath;
    }

    public function load(string $moduleName): bool
    {
        if (isset($this->loadedModules[$moduleName])) {
            return true; // 已加载
        }

        $filePath = "{$this->modulesPath}/{$moduleName}.php";

        if (!file_exists($filePath)) {
            return false;
        }

        include $filePath;
        $this->loadedModules[$moduleName] = true;
        return true;
    }

    public function isLoaded(string $moduleName): bool
    {
        return isset($this->loadedModules[$moduleName]);
    }

    public function getLoadedModules(): array
    {
        return array_keys($this->loadedModules);
    }
}

// 演示
$loader = new ModuleLoader(sys_get_temp_dir());

// 创建临时模块文件
$modules = ['auth', 'cache', 'logger'];
foreach ($modules as $mod) {
    $path = sys_get_temp_dir() . "/{$mod}.php";
    file_put_contents($path, "<?php echo \"模块 {$mod} 已加载\\n\";");
}

foreach ($modules as $mod) {
    if ($loader->load($mod)) {
        echo "{$mod} 加载成功\n";
    }
}

// 再次加载已加载的模块(不会重复)
$loader->load('auth');

echo "已加载模块:" . implode(', ', $loader->getLoadedModules()) . "\n";

// 清理
foreach ($modules as $mod) {
    unlink(sys_get_temp_dir() . "/{$mod}.php");
}

注意事项

  1. 路径安全:不要将用户输入直接作为 include 的路径,这可能导致远程文件包含(RFI)漏洞。

  2. 被包含文件的返回值:如果被包含文件有 returninclude 会返回该值;如果没有,返回 1

  3. 变量污染:被包含的文件与包含方共享同一作用域,注意变量名冲突。

  4. 使用 require 保护核心文件:对于必需的配置和库文件,使用 require 确保缺失时脚本立即终止。

  5. 现代项目使用自动加载:使用 Composer 的 PSR-4 自动加载代替手动 include 类文件。

最佳实践

  1. 核心依赖用 require:配置文件、引导文件、核心库用 require

  2. 可选模块用 include:模板、插件、条件性功能用 include

  3. 配置文件用 return:被包含的配置文件使用 return 返回数组。

  4. 使用 Composer 自动加载:类文件的加载应交给 Composer 的 PSR-4 自动加载处理。

  5. 始终使用绝对路径:配合 __DIR__ 常量使用绝对路径,避免相对路径的不确定性。

下一节

include_oncerequire_once 确保文件只被包含一次。接下来学习 include_once/require_once

参考链接