名前空間

名前空間とは?

PHP5.3から名前空間という仕組みが登場しました。
名前空間とは何かというと、PHPの公式マニュアルにもあるように、ディレクトリに近い概念です。
クラスをディレクトリ構造のように階層的に分類することが出来る仕組み、と考えれば良いかもしれません。

これがどういう恩恵をもたらすかというと、ディレクトリと同じように、異なる名前空間同士では同じ名前のクラスが定義できるようになるわけです。それの何が嬉しいかという話ですが、例えば名前空間なしではPHPの標準クラスと同じ名前のクラスは絶対に作れません。例えばDateTimeクラスです。標準のDateTimeクラスが使い物にならない、あるいは標準のDateTimeクラスをラッピングして更に拡張したDateTimeクラスを作りたい、と思った人がいたとします。でもDateTime以外のクラス名は考えられない。このような場合でも、例えばDateTimeExとか、OriginalDateTimeとか、違う名前にするしかありませんでした。しかし、名前空間の概念を使えば、堂々とDateTimeというオリジナルクラスを定義することができるのです。

他には大人数での開発の場合で、サブシステム単位にクラス設計者が復数いる場合、サブシステムごとに名前空間が分かれていれば、お互いにクラス名の衝突を避けるという事を意識する必要がなくなります。これまでであればこのような場合、お互いに連絡を取り合って同じクラス名を付けないように気をつけるか、クラス名にサブシステム名のプレフィックスを付けるか、という方法しか衝突を避ける方法がありませんでした。後者の場合であれば結果的にクラス名が無駄に長くなってしまいます。

名前空間の仕組みを利用していないPHPのフレームワークの一つとしてZendFramework1がありますが、これはプレフィックス方式をとっており、結果的にクラス名が非常に長くなっています。
例えばZendFramework1のMySQLデータベースとの接続関連のクラスは、Zend_Db_Adapter_Pdo_Mysqlという長い名前になっています。
ZendFrameworkの次のバージョンであるZendFramework2では名前空間を利用しており、クラス定義自体は単にMysqlになっていたりします。

このように、名前空間は必須の仕組みではありませんが、クラス命名の自由度やパッケージングの意味で有用といえます。

名前空間の定義

名前空間の定義方法はいくつかありますが、最も一般的で扱いやすい方法は、クラス定義ファイルの頭にnamespaceキーワードて定義する方法です。

<?php

namespace FoodShop;

class Product
{

}

?>

これでProductというクラスはFoodShopという名前空間に属することになります。
もう一つ、定義してみましょう。

<?php

namespace FurnitureShop;

class Product
{

}

?>

次に、これらのクラスを利用する方法です。
それには、namespaceを含めたフルパスでクラス名を指定するか、名前空間の利用宣言をあらかじめするかの2つの方法があります。

フルパスで都度クラス名指定
<?php

$foodProduct = new \FoodShop\Product();

?>
あらかじめ利用宣言
<?php

use FoodShop\Product;

$foodProduct = new Product();

?>

useというキーワードの後に名前空間を指定します。これで、このPHPファイルの中では、単に"Product"と言うとFoodShop\Productのことを指すことになります。

これらはどちらでも良いですが、前者だとnewの時のクラス指定が長く、特に復数箇所でnewを行う場合はとても煩わしく、可読性も下がるため、基本的には後者が良いでしょう。

ただし、異なる名前空間で同じクラス名が存在する場合は注意が必要です。

<?php

use FoodShop\Product;
use FurnitureShop\Product;

$foodProduct = new Product();

?>
Fatal error: Cannot use FurnitureShopProduct as Product because the name is already in use in.....

何が問題かというと、FoodShopとFurnitureShopの両方のProductクラスを読み込んでしまったため、"Product"クラスというクラス名がバッティングしてしまった、ということになります。定義としては同名のクラスの定義は可能ですが、呼び出しの際は同時に呼び出すと、"Product"がどちらのProductのことなのか判別がつかないというわけです。
ではどうすればよいかというと、useをやめてフルパスで都度指定するしかないように思えますが、一時的に別名を割りてるという方法があります。

<?php

use FoodShop\Product as FoodProduct;
use FurnitureShop\Product as FurnitureProduct;

$foodProduct = new FoodProduct();

?>

useキーワードでの利用宣言に、asキーワードによる別名の定義を行うことができます。こうすることで、その定義ファイル内でのみ、別名を持つことが出来ます。
上の例で言えば、FoodShop\Productが、FoodProductという別名を持つことになります。

名前空間未指定はグローバル空間

PHP5.3から名前空間が導入されたわけですが、名前空間を利用せず実装することはもちろん可能です。ですが、実際には名前空間を意識しない場合には、全てグローバル空間に属することになります。それって実際は名前空間の概念が存在しないのとほとんど同じ状態なので、意識する必要が無いわけです。

グローバル名前空間って何か?
名前空間の定義は円マーク(\)で階層構造を取ることが出来るわけですが、グローバル空間は\です。つまり、例えば以下の2行は同じ意味です。

<?php

// \Exception
throw new Excetpion('hogehoge');

// \Exception
throw new \Excetpion('hogehoge');

?>

頭の\がグローバル空間を表すため、\Exceptionと表記すれば、「グローバル空間にあるExceptionクラス」ということになります。
では、\を省略したらどうなるか?その場合、実は現在の名前空間の直下にある、という意味になります。PHPファイルの頭にnamespaceの定義がない場合、それはグローバル空間であることになります。
つまり、頭にnamespace定義がなければ、\Exceptionと、Exceptionは同じクラスを指します。

逆に言えば、頭にnamespace定義がある場合は注意が必要になります。
例えば以下の様な場合。

<?php

namespace My;

// \My\Exception
throw new Excetpion('hogehoge');

// \Exception
throw new \Excetpion('hogehoge');

?>

この場合、2つのExceptionは異なるクラスを表します。
つまり、\Exceptionは前述のとおりグローバル空間直下のExcepionクラスを明示的に指定しているので、PHP標準のExceptionクラス。
しかし、namespaceとしてMyという名前空間が定義されているため、\を省略して単にExceptionとした場合には、実際は\My\Exceptionクラスを指すことになります。これって結構落とし穴で、あるオリジナルの名前空間に属するクラスを定義した場合、その中でPHP標準のクラスを利用しようとした場合に「そのようなクラスは存在しません」的なエラーが出て、あれ?となる場合があります。特に要注意なのがExceptionです。

<?php

namespace My;

class Hoge
{
    try {

    } catch (Exception $e) {

    }
}

?>

このように何気なく、try-catch構文のcatchで全ての種類のExceptionを捕まえようと、全ての例外クラスの大元である標準のExceptionクラスを指定したつもりになりそうですが、このクラスファイルのトップには"My"という名前空間定義があり、実際には\My\Exceptionを指してしまっているのです。しかも悪いことに、catchなんて滅多に通りませんから、この状態でもほとんどは動いてしまうわけです。そしてある日突然、想定外のエラーが発生した場合にcatchブロックに入り、クラス未定義エラーでスクリプトがストップしてしまう、という事になります。
これ、ほんと要注意です。

もちろんこの場合、実際に\MyExceptionというクラスを作っており、そのクラスを利用する事を意図していた場合なら上記のコードで問題は無いことになりますが。

名前空間の階層化

名前空間はディレクトリ構造と同じような考え方で階層化することが出来ます。
階層にするには、各階層の名前空間を円マーク(\)でつなぎます。
以下の例ではEcSiteという名前空間がグローバルに属しており、そのEcSiteという名前空間の下にModelという名前空間があり、更にその下にShopという名前空間がある。そしてその中にFoodShopというクラスを定義したことになります。

<?php

namespace EcSite\Model\Shop;

class FoodShop
{

}

?>

このように深い階層の名前空間に属するクラスを利用する場合、さすがに以下のようにフルパス指定はウザいコードになってしまいます。

<?php

$foodShop = new \EcSite\Model\Shop\FoodShop();

?>

なのでuseであらかじめ名前空間の利用宣言をします。

<?php

use EcSite\Model\Shop\FoodShop;

$foodShop= new FoodShop();

?>

しかし、同じようなクラスを復数利用する場合、useもたくさん増え、これはこれでウザいコードになってしまいます。

<?php

use EcSite\Model\Shop\FoodShop;
use EcSite\Model\Shop\FurnitureShop;
use EcSite\Model\Shop\ToyShop;
use EcSite\Model\Shop\PlantShop;
use EcSite\Model\Shop\ComputerShop;

$foodShop= new FoodShop();
$furnitureShop= new FurnitureShop();
$toyShop= new ToyShop();
$plantShop= new PlantShop();
$computerShop= new ComputerShop();


?>

これがウザいかどうかは主観ですが、例えば今後、更にクラスが増えたら、必ずuseとnewをセットで増やしていかなければなりません。
その対策として、途中階層までの名前空間をuseするという事が考えられます。

<?php

use EcSite\Model\Shop;

$foodShop = new Shop\FoodShop();
$furnitureShop = new Shop\FurnitureShop();
$toyShop = new Shop\ToyShop();
$plantShop = new Shop\PlantShop();
$computerShop = new Shop\ComputerShop();

?>

一つ一つのnewはすこしウザくなりましたが、すべてフルパスよりはマシです。

<?php

$foodShop = new EcSite\Model\Shop\FoodShop();
$furnitureShop = new EcSite\Model\Shop\FurnitureShop();
$toyShop = new EcSite\Model\Shop\ToyShop();
$plantShop = new EcSite\Model\Shop\PlantShop();
$computerShop = new EcSite\Model\Shop\ComputerShop();

?>

ウザいかどうかで決めるものではないですが、コードの可読性という観点で見ると重要とも言えます。
また、全てフルパスで指定している場合、それらのクラスが属する名前空間がもし変更になるとしたら、変更箇所が多くなってしまいます。なので、基本的にはフルパス指定はあまりお勧めできません。

一つのルールとして、newの時には必ず名前空間を含まないクラス名で、というふうにすれば、名前空間の変更はuse文のみ気にしていればいいことになるので、変更容易性という意味でもこれが良いでしょう。

でもたくさんのクラスをuseしなければならない場合、極端な場合では、最初の数十行がuseで埋まるという事も十分ありえます。それはそれで名前空間の変更時に大変とも言えるし、可読性も少し下がります。なので上の例のような、途中の名前空間までが同じクラスがたくさん存在するような場合、その途中の名前空間までをuseするという選択がバランスがいいかもしれません。

ということで、規定のルールがない場合は、useの書き方のルールは臨機応変に、というのが正解かもしれません。

名前空間とディレクトリ階層を一致させる

上の例では省略していますが、当然、実際はuseするにはその前にそのクラスを定義したPHPファイルを読み込む必要があります。
例えば以下の様なクラスが存在するとします。

<?php

namespace EcSite\Model\Shop;

class FoodShop
{

}

?>

このクラスを利用する場合、実際には以下のようになります。

<?php

require_once 'FoodShop.php';
use EcSite\Model\Shop\FoodShop;

$foodShop = new FoodShop();

?>

ここでは単に'FoodShop.php'という感じで読み込んでいます。これだと実際にはphp.iniで設定されたinclude_pathの直下か、このコードが記述されたファイルと同じディレクトリ内にFoodShop.phpが存在することになります。
実際にはこのFoodShop.phpはどこか適当なディレクトリに格納してあるかもしれないわけなので、それに合わせてrequire_onceで指定する値も変わるわけです。

PHPにはオートロードという仕組みがあります。ここではオートロードの仕組みの詳しい説明は省きますが、spl_autoload_register()という関数を使って、ユーザー定義の関数を登録することで、未読込のクラスがnewされようとした時に、そのユーザー定義関数が呼び出されるというものです。なので、この関数で、requireなどを実行するようにすることで、いちいち読み込む必要がなくなるというものです。詳しくはPHPの公式マニュアルを参照して下さい。

PHPのオートロードは、全てのクラス定義ファイルが同じディレクトリに存在している、もしくはクラス名と、そのクラス定義ファイルの存在位置・ファイル名がある一定の規則で関連づいている場合に実現可能な仕組みです。全てのクラス定義ファイルが同一のディレクトリに分類もされずにひたすら突っ込まれているのはあまり好ましい状態とはいえません。

そこである程度クラスファイルを分類し、適切にディレクトリに分けて格納していく事になります。そうするとクラス名と、そのクラスファイル定義の位置の関係に一定のルールが必要なわけですが、ここで一つの考え方が生まれます。最初に、名前空間はディレクトリと似たような考え方だと言いましたが、それでは実際に名前空間の階層とディレクトリの階層を一致させればいいではないかという考えに至ります。

そこで、このクラスファイルを名前空間の階層と一致するようなディレクトリ位置に配置します。

EcSite
  ∟Model
    ∟Shop
      ∟FoodShop.php
<?php

require_once 'EcSite\Model\Shop\FoodShop.php';
use EcSite\Model\Shop\FoodShop;

$foodShop = new FoodShop();

?>

requireをいちいち書くとすると、このようになります。
しかし、名前空間の階層とディレクトリ階層を一致させているので、require_onceとuseでほぼ同じような指定をしていることがわかります。当然です。

ということは、このrequire_onceは無くして、オートロードに任せる事が容易であることになります。
例えば以下の様な感じです。

<?php

function autoload($className)
{
    require_once $className . '.php';
}

spl_autoload_register('autoload');

?>

このスクリプトをあらかじめ実行しておけば、あとはいちいちrequireする必要がなくなります。
実際はここまで単純な関数ではダメな場合があると思いますが、考え方としてはこんなかんじです。
これでいちいちrequire_onceしなければならない煩わしさから開放されるわけです。

<?php

use EcSite\Model\Shop\FoodShop;

$foodShop = new FoodShop();

?>

この手法によるメリットは、単にrequire_onceから開放されるだけではありません。
それだけであれば、一定の規則で関連づいていれば、階層が一致している必要はありません。
しかし、階層が一致していない状態でオートロードに任せてrequire_onceがなくなると、実際のクラス定義ファイルがどこにあるかがわかりにくくなります。

例えばクラスを利用して実装する人間が、このクラスのソースを見たい、となった時に、そのクラスファイルがどこに存在するのかをまず探し当てる必要があります。その時、require_onceが存在しないため、実際のクラスファイル位置がどこにあるかは、クラス名とクラスファイル位置の関連付けのルールを知らなければならないことになります。

ここで、名前空間の階層とクラスファイルの存在するディレクトリ階層が一致していると、メリットが現れてきます。つまりuse文の定義がそのままクラスファイルの存在位置を表していることになるからです。なのでuse文を見れば一目瞭然。

ということで、名前空間と、ディレクトリ階層を一致させるというのは非常に有効な手法です。


実際、最新のPHPフレームワークはだいたいこの仕組みでオートロードが動くようになっています。
PHP5.3で名前空間の仕組みが導入されたわけですが、そもそもこの仕組を前提にしていたのではないかと思うほどです。