Skip to content

include_once 和 require_once

include_oncerequire_onceincluderequire 的变体。它们在包含文件之前会先检查该文件是否已经被包含过,如果已包含则跳过。这可以避免同一文件被重复包含导致的函数重定义、常量重复声明等问题。

前置知识

基础概念

特性include_oncerequire_onceincluderequire
重复包含自动跳过自动跳过允许允许
文件不存在E_WARNINGE_ERRORE_WARNINGE_ERROR
性能有额外检查开销有额外检查开销无额外检查无额外检查
基于路径判断解析后的绝对路径解析后的绝对路径N/AN/A

性能影响

_once 变体需要在每次调用时检查文件是否已被包含,这会带来轻微的性能开销。在现代项目中,使用 OPcache 后影响很小,但对于高频加载场景仍需注意。

语法结构

基本 include_once 用法

php
<?php
declare(strict_types=1);

// 确保 utils.php 只被包含一次
include_once 'utils.php';
include_once 'utils.php'; // 第二次包含会被跳过
include_once 'utils.php'; // 也会被跳过

echo "utils.php 只被包含了一次\n";

基本 require_once 用法

php
<?php
declare(strict_types=1);

// 确保核心库只被包含一次
require_once 'core/Database.php';
require_once 'core/Cache.php';

echo "核心库加载完成\n";

include_once 在条件中使用

php
<?php
declare(strict_types=1);

function loadFeature(string $featureName): void
{
    $filePath = __DIR__ . "/features/{$featureName}.php";

    if (file_exists($filePath)) {
        include_once $filePath;
        echo "功能 {$featureName} 加载成功\n";
    } else {
        echo "功能 {$featureName} 不存在\n";
    }
}

loadFeature('auth');
loadFeature('auth'); // 第二次调用会被跳过

require_once 的返回值

php
<?php
declare(strict_types=1);

// 创建临时文件
$tempFile = sys_get_temp_dir() . '/once_test.php';
file_put_contents($tempFile, '<?php
static $loadCount = 0;
$loadCount++;
echo "文件被加载了 {$loadCount} 次\n";
return $loadCount;
');

$result1 = require_once $tempFile; // 加载
$result2 = require_once $tempFile; // 跳过

echo "第一次返回:{$result1}\n";
echo "第二次返回:{$result2}\n";

// 清理
unlink($tempFile);
// 输出:
// 文件被加载了 1 次
// 第一次返回:1
// 第二次返回:true(include_once 在跳过时返回 true)

详细说明

与 include/require 的关键区别

_once 变体的核心价值是防止重复定义。在 PHP 中,重复定义函数、类或常量会导致致命错误。

php
<?php
declare(strict_types=1);

// 场景:多个文件都需要用到工具函数

// file: helpers.php
$helpersContent = '<?php
function formatCurrency(float $amount): string
{
    return "¥" . number_format($amount, 2);
}
';
$helpersFile = sys_get_temp_dir() . '/helpers.php';
file_put_contents($helpersFile, $helpersContent);

// file: report.php(需要 helpers)
$reportContent = '<?php include "' . $helpersFile . '";';
$reportFile = sys_get_temp_dir() . '/report.php';
file_put_contents($reportFile, $reportContent);

// file: dashboard.php(也需要 helpers)
$dashContent = '<?php include "' . $helpersFile . '";';
$dashFile = sys_get_temp_dir() . '/dashboard.php';
file_put_contents($dashFile, $dashContent);

// 使用 include_once 避免重复定义
include_once $helpersFile;
include_once $reportFile;    // report.php 中 include helpers,但因已加载会被跳过
include_once $dashFile;      // dashboard.php 中 include helpers,同样跳过

echo formatCurrency(1234.5) . "\n";

// 清理
unlink($helpersFile);
unlink($reportFile);
unlink($dashFile);

_once 基于解析后的绝对路径判断

_once 的"已包含"判断基于文件被解析后的绝对路径。这意味着通过不同相对路径引用同一文件,仍然会被识别为同一文件。

php
<?php
declare(strict_types=1);

$tempDir = sys_get_temp_dir();
$tempFile = $tempDir . '/unique_file.php';
file_put_contents($tempFile, '<?php echo "文件已加载\n";');

// 通过不同路径包含同一文件
// 第二次 require_once 会被跳过,因为解析后的绝对路径相同
require_once $tempFile;
require_once realpath($tempFile); // 同一文件,跳过

echo "两次 require_once 只加载了一次\n";

// 清理
unlink($tempFile);

使用场景

场景推荐原因
类/接口定义Composer autoloading不需要手动 include_once
函数库文件require_once防止重复定义函数
常量/配置定义require_once防止重复定义常量
模板文件include模板可以多次渲染
条件加载include_once确保只加载一次
核心依赖require_once必须加载且不重复

性能影响

php
<?php
declare(strict_types=1);

// _once 的性能测试概念
// include_once 在内部维护一个已包含文件的哈希表
// 每次调用都需要计算路径并查询哈希表

// 在循环中使用 include_once(不推荐)
/*
for ($i = 0; $i < 1000; $i++) {
    include_once 'utils.php'; // 每次循环都要检查哈希表
}
*/

// 推荐:在循环外 include_once 一次
include_once 'utils.php';
for ($i = 0; $i < 1000; $i++) {
    // 直接使用已加载的函数
}

避免在循环中使用 _once

尽管 _once 会自动跳过重复包含,但在循环中反复调用仍会产生不必要的哈希表查询开销。应该在循环外部加载一次。

实战示例

兼容旧式类加载

在没有使用 Composer 自动加载的旧项目中,require_once 是加载类文件的标准方式。

php
<?php
declare(strict_types=1);

// 旧式手动类加载
function loadClass(string $className): void
{
    $classFile = __DIR__ . '/classes/' . str_replace('\\', '/', $className) . '.php';

    if (file_exists($classFile)) {
        require_once $classFile;
    }
}

// 在新项目中,推荐使用 Composer 的 spl_autoload_register
// 而不是手动 require_once

安全的 include_once 使用

php
<?php
declare(strict_types=1);

class SafeIncluder
{
    private array $includedFiles = [];

    public function includeOnce(string $filePath, bool $required = false): mixed
    {
        // 标准化路径
        $realPath = realpath($filePath);

        if ($realPath === false) {
            if ($required) {
                throw new \RuntimeException("文件不存在:{$filePath}");
            }
            return false;
        }

        // 检查是否已包含
        if (isset($this->includedFiles[$realPath])) {
            return $this->includedFiles[$realPath];
        }

        // 包含文件
        $result = include $realPath;

        // 记录已包含的文件
        $this->includedFiles[$realPath] = $result;

        return $result;
    }

    public function getIncludedFiles(): array
    {
        return array_keys($this->includedFiles);
    }
}

// 使用示例
$includer = new SafeIncluder();

$tempFile = sys_get_temp_dir() . '/safe_include.php';
file_put_contents($tempFile, '<?php return ["loaded" => true];');

$result1 = $includer->includeOnce($tempFile);
$result2 = $includer->includeOnce($tempFile);

echo "第一次:" . ($result1['loaded'] ? '已加载' : '未加载') . "\n";
echo "第二次:" . ($result2['loaded'] ? '已加载' : '使用缓存') . "\n";
echo "已包含文件数:" . count($includer->getIncludedFiles()) . "\n";

unlink($tempFile);

现代项目中的替代方案

在现代 PHP 项目中,include_once/require_once 加载类文件的用法已被 Composer 的 PSR-4 自动加载机制取代。

php
<?php
// 旧方式(不推荐)
require_once 'src/Models/User.php';
require_once 'src/Models/Order.php';
require_once 'src/Services/PaymentService.php';

// 新方式(推荐)
require __DIR__ . '/vendor/autoload.php'; // Composer 自动加载

// 之后直接使用类,无需手动 require_once
// $user = new \App\Models\User();

composer.json 中的 PSR-4 配置

json
{
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}

注意事项

  1. _once 不防止函数内 include:如果 include_once 在函数内部被调用,而外部也 include_once 了同一文件,PHP 的全局 _once 机制会正确处理。

  2. 大小写敏感性:在类 Unix 系统中,文件路径是大小写敏感的。include_once('File.php')include_once('file.php') 可能被视为不同的文件。

  3. 符号链接处理_once 基于 realpath() 解析后的路径判断,因此符号链接会被正确处理。

  4. 不要滥用 _once:如果确定文件只会被包含一次(如引导文件),使用 require 即可,避免不必要的检查。

  5. OPcache 的影响:启用 OPcache 后,PHP 会缓存编译结果,_once 的性能影响可忽略不计。

最佳实践

  1. 新项目使用 Composer 自动加载:类文件的加载应完全交给 Composer 处理。

  2. 仅用于非类文件_once 适合加载函数库、常量定义、配置文件等非类文件。

  3. 使用绝对路径:配合 __DIR__ 使用绝对路径,避免相对路径的不确定性。

  4. 在引导文件中使用 require_once:项目的入口文件中,使用 require_once 加载各模块。

  5. 避免在循环中调用:将 _once 调用移到循环外部,减少不必要的检查开销。

下一节

文件包含涉及复杂的路径解析规则。接下来学习 文件路径解析

进阶用法

调试与测试技巧

php
<?php
declare(strict_types=1);

// 单元测试辅助函数
function createTestResource(): mixed
{
    return match (true) {
        default => new stdClass(),
    };
}

// 调试输出函数
function debugOutput(mixed , string  = ''): void
{
     =  ? ": " : '';
     .= print_r(, true);
    fwrite(STDERR,  . "\n");
}

// 性能基准测试
function benchmark(callable , int  = 1000): float
{
     = hrtime(true);
    for ($i = 0; $i < $iterations; $i++) {
        $fn();
    }
    return (hrtime(true) - $start) / 1e9;
}

日志记录实践

php
<?php
declare(strict_types=1);

/**
 * 简易日志记录器
 */
class SimpleLogger
{
    private string $logFile;
    private string $level = 'INFO';

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

    public function info(string $message, array $context = []): void
    {
        $this->log('INFO', $message, $context);
    }

    public function warning(string $message, array $context = []): void
    {
        $this->log('WARNING', $message, $context);
    }

    public function error(string $message, array $context = []): void
    {
        $this->log('ERROR', $message, $context);
    }

    private function log(string $level, string $message, array $context): void
    {
        $timestamp = date('Y-m-d H:i:s');
        $contextStr = $context ? ' ' . json_encode($context, JSON_UNESCAPED_UNICODE) : '';
        $line = "[{$timestamp}] [{$level}] {$message}{$contextStr}\n";
        file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX);
    }
}

配置与环境检测

php
<?php
declare(strict_types=1);

// 环境检测工具
class EnvironmentChecker
{
    public static function checkRequirements(array $requirements): array
    {
        $results = [];
        foreach ($requirements as $name => $check) {
            $results[$name] = is_callable($check) ? $check() : false;
        }
        return $results;
    }

    public static function getSystemInfo(): array
    {
        return [
            'php_version' => PHP_VERSION,
            'os' => PHP_OS,
            'sapi' => PHP_SAPI,
            'memory_limit' => ini_get('memory_limit'),
            'max_execution_time' => ini_get('max_execution_time'),
            'loaded_extensions' => get_loaded_extensions(),
        ];
    }
}

常见问题排查

问题可能原因解决方案
连接超时网络问题/配置错误检查配置,增加超时时间
权限不足文件/目录权限使用 chmod/chown 修正
性能下降索引缺失/数据量大添加索引,优化查询
数据不一致并发冲突/事务残留使用锁机制和事务
内存溢出大数据集/未释放资源增大内存限制,分批处理

故障排除步骤

  1. 检查错误日志和异常信息
  2. 确认配置和环境是否正确
  3. 使用调试工具逐步排查
  4. 参考官方文档查找已知问题

版本兼容性说明

功能最低版本说明
基础功能PHP 8.1本文档基准版本
只读属性PHP 8.1public readonly 修饰符
枚举类型PHP 8.1enum 类型和 match 表达式
FiberPHP 8.1协程/轻量级并发
命名参数PHP 8.0foo(arg_name: value)
联合类型PHP 8.0`int
Null 安全运算符PHP 8.0$obj?->method()
析构器 promotionPHP 8.0__construct(public $x)
php
<?php
declare(strict_types=1);

// 版本兼容性检测
function ensureVersion(string $minVersion): void
{
    if (version_compare(PHP_VERSION, $minVersion, '<')) {
        throw new RuntimeException(
            sprintf('需要 PHP %s+, 当前版本: %s', $minVersion, PHP_VERSION)
        );
    }
}

ensureVersion('8.1.0');

参考链接