こんにちは、かつコーチです。
前回はtry-catchによる例外処理を解説し、独自の例外クラスを作る方法にも触れました。
クラスの数が増えてファイルを分割するようになると、次にぶつかるのが「クラス名の衝突」と「ファイルの読み込み管理」という問題です。
今回は、この2つを解決する「名前空間(namespace)」と「autoload」を扱います。
これはLaravelを含む多くのフレームワークの土台になっている考え方なので、少し丁寧に見ていきましょう。
なぜ名前空間・autoloadが必要なのか
ファイルが増えると起きる問題
前回、InsufficientStockException のような独自の例外クラスを作りました。
実際の開発では、こうしたクラスをどんどん作っていき、1ファイル1クラスの形でファイルを分割していくのが一般的です。
project/
├── Exceptions/
│ └── InsufficientStockException.php
├── Models/
│ └── User.php
└── index.php
ファイルを分割すると、次の2つの課題が出てきます。
- クラス名の衝突:別々のファイルで同じクラス名(例:
User)を使ってしまうと、PHPはどちらのことか区別できない - 読み込みの手間:クラスを使うたびに
require "Exceptions/InsufficientStockException.php";と書くのは非効率で、書き忘れも起きやすい
私が最初に複数ファイルでクラスを扱うプログラムを書いたときは、require をファイルの先頭にひたすら並べていました。
ファイルが増えるたびに require の行も増え、しかも読み込む順番を間違えると「未定義のクラスです」というエラーが出るので、正直かなり面倒に感じていました。
この2つの課題をまとめて解決してくれるのが、名前空間とautoloadです。
名前空間(namespace)の基本
手順1:名前空間を宣言する
名前空間は、クラスに「所属グループ」を与える仕組みです。
<?php
// Exceptions/InsufficientStockException.php
namespace App\Exceptions;
class InsufficientStockException extends \Exception
{
}
?>
namespace App\Exceptions; は、ファイルの一番先頭(<?php の直後)に書く必要があります。
これにより、このクラスの正式な名前は App\Exceptions\InsufficientStockException になります。
手順2:名前空間をまたいで同名クラスを共存させる
名前空間を使うと、同じ「クラス名」でも別のグループとして共存できます。
<?php
// Models/User.php
namespace App\Models;
class User
{
public string $name;
}
?>
<?php
// Admin/User.php
namespace App\Admin;
class User
{
public string $role;
}
?>
どちらも User という同じクラス名ですが、正式には App\Models\User と App\Admin\User という別物として扱われるので、名前が衝突しません。
手順3:useで名前空間を短く呼び出す
クラスを使う側では、use 文で名前空間をインポートしておくと、毎回フルパスを書かずに済みます。
<?php
namespace App;
use App\Models\User;
use App\Admin\User as AdminUser; // 同名になる場合は as で別名をつける
$user = new User();
$user->name = "かつコーチ";
$admin = new AdminUser();
$admin->role = "管理者";
?>
同じ名前のクラスを両方使いたい場合は、as で別名(エイリアス)をつけることで区別できます。
autoloadの基本
なぜrequireの手動管理が大変なのか
名前空間でクラス名の衝突は解決できますが、「そのファイルをいつ読み込むか」という問題はまだ残っています。
❌ Before:使うクラスの分だけrequireを並べる
<?php
require __DIR__ . "/Exceptions/InsufficientStockException.php";
require __DIR__ . "/Models/User.php";
require __DIR__ . "/Models/Product.php";
require __DIR__ . "/Services/OrderService.php";
// クラスが増えるたびにここが増え続ける…
use App\Services\OrderService;
$service = new OrderService();
?>
クラスを1つ追加するたびに require を1行追加する必要があり、ファイルの依存関係が複雑になると読み込み順序でハマることもあります。
私は一度、同じファイルを別の場所からも require してしまい、次のようなエラーに遭遇したことがあります。
Fatal error: Cannot redeclare class App\Models\User
「同じクラスを2回読み込んでしまった」ことが原因で、原因箇所を探すのにかなり時間がかかりました。
手順1:spl_autoload_registerで自動読み込みの仕組みを作る
✅ After:autoloadで自動的にファイルを読み込む
PHPには、「クラスが使われた瞬間に、対応するファイルを自動で読み込む」仕組みがあります。
<?php
spl_autoload_register(function ($className) {
// "App\Models\User" → "App/Models/User.php" に変換して読み込む
$path = __DIR__ . "/" . str_replace("\\", "/", $className) . ".php";
if (file_exists($path)) {
require $path;
}
});
use App\Models\User;
$user = new User(); // ここで初めてUser.phpが自動的に読み込まれる
?>
spl_autoload_register() に登録した関数は、「まだ読み込まれていないクラスが使われたタイミング」で自動的に呼び出されます。
名前空間 App\Models\User をファイルパス App/Models/User.php に変換するルールを決めておけば、require を手動で並べる必要がなくなります。
手順2:Composerのautoload(PSR-4)を使う
実務では、spl_autoload_register() を自分で書くことはあまりなく、Composerというパッケージ管理ツールのautoload機能を使うのが一般的です。
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
composer.json にこのように設定しておくと、「App\ から始まる名前空間は src/ フォルダの中を探す」というルールが自動生成されます。
<?php
require __DIR__ . "/vendor/autoload.php"; // これ1行だけでOK
use App\Models\User;
$user = new User();
?>
vendor/autoload.php を1回読み込むだけで、あとは名前空間さえ正しく書けば、必要なクラスファイルが自動的に読み込まれるようになります。
このPSR-4という規約(名前空間とフォルダ構成を一致させるルール)は、Laravelを含む多くのモダンなPHPフレームワークの標準になっています。
つまずきやすいポイント:namespaceの宣言忘れ
クラス名がグローバル空間で衝突する
もう一つよくあるつまずきが、名前空間の宣言忘れです。
❌ Before:namespaceを宣言せずにクラスを作る
<?php
// Exceptions/InsufficientStockException.php
// namespaceの宣言を忘れている
class InsufficientStockException extends Exception
{
}
?>
このクラスは「グローバル名前空間」というPHPのデフォルトの場所に定義されます。
他のファイルでもnamespace宣言のないクラスを作っていると、ファイルを分けているのにクラス名が衝突してしまい、こちらも Cannot redeclare class のエラーにつながります。
✅ After:ファイルの先頭で必ずnamespaceを宣言する
<?php
// Exceptions/InsufficientStockException.php
namespace App\Exceptions;
class InsufficientStockException extends \Exception
{
}
?>
余談ですが、グローバル名前空間にある Exception クラスを名前空間の中から使う場合は、\Exception のように先頭にバックスラッシュをつけて「これはグローバルのクラスですよ」と明示する必要があります。
私はこのバックスラッシュを忘れて、「App\Exceptions\Exception というクラスが見つかりません」というエラーに何度か遭遇しました。
「フォルダ構成を作ったら、まずファイルの先頭にnamespace宣言を書く」という順番を習慣にしてから、この手のミスはかなり減りました。
応用:Laravelにおけるnamespace・autoload
Laravelはこの仕組みの上に成り立っている
Laravelのコントローラやモデルを開くと、必ずファイルの先頭に namespace App\Http\Controllers; のような宣言があります。
<?php
namespace App\Http\Controllers;
use App\Models\User;
class UserController
{
public function index()
{
$users = User::all();
return view("users.index", ["users" => $users]);
}
}
?>
Laravelで新しいクラスを作るたびに require を書いた記憶がある人は少ないと思いますが、それは裏側でComposerのPSR-4 autoloadが自動的に働いているからです。
素のPHPで「名前空間とファイルパスを一致させる」「autoloadで自動的にクラスを読み込む」という仕組みを理解しておくと、Laravelのフォルダ構成や use 文の意味が、単なる「お作法」ではなく「なぜそうなっているか」まで含めて理解できるようになります。
まとめ
この記事のポイント
- 名前空間(namespace)は、クラスに「所属グループ」を与えてクラス名の衝突を防ぐ仕組み
useで名前空間をインポートし、同名クラスはasでエイリアスをつけて区別するspl_autoload_register()で、クラス使用時に自動でファイルを読み込む仕組みを作れる- 実務ではComposerのPSR-4 autoloadを使い、
requireの手動管理から解放される - namespace宣言を忘れるとクラス名がグローバル空間で衝突し、
Cannot redeclare classエラーの原因になる
次に読むべき記事
名前空間とautoloadでファイル分割の土台が整ったところで、次はいよいよPHPのオブジェクト指向の中心となる「クラスとインスタンス」を基礎から学んでいきます。
→ 次の記事:PHPのオブジェクト指向入門:クラスとインスタンスの基本
