[PHP]include_onceとは?ファイルの重複読み込みを防ぐ方法を徹底解説

PHP

はじめに

前回の記事で、外部ファイルを読み込む基本構文 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を手動で書く必要自体をなくす、現代的な代替手段

よくある落とし穴(注意点)

  1. ファイルが見つからなくても処理が止まらない include と同様、include_once も失敗時には警告が出るだけで、スクリプトの実行は継続されます。クラス定義など、なければ処理が成立しない重要なファイルには require_once を使うべきです。
  2. 2回目以降の戻り値は常にtrueになる 初回読み込み時はファイルが明示的に返した値(または 1)が戻り値になりますが、2回目以降は常に true が返ります。ファイルの内容(戻り値)を毎回取得したい場合、include_once ではなく通常の include を使う必要があります(例4を参照)。
  3. パスの正規化によって、わずかな表記違いも同一ファイルとして扱われる 相対パスの書き方が少し異なっていても、解決後の絶対パスが同じであれば重複と判定されます。これは便利な仕組みですが、「別のファイルとして明示的に扱いたかったのに意図せずスキップされた」という誤解を招くこともあるため、仕組みを理解しておくことが重要です。
  4. シンボリックリンクなど特殊な状況での挙動 シンボリックリンクを介して同じ実体のファイルに複数の異なるパスからアクセスする場合、PHPのバージョンや環境によって重複検知の挙動に差が出ることがあります。
  5. 現代的なプロジェクトではComposerのオートローダーが優先される クラスの読み込みにおいては、Composerのオートローダーを使えば include_once を手動で書く必要自体がなくなります。新規プロジェクトでは、クラスの重複読み込み対策はオートローダーに任せ、include_once は設定ファイルやヘルパー関数群など、オートロードの対象にならないファイルに限定して使うのが現代的なアプローチです。

まとめ

観点まとめ
何をする構文か指定したファイルが既に読み込まれていなければ読み込み、既読み込み済みならスキップする
主な用途関数・クラス定義ファイルの重複読み込み防止、複数モジュールが共有する依存ファイルの安全な読み込み
includeとの違い同一ファイルの重複実行を防ぐかどうか
判定基準ファイルの絶対パス(正規化後)が同一かどうか
注意点失敗時も処理が継続すること、2回目以降の戻り値はtrue固定、Composerオートローダーとの使い分け

include_once は、「関数やクラスの再定義エラー」という、複数ファイルが絡む開発で頻発しがちな問題を未然に防ぐための、実用性の高い構文です。Composerのオートローダーが普及した現代でも、設定ファイルや特定のヘルパー関数群を安全に共有する場面で、その役割を正しく理解しておくことが重要です。

タイトルとURLをコピーしました