名前空間とクラス名

11月12日にパーフェクトPHPが発売しました。購入いただいた方、コメントいただいた方、ありがとうございます!

      • -

さて本題です。PHP 5.3から名前空間が使えるようになりました。僕はここ最近Symfony2で社内向けのアプリを開発したり、Symfony2のバンドルをいくつか作ったりしてすっかり名前空間に慣れてきました。

名前空間を使い始めて感じたのが、クラス名をつける際の感覚がこれまでとは少し違うように思います。これまで、というのは主にPEAR形式のクラス名を指しています。Doctrineを例にとって解説します。

名前空間とクラス名

Doctrine 1では、次のようなクラスが定義されています。

  • Doctrine_Expression_Mysql
  • Doctrine_DataDict_Mysql
  • Doctrine_Connection_Mysql

これらを単純に名前空間を使った形式に置き換えてみます。

  • Doctrine\Expression\Mysql
  • Doctrine\DataDict\Mysql
  • Doctrine\Connection\Mysql

ここで問題となるのが、これらをインポートした時です。

<?php
use Doctrine\Expression\Mysql;
use Doctrine\DataDict\Mysql;
use Doctrine\Connection\Mysql;

みての通り、クラス名が被ってしまいます。これでは本末転倒です。

名前空間を用いた場合、それぞれ3つのクラスはあくまでも「Mysql」というクラス名になります。これはそもそもクラス名として不適切ではないでしょうか。それぞれが「MysqLExpression」「MysqlDataDict」「MysqlConnection」というクラス名であれば、同時にインポートしても何ら問題はありませんし、クラス名としても妥当です。

これまでは名前空間に該当する部分も含めてクラス名としていましたが、名前空間が実装されたことによって、名前空間としての意味しか持たなくなりました。名前空間を取り除いたとしても、クラス名からクラスの役割がわかるように名前をつける必要があります。

ここについては、これまでの感覚だと「ExpressionMysql」や「ExpressionPgsql」のように共通語句をプレフィックスをつけがちかもしれませんが、「MysqlExpression」「PgsqlExpression」のようにサフィックスにした方が英語的にしっくりくると思います。

抽象クラスのポジション

Doctrine_Expression_MysqlクラスはDoctrine_Expressionクラスを継承しています。Doctrine_Expressionをそのまま名前空間を使った形式に置き換えると、Doctrine\Expressionとなります。そうなった場合、ExpressionはあくまでDoctrine名前空間の定義された状態です。これは感覚的な問題かもしれませんが、僕はこれには違和感があります。

ディレクトリ的には次のようになります。

Doctrine/
  - Expression.php
  - Expression/
    - MysqlExpression.php

これについて、あくまでDoctrine\Expressionという名前空間の中で完結しておくべきだと考えます。

Doctrine/
  - Expression/
    - Expression.php
    - MysqlExpression.php

そうなると修飾クラス名は「Doctrine\Expression\Expression」となります。

同一の名前空間にいれば、クラス定義の際も親クラスをインポートする必要がないので楽ですし、自然に思います。

同一名前空間にいる場合:

<?php

namespace Doctrine\Expression;

class MysqlExpression extends Expression {}

同一名前空間にいない場合:

<?php

namespace Doctrine\Expression;

use Doctrine\Expression;

class MysqlExpression extends Expression {}

同一名前空間でない場合は、そもそもクラス名と名前空間が被ってしまいますよね。動作はしますけども。

まとめ

  • 名前空間を修飾していないクラス名だけを見ても、クラスの役割がわかるような名前をつける
  • 抽象クラスと具象クラスは同じ名前空間に配置する

気をつけている些細なこと

名前空間は単数形

ディレクトリを名前空間として扱う場合、ディレクトリの中に複数ファイルを管理しているという視点で見ると確かに複数形にしたくなりますが、あくまで名前空間で、最終的には単一の何かを特定するためのものだと考えています。

例えば「Loaders\YamlLoader」と「Loader\YamlLoader」があったとしましょう。YamlLoaderはYamlファイルをロードするクラスです。Loadersディレクトリの中にYamlLoaderクラスファイルがあったとして、Loadersディレクトリの中に複数のLoaderクラスが配置されることを考えると、ディレクトリ名としては適切に感じます。しかし逆に、YamlLoaderクラス側に立って考えると、YamlLoaderクラス自体は単一の存在で、複数の何かを表わすものではありません。クラスの立場から考えると、名前空間は単数形の方がしっくりくると思います。

基本的にはインポートしてから使う

基本的にはインポートしてます。

<?php
$request = new \Symfony\HttpFoundation\Request();

ではなく、

<?php
use Symfony\HttpFoundation\Request;

$request = new Request();

上記のようにインポートをする、ということです。これは見通しを良くすることと、関連する他の名前空間のクラスをわかりやすくするために行っています。

RuntimeException、DateTime、ArrayAccessなど、PHPが提供しているどこでも使えるようなクラスについては特にインポートはしません。useを書いて、使っていることを明示するメリットが特にないと思うのが理由です。これらはあちこちで唐突に使いたくなるわけです。日付を操作したいからDateTimeクラスを使おう、なんて色んな箇所で考えますよね。バックスラッシュ1個のためにわざわざ上まで戻ってuseを書くのは面倒、というのが本音です。

インポートしたいクラスが複数あるときは、それぞれuseを書く

useはカンマでつなげて複数指定が可能です。

<?php

use Symfony\Component\HttpKernel\Bundle\Bundle,
    Symfony\Component\DependencyInjection\ContainerInterface;

ですがあえて、1つ1つuseをつけて書くようにしています。

<?php

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerInterface;

先頭の要素はuseがついて、末尾の要素はカンマではなくセミコロンがつく、といった全く同じ役割を持つものの並びの中に例外が入ることが好きではないためです。追加や削除の際のミスを減らす意味もあります。クラスのプロパティ定義なんかも同様にしています。

その他テクニック的な

useが面倒くさいという点に関して言うと、例えばSymfony2でアプリケーションを作る場合、各コントローラーはSymfony\FrameworkBundle\Controller\Controllerクラスを継承して作るのが基本です。ただ毎回useするのは面倒なので、各BundleのController名前空間に空のControllerクラスを作るようにしています。

<?php

namespace Application\AppBundle\Controller;

use Symfony\FrameworkBundle\Controller\Controller as BaseController;

abstract class Controller extends BaseController
{
}
<?php

namespace Application\AppBundle\Controller;

// 名前解決のルールにのっとりApplication\AppBundle\Controller\Controllerを継承
class AccountController extends Controller
{
}

こうするとコントローラを作るたびにuseする必要はないですし、共通処理の追加も当然楽になりますよね。問題なければフレームワークとの間に1枚挟んでおくと何かと楽です。

終わりに

これまでは名前空間部分もすべて含めてクラス名として扱っていたので、トップダウン的にクラス名をつけていけばいずれあるべきクラス名になっていました。しかし、名前空間の採用により、そのままのつけ方では、クラス名が短くなりすぎて本来持っている意味をあらわせなくなる可能性もあります。

僕がコードを書くとき、とりあえず名前空間は気にせず、1つのファイルにガーっとクラスを定義していき、ある程度まとまったところで個別にクラスファイルを配置する、といったやり方をとることが多いです。やり方は人それぞれですが、まずはクラス名を意識して、あとから適切な名前空間を設けるように考えていくとよいと思います。