include_once 和 require_once
include_once 和 require_once 是 include 和 require 的变体。它们在包含文件之前会先检查该文件是否已经被包含过,如果已包含则跳过。这可以避免同一文件被重复包含导致的函数重定义、常量重复声明等问题。
前置知识
- 熟悉 include/require 的基本用法
- 了解 文件路径解析 规则
- 了解 Composer PSR-4 自动加载机制
基础概念
| 特性 | include_once | require_once | include | require |
|---|---|---|---|---|
| 重复包含 | 自动跳过 | 自动跳过 | 允许 | 允许 |
| 文件不存在 | E_WARNING | E_ERROR | E_WARNING | E_ERROR |
| 性能 | 有额外检查开销 | 有额外检查开销 | 无额外检查 | 无额外检查 |
| 基于路径判断 | 解析后的绝对路径 | 解析后的绝对路径 | N/A | N/A |
性能影响
_once 变体需要在每次调用时检查文件是否已被包含,这会带来轻微的性能开销。在现代项目中,使用 OPcache 后影响很小,但对于高频加载场景仍需注意。
语法结构
基本 include_once 用法
<?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
declare(strict_types=1);
// 确保核心库只被包含一次
require_once 'core/Database.php';
require_once 'core/Cache.php';
echo "核心库加载完成\n";include_once 在条件中使用
<?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
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
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
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
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
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
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
// 旧方式(不推荐)
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 配置
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}注意事项
_once 不防止函数内 include:如果
include_once在函数内部被调用,而外部也include_once了同一文件,PHP 的全局_once机制会正确处理。大小写敏感性:在类 Unix 系统中,文件路径是大小写敏感的。
include_once('File.php')和include_once('file.php')可能被视为不同的文件。符号链接处理:
_once基于realpath()解析后的路径判断,因此符号链接会被正确处理。不要滥用 _once:如果确定文件只会被包含一次(如引导文件),使用
require即可,避免不必要的检查。OPcache 的影响:启用 OPcache 后,PHP 会缓存编译结果,
_once的性能影响可忽略不计。
最佳实践
新项目使用 Composer 自动加载:类文件的加载应完全交给 Composer 处理。
仅用于非类文件:
_once适合加载函数库、常量定义、配置文件等非类文件。使用绝对路径:配合
__DIR__使用绝对路径,避免相对路径的不确定性。在引导文件中使用
require_once:项目的入口文件中,使用require_once加载各模块。避免在循环中调用:将
_once调用移到循环外部,减少不必要的检查开销。
下一节
文件包含涉及复杂的路径解析规则。接下来学习 文件路径解析。
进阶用法
调试与测试技巧
<?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
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
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 修正 |
| 性能下降 | 索引缺失/数据量大 | 添加索引,优化查询 |
| 数据不一致 | 并发冲突/事务残留 | 使用锁机制和事务 |
| 内存溢出 | 大数据集/未释放资源 | 增大内存限制,分批处理 |
故障排除步骤
- 检查错误日志和异常信息
- 确认配置和环境是否正确
- 使用调试工具逐步排查
- 参考官方文档查找已知问题
版本兼容性说明
| 功能 | 最低版本 | 说明 |
|---|---|---|
| 基础功能 | PHP 8.1 | 本文档基准版本 |
| 只读属性 | PHP 8.1 | public readonly 修饰符 |
| 枚举类型 | PHP 8.1 | enum 类型和 match 表达式 |
| Fiber | PHP 8.1 | 协程/轻量级并发 |
| 命名参数 | PHP 8.0 | foo(arg_name: value) |
| 联合类型 | PHP 8.0 | `int |
| Null 安全运算符 | PHP 8.0 | $obj?->method() |
| 析构器 promotion | PHP 8.0 | __construct(public $x) |
<?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');