PHPで動的にクラス名を指定するとオートロードできない件

PHP で動的にクラス名を指定する場合、現在いる名前空間が修飾されないため、
クラス名のみで名前空間内のクラスを指定して new するとクラスを見つけられず致命的なエラーになります。

エラーになる例

クラスファイルのパス構成

例えば PSR-0 規約に準拠して以下のような構成で Car クラスと CarCreator クラスを配置していたとします。

  • /var/www/sample/Vendor/SampleVendor/Entity
    • Car.php
    • CarCreator.php

Car.php

SampleVendor\Entity の名前空間に Car クラスを定義します。

<?php
namespace SampleVendor\Entity

class Car {}

CarCreator.php

この状態で Car クラスと同じ名前空間の CarCreator クラスから Car クラスのインスタンスを生成してみます。(autoload.php は既に読み込まれている状態とします。)

<?php
namespace SampleVendor\Entity

class CarCreator {
    public function create() {
        // 🙆🏻‍♀️ OKパターン:
        // 暗黙的に現在の名前空間が修飾され、問題なくインスタンスの生成ができる
        $car = new Car();

        // 🙅‍♀️ NGパターン:
        // 名前空間が修飾されずクラスが見つからないためエラー
        // Fatal error: Class 'Car' not found in /var/www/...
        $className = 'Car';
        $car = new $className();
    }
}

このエラーを解決するにはどうすればいいでしょうか?

解決方法

完全修飾形式(名前空間まで含めた完全な指定)でクラスを指定するか、Car クラスを別途 use 句で指定することで解決できます。

以下は完全修飾形式でクラスを指定する場合の例です。

<?php
namespace SampleVendor\Entity

class CarCreator {
    public function create() {
        $className = __NAMESPACE__ . '\\' . 'Car';
        $car = new $className();
    }
}

マジック定数の __NAMESPACE__ で現在の名前空間名の文字列を取得できるので、これを先頭に付けてクラスを指定します。

フレームワーク開発はジグソーパズルと似ている

こんにちは。ウェブ系システム開発者の山本です。

ウェブ開発のフレームワークって最近は本当にたくさんありますね。メジャーなところだとCakePHPや、Symfonyzend frameworkCodeIgniter、あとはRubyでお馴染みruby on railsなどでしょうか。

ここしばらくPHPフレームワークでの開発を継続的にやっています。去年はCakePHP1とCakePHP2を、そして今年はSymfonyを使った実務開発をしています。楽しくコーディングの日々を続ける中で、ジグソーパズルとフレームワークを用いた開発ってとても似ているなってふと思いました。

とにかく手を動かして答えを探る

パソコンでカタカタプログラムを打ち込むフレームワーク開発と、ジグソーパズル。
この2つの一体何が似ているのか。

直感的に感じた共通点は手を動かすことです。

どちらも、とにかく手を動かし探って作り上げていきます。ピースを組み立てていくと徐々に絵が見えてきます。

ピースをはめるまでのもやもやした感じ、
そしてピースがカチッとハマった時の心が小さく踊る感じ。

フレームワークに用意されている機能や仕組みはパズルのピース

必要なピースは、基本的にフレームワーク上に全て用意されています。

用意された無数の部品が正にピースのようなもので、ピッタリとピースをはめるまでの過程が苦労します。ですが、ぴったりとはまる瞬間が気持ちいいし楽しいです。

そしてピースがはまる度に徐々にリズムが生まれてきます。ジグソーパズルも後半になるほど楽ですね。

検索フォームを作った時に強く実感

パズルな感覚はデータ検索フォームを作った時に強く実感しました。

フォームに入力した文字でデータベースを検索して表示するシンプルな画面。
なにもフレームワークの仕組みを理解してまで頑張って検索しなくても、自分でゴリゴリ書いて実現してしまえばいいじゃんと思いながらも、フレームワークの仕組みを使って頑張って実装することにしました。

ピースが合うまでが、ものすごーく苦労しました!

そもそも、どうプログラムを書いていいのかわからないですし、
フレームワークで一体なにが行われているのか分からない!

思い通りにシステムが動かない時間が続きました。

・・・正直もうあまりやりたくない作業です。

開発を楽にするためのフレームワークなのに、
こんなに苦労して意味があるの?ってちょっと思いました。

でもでも!

それでも、マニュアルや動作サンプルを調べて動かしてみて、やっと動いた時に真価が分かりました。間違いなく苦労した以上の価値がありました。

動くまで苦労するけど、動いてしまえば「あれ、もう全部出来てる」って。データを検索できるし、表示もできる、入力チェックもできる、更にページングの実装まで出来上がってる…!

自力でこれらをコーディングする大変さも知っている自分にとっては、これがフレームワークの力なのかと唖然としましたね。

フレームワークに用意された仕組みを使って実現すること

せっかくフレームワークを使っているのですから、
是非フレームワークの仕組みを使ってやりたい事を実現することを検討してみてください。

必ずしもそれが最良とは限らないですし、時と場合によりますが
動いてしまえば苦労した以上の利益が得られることも多くあります。

新たなフレームワークやプラグインの使い方を知ることで、
今後の開発が圧倒的に楽になります。

フレームワークの理解を深めることは今後への投資とも言えるでしょう。

PHP5で定数の定義はどうするべきか

今まではPHPで定数といえば普通に大文字の変数名で定義しておくか、define関数を使って定義するかだったと思います。

変数の定義はのメリットとにかく手軽ということが挙げられます。制約などを考えずにいつもの変数通りに使えます。
逆にデメリットは変数であるということ。簡単に上書きできてしまいますし、グローバルな名前空間も汚してしまいます。「変」数なんです。「定」数ではありません。

//定数絡みの命名はよく大文字アンダーバー区切りが用いられる
$APPLE = 'リンゴ';
$ORANGE = 'オレンジ';

//もちろん配列や関数などによる定義も自由に可能
$FROUIT_LIST = array($APPLE, $ORANGE);

//上書きされる可能性もつきまとう
$ORANGE = 'グリーン';

もう一つのdefine関数の定義はしっかり定数です。define関数で定義する時、通常通り式による定義も出来ます。
デメリットとしては名前空間がグローバルなこと。変数とは名前は別扱いですがいろんなファイルやライブラリでどんどん定数を定義していくと重複の可能性も出てきますし、近年の名前空間を取り入れる流れからするとあんまりよくないです。PHPであらかじめ定義されている定数も結構な数あるためちょっと使いにくい。

define('ECODE_NOT_FOUND', 100);
define('ECODE_BAD_REQUEST', 200);
//式や関数による定義も可能
define('NEXT_MONTH', date('n', mktime(0, 0, 0, date('n')+1, date('j'), date('Y'))));

if( !file_exists('C:\define.txt') ) {
    //通常の変数と同じように値の参照が可能
    echo 'エラーコード:' . ECODE_NOT_FOUND
    die();
}

PHP5ではオブジェクト指向の機能が本格的に強化されJavaのような要素もじゃんじゃん取り入れられています。
この記事を書いている現在はPHP5.3が主流かと思います。オブジェクト指向化の中でクラス定数というものも出てきました。位置付けとしてはクラスに属する定数です。クラス定数はクラスの中でconstキーワードを用いて定義します。こんな感じで使えます。

//クラス定数を定義する専用のクラスを作るのもあり
class CONFIG {
    const TOPPAGE_URL = 'http://https://sousaku-memo.net/';
    const DEBUG_MODE = true;
    //以下の定義はエラーが発生するので注意
    //配列や式(Expression)による柔軟な定義が出来ないのが痛い…
    //const FAILD_ARRAY_DEFINE = array(10,20,4);
}

//参照するときは::(ダブルコロン)演算子を用いる。
//クラス名の接頭辞が付くことにより名前空間を汚さないのはGood
echo sprintf('創作メモ帳トップページ', CONFIG::TOPPAGE_URL);

んー、どれもメリットデメリットがあります…
PHP6にはクラス定数の強化を望みます。

自分はどうしているかというと、クラス定数はやっぱり式による定義が出来ないのがつらいのでdefine関数で定義をしています。また、あちこちで定義すると分からなくなるのでまとめてコメント付きで定義しています。