はじめに
前回の記事で、外部ファイルを読み込む基本構文 include を解説し、その注意点の一つとして「同じファイルを複数回読み込むと、関数やクラスの再定義エラーを引き起こす可能性がある」ことに触れました。今回取り上げる include_once は、まさにこの問題を解決するための構文です。
include_once は、include とほぼ同じ役割を持ちながら、同じファイルが既に一度読み込まれていれば、2回目以降は読み込みをスキップするという、重複読み込み防止の仕組みを内蔵しています。複数の場所から同じユーティリティファイルやクラス定義ファイルが読み込まれる可能性がある、中規模以上のプロジェクトにおいて非常に重要な構文です。本記事では基本的な使い方から、include との挙動の違い、実践的な活用例まで詳しく解説します。
構文概要
| 項目 | 内容 |
|---|---|
| 構文名 | include_once |
| 種別 | 言語構文(関数ではない) |
| 構文例 | include_once 'path/to/file.php'; |
| 戻り値 | 初回読み込み時はファイルの戻り値(またはデフォルトで1)。2回目以降はtrue |
| 失敗時の挙動 | includeと同様、警告を出すが処理は継続する |
| 対応バージョン | PHP 4以降 |
| 判定基準 | 読み込むファイルの絶対パスが同一かどうかで判定される |
重複判定の仕組み(イメージ図)
main.php
<?php
include_once 'utils.php'; ← 1回目:utils.phpの内容が実行される
include_once 'utils.php'; ← 2回目:既読み込み済みのため、スキップされる
helper.php
<?php
include_once 'utils.php'; ← 別ファイル経由でも、同じutils.phpなら
スキップされる(絶対パスで判定)
include_once '/full/path/utils.php'; ← 相対パス表記が違っても
解決後の絶対パスが同じなら
やはりスキップされる
ポイントは、include_once が**「このファイルパスは既に読み込んだかどうか」を、PHP内部で絶対パスに正規化した上で記録・判定している**という点です。これにより、相対パスの書き方が多少異なっていても、実際に指しているファイルが同じであれば正しく重複が検知されます。
実践サンプル7選
例1:基本的な使い方(重複読み込みのスキップを確認)
<?php
// greeting.php: <?php echo "greeting.phpが読み込まれました\n";
include_once 'greeting.php'; // 1回目:実行される
include_once 'greeting.php'; // 2回目:スキップされる(何も出力されない)
echo '処理完了' . PHP_EOL;
例2:関数定義ファイルの安全な読み込み
<?php
// functions.php:
// <?php
// function formatCurrency(int $amount): string {
// return number_format($amount) . '円';
// }
function loadFunctions(): void
{
// この関数が複数回呼ばれても、functions.phpの関数定義は
// 一度しか実行されないため「関数の再定義」エラーを防げる
include_once __DIR__ . '/functions.php';
}
loadFunctions();
loadFunctions(); // 2回呼んでも安全
echo formatCurrency(15000) . PHP_EOL;
例3:複数のファイルが共通の依存ファイルを読み込むケース
<?php
// Database.php: <?php class Database { public function connect() { echo "接続しました\n"; } }
// ModuleA.php:
function loadModuleA(): Database
{
include_once __DIR__ . '/Database.php';
return new Database();
}
// ModuleB.php:
function loadModuleB(): Database
{
include_once __DIR__ . '/Database.php'; // ModuleAと同じファイルを読み込む
return new Database();
}
/**
* ModuleAとModuleBの両方が同じDatabase.phpに依存していても、
* include_onceのおかげでクラスの再定義エラーが起きない
*/
$dbA = loadModuleA();
$dbB = loadModuleB();
$dbA->connect();
例4:異なる相対パス表記でも正しく重複を検知する確認
<?php
// config.php: <?php return ['loaded' => true];
$config1 = include_once __DIR__ . '/config.php'; // 1回目:配列が返る
$config2 = include_once __DIR__ . '/./config.php'; // 表記は違うが同じファイル → trueが返る
var_dump($config1); // array
var_dump($config2); // bool(true) (2回目は常にtrueが返る点に注意)
例5:プラグインシステムでの安全な読み込み
<?php
class PluginLoader
{
private array $loadedPlugins = [];
/**
* 同じプラグインが複数の経路から読み込み要求されても、
* include_onceにより安全に一度だけ初期化される
*/
public function load(string $pluginPath): void
{
$result = include_once $pluginPath;
if ($result !== false) {
$this->loadedPlugins[] = basename($pluginPath);
}
}
public function getLoadedPlugins(): array
{
return $this->loadedPlugins;
}
}
$loader = new PluginLoader();
// $loader->load(__DIR__ . '/plugins/analytics.php');
// $loader->load(__DIR__ . '/plugins/analytics.php'); // 2回目は無視される
例6:includeとの挙動の違いを比較するデモ
<?php
// counter_increment.php: <?php $GLOBALS['counter'] = ($GLOBALS['counter'] ?? 0) + 1;
$GLOBALS['counter'] = 0;
include 'counter_increment.php';
include 'counter_increment.php';
echo "include使用時: {$GLOBALS['counter']}" . PHP_EOL; // 2(2回とも実行される)
$GLOBALS['counter'] = 0;
include_once 'counter_increment.php';
include_once 'counter_increment.php';
echo "include_once使用時: {$GLOBALS['counter']}" . PHP_EOL; // 1(1回しか実行されない)
例7:クラス定義を含むライブラリの安全な共有読み込み
<?php
namespace App\Support;
/**
* このようなヘルパー関数を複数の場所から呼び出される設計の場合、
* 内部でinclude_onceを使うことで、どこから何度呼ばれても安全になる
*/
function ensureLoaded(string $file): void
{
include_once $file;
}
// Validator.php: <?php namespace App\Support; class Validator { public function isValid($v) { return !empty($v); } }
ensureLoaded(__DIR__ . '/Validator.php');
ensureLoaded(__DIR__ . '/Validator.php'); // 安全に重複がスキップされる
$validator = new Validator();
var_dump($validator->isValid('test'));
関連機能との比較
| 構文 | 役割 | include_onceとの違い |
|---|---|---|
include_once | ファイルを一度だけ読み込む | 本記事の対象。失敗時は警告のみで処理を継続する |
include | ファイルを読み込むたびに毎回実行する | 重複チェックを行わない、よりシンプルな構文(前回記事を参照) |
require_once | ファイルを一度だけ読み込む(厳格) | 失敗時に致命的エラーとなる、include_onceの厳格版 |
require | ファイルを読み込むたびに毎回実行する(厳格) | 重複チェックを行わない、require_onceとの対比 |
| Composerのオートローダー | クラスを必要な時に自動的に、重複なく読み込む | include_onceを手動で書く必要自体をなくす、現代的な代替手段 |
よくある落とし穴(注意点)
- ファイルが見つからなくても処理が止まらない
includeと同様、include_onceも失敗時には警告が出るだけで、スクリプトの実行は継続されます。クラス定義など、なければ処理が成立しない重要なファイルにはrequire_onceを使うべきです。 - 2回目以降の戻り値は常に
trueになる 初回読み込み時はファイルが明示的に返した値(または1)が戻り値になりますが、2回目以降は常にtrueが返ります。ファイルの内容(戻り値)を毎回取得したい場合、include_onceではなく通常のincludeを使う必要があります(例4を参照)。 - パスの正規化によって、わずかな表記違いも同一ファイルとして扱われる 相対パスの書き方が少し異なっていても、解決後の絶対パスが同じであれば重複と判定されます。これは便利な仕組みですが、「別のファイルとして明示的に扱いたかったのに意図せずスキップされた」という誤解を招くこともあるため、仕組みを理解しておくことが重要です。
- シンボリックリンクなど特殊な状況での挙動 シンボリックリンクを介して同じ実体のファイルに複数の異なるパスからアクセスする場合、PHPのバージョンや環境によって重複検知の挙動に差が出ることがあります。
- 現代的なプロジェクトではComposerのオートローダーが優先される クラスの読み込みにおいては、Composerのオートローダーを使えば
include_onceを手動で書く必要自体がなくなります。新規プロジェクトでは、クラスの重複読み込み対策はオートローダーに任せ、include_onceは設定ファイルやヘルパー関数群など、オートロードの対象にならないファイルに限定して使うのが現代的なアプローチです。
まとめ
| 観点 | まとめ |
|---|---|
| 何をする構文か | 指定したファイルが既に読み込まれていなければ読み込み、既読み込み済みならスキップする |
| 主な用途 | 関数・クラス定義ファイルの重複読み込み防止、複数モジュールが共有する依存ファイルの安全な読み込み |
includeとの違い | 同一ファイルの重複実行を防ぐかどうか |
| 判定基準 | ファイルの絶対パス(正規化後)が同一かどうか |
| 注意点 | 失敗時も処理が継続すること、2回目以降の戻り値はtrue固定、Composerオートローダーとの使い分け |
include_once は、「関数やクラスの再定義エラー」という、複数ファイルが絡む開発で頻発しがちな問題を未然に防ぐための、実用性の高い構文です。Composerのオートローダーが普及した現代でも、設定ファイルや特定のヘルパー関数群を安全に共有する場面で、その役割を正しく理解しておくことが重要です。
