LoginSignup
70

More than 5 years have passed since last update.

オートローダはなぜ名前空間がいいのか

Last updated at Posted at 2016-03-09

初心phperがいきなりフレームワークを自作したときにオートローダがどういう仕組みで動いているかわからなかったので、設計してみてわかったことメモです。今更ありふれたネタかもしれませんがお許しください。
最初は名前空間なんぞいらんやんけ!とか思ってましたが、徐々に良さに気づいてきたので順に書いてみます。

はじめに

composer使いましょう!
特殊な案件でないかぎり既成フレームワークを使いましょう!
ということで、実用ではなく理解のための記事になりますのでご容赦を。

名前空間を用いているのでPHP5.3以降でのおはなしですが、オートロードそのものは名前空間とは全くの別モノです。
名前空間という仕組みが導入されたことによって、オートローダ設計が格段にやりやすく、わかりやすくなりました。

基本はrequire_once(require)だった

オブジェクト指向で開発をすると1クラス1ファイルの原則で書いていきます。
スクリプトAでクラスBとCとDを使いたければ、スクリプトAの先頭に

A.php
require_once B
require_once C
require_once D

を書く必要がありました。
ただしこれだと、利用するクラスと同じだけのrequire_once行を追加しなければいけません。

さて、このスクリプトAで、うしろのほうの行でクラスEが$e = new E();されたとします。
Eはrequire_onceされていません。このままだとクラスEは存在しないというエラーが出てしまいます…

オートロード関数という保険があった

クラスのオートローディングのためのspl_autoload_registerという関数があります。
(他にもオートロードはいくつか関数がありますがここではこれだけ使用します)
この関数を、どんなスクリプトが始まるよりも先にたった1度だけ実行しておきましょう。
引数にはコールバック関数をいれます。

A.php
spl_autoload_register('myLoader');

function myLoader($class) {
    require_once  $class . '.php';
    // new E() される前に自動で ./E.phpがrequire_onceされる
}

require_once B
...
..

このようにしておくと
クラスの読み込みができなかったとき、そのクラスを$classに代入し、関数myLoader($class)を実行してくれます。
spl_autoload_registerは、 クラス読み込み失敗時に実行する関数を登録するための関数 です。
これでrequireが漏れることはありません!

…しかし、大半のクラスはスクリプトAと同じディレクトリにあるものの、クラスEはシステム上とても重要な機能を扱うものであったため別のディレクトリに置いてありました。
そもそもクラスファイルなんて、いろいろな場所に散り散りになっていたりすることがあるので、この書き方ではどうしようもありません。

クラス名とクラスファイルパスの関係に決まりをつくったら

「このクラスは、このパスにある何々というphpファイル」というのがしっかりわかっている、もしくは決まっていさえすればいいのです。
例えばこのように

mappedLoader.php
spl_autoload_register('myLoader');

function myLoader($class) {
    $classMap = array(
        'Hoge' => '/var/www/html/classes/hogeClass.php',
        'Foo' => '/var/www/html/classes/bar/foo.php',
        'E' => '/var/www/html/classes/core/util/E.php',
        ...
        ..
    );
    require_once $classMap[$class];
}

あらかじめマップを作っておけば全然問題ナシですね!もはやrequireとか必要なさそうです!
でもこれ、クラスを新しく作成したときにmyLoaderに追加するのを忘れそうで悪寒が止まりません。
汎用性がゼロです。どうすればよいでしょうか。

クラス名にも階層のあるパスがついてたらいいのにな

システムのルートを$root = '/var/www/html'とします。
ここにindex.phpと、/classesがあります。
上記のmappedLoader.phpの例だと、クラスEはルートから見て/classes/core/utilというパスにあるE.phpというファイルだということになっています。面倒ですね。
いっそ/classes/core/util/Eとかいうクラス名 だったら.phpつけてrequire_onceするだけでいいのに…とか思っちゃいます。


さすがにスラッシュをつけるとホンモノのパスみたいでなんだかマズいので、せめて逆に傾けてやりましょう。
\classes\core\utilという仮想的な空間にあるE ということにしちゃえば、バックスラッシュをスラッシュに置き換えて末尾に拡張子をつけるだけで実際のファイルパスに変換できます。
このクラスに対する仮想的なパスを名前空間といいます。

各ファイルの先頭にクラス専用のパスをつけてあげよう

クラスファイルはパスが決まっていますが、名前空間は自動的には決まりません。
必ずファイルの先頭に「このファイルにあるクラスはこの空間(仮想パス)に存在してるよ」ということを書いてあげなければいけません。

(SystemRoot/)classes/core/util/E.php
namespace classes\core\util

class E
{
    // 処理..
}

こうすると、E.phpが読み込まれたあとでクラスEを使うためには
$e = new \classes\core\util\E();とすることで可能となります。なんだか長くなっちゃいましたが、use文を使うことで解決できます。(ここでは説明しません)

名前空間が自動的に決まらないということは、つまり実際のファイルパスとは関係なくどんな名前空間をつけてあげてもいいのですが、それではメリットがありません。
次のようにルールを決めてみましょう。

  • 名前空間は、システムのルートディレクトリ以降のクラスファイルパスの/\に置き換えたものとする。
  • クラスファイル名は、(クラス名).phpとする。

このルールをオートローダに適用させてみるとどうなるでしょうか。

名前空間を利用したオートローダ

システムルート直下にindex.phpとautoLoader.phpを置いておきます。

autoLoader.php
<?php

class autoLoader
{
    private $system_root;

    public function __construct($root)
    {
         // ルートディレクトリの設定
         $this->system_root = $root;
    }

    public function register()
    {
        spl_autoload_register(array($this, 'myLoader'));
    }

    public function myLoader($class)
    {
        // 最初のスラッシュを含めるかどうかは適宜調整(ここでは取り除く)
        $classNamespace = ltrim($class, '\\');
        // バックスラッシュを右に倒してルートと拡張子を結合
        $classFileName = $this->system_root . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $classNamespace) . '.php';
        // クラスファイルが存在して読み込めるか確認
        if(is_readable($classFileName)){
            require_once $classFileName;
            return true;
        } else {
            return false;
        }
    }
}

index.php
<?php

// ルートディレクトリ
$root = __DIR__;

// オートローダ読み込み
// これだけはrequireしなければいけない
require_once $root . 'autoLoader.php';
$autoloader = new autoLoader($root);
$autoloader->register();

$e = new \classes\core\util\E();
$e->setVar("baz");
...
..

このようにすると、
$e = new \classes\core\util\E();がコールされた段階であらかじめ登録されていたmyLoaderが動作し、ルールに従って($root/)classes/core/util/E.phpをrequire_onceしてくれるというわけです。
各ファイルに名前空間をきちんと設定し、new時に名前空間付きのクラス名を記述しておけばオートローダ本体を触る必要はありません。

もっとルールを含ませれば名前空間に縛られない多様なオートロードもできそうですね。
以上、オートローダと名前空間についてざっくり解説でした。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
70