PHP V5.3 では何が新しいのか: 第 3 回 名前空間

PHP V5.3 がまもなくリリースされようとしています。この「PHP V5.3 では何が新しいのか」シリーズでは、このリリースでの非常に興味がそそられる新しい機能について説明します。第 1 回では PHP V5.3 でのオブジェクト指向プログラミングに関する変更とオブジェクトの処理方法に関する変更を説明しました。第 2 回ではクロージャーとラムダ関数について説明しました。この第 3 回では名前空間について説明します。この PHP V5.3 リリースの機能の中で、名前空間は最も期待され、また最も議論が多かった機能の 1 つです。名前空間の概念を使うことで、同じ名前を持つ関数、クラス、定数などが何度も定義されてしまう問題を避けることができます。

John Mertic, Software Engineer, SugarCRM

John Mertic は Kent State University をコンピューター・サイエンスの学位で卒業し、現在は SugarCRM のソフトウェア・エンジニアです。彼は多くのオープンソース・プロジェクトに貢献しており、そのうち最も重要なプロジェクトは PHP プロジェクトです。彼は PHP の Windows インストーラーの作成者でありメンテナーでもあります。



2009年 1月 20日

名前空間は、C++ や Java™ プログラミング言語など、多くの言語に存在します。名前空間が考え出されたおかげで、大規模なコードベースを持つ組織のほとんどが持っていた懸念、つまりアプリケーションで使われる関数名やクラス名が重複することで問題が発生するという懸念が解消されました。名前空間を使えば、コードの中で使われている関数やユーティリティーがどのグループのものなのかを特定することができ、あるいは逆にどのグループに属している関数やユーティリティーを使用するのかを指定することもできます。名前空間の一例が C# の System 名前空間です。この名前空間には .NET フレームワークで提供されるすべての関数とクラスが含まれています。

正式な名前空間を持たない他の言語 (PHP V5.2 やそれ以前など) の場合、クラス名や関数名の中で特定の命名規則を使うことによって名前空間をエミュレートすることがよくあります。Zend Framework はこれを行っており、各クラス名は Zend で始まり、また子名前空間はそれぞれアンダースコアーで区切られます。例えば Zend_Db_Table というクラス定義は Zend Framework に含まれるクラスであり、データベース機能を持っています。この方法の問題の 1 つは、生成されるコードが冗長になることで、特にクラスや関数が深い階層にある場合はそれが顕著です (その一例が Zend Framework の Zend_Cache_Backend_Apc です)。もう 1 つの問題は、すべてのコードがこのスタイルに従う必要があるということです。この命名規則に従わないサードパーティーのコードをアプリケーションの中で統合する場合、すべてのコードがそうしたスタイルに従うようにすることは困難です。

PHP での名前空間は紆余曲折を経てきました。名前空間は元々 PHP V5 の一部になるはずでしたが、適切な実装ができなかったため開発段階で削除されました。そして最終的に PHP V6 の一部として復活しましたが、Unicode 関連以外のすべての機能強化を別の PHP V5.x リリースに移すという決定が 2007年になされた後、PHP V5.3 に移されました。名前空間の使い方の大部分は当初の設計から変わっていませんが、名前空間にどの演算子を使うかという問題が最大の問題であり、この問題に関してコミュニティーのメンバー全員の見方が分かれていました。言語の設計と使いやすさの観点からさまざまな演算子が提案されましたが、どの演算子も問題を抱えており、バックスラッシュのみが他の演算子が抱える問題をすべて解決できたため、最終的に演算子としてバックスラッシュを使うという決定が 2008年10月に下されました。

PHP での名前空間

PHP の名前空間は、他の言語の名前空間の構文や設計のほとんどを参考にしており、最も参考にしたのは C++ です。しかし PHP では、一部の状況で名前空間を独特な方法で扱うため、名前空間の使い方が他の言語とまったく同じものであると想定していると、そこでつまずく可能性があります。このセクションでは、PHP での名前空間はどのように使うのかを説明します。

名前空間を定義する

新しい名前空間の定義方法は非常に簡単です。新しい名前空間を定義するためには、リスト 1 に示す行をファイルの中の最初の命令または出力として追加します。

リスト 1. 名前空間を定義する
<?php 
namespace Foo; 
class Example {} 
?>

理解しておく必要のある重要なことは、上記 namespace の宣言がファイル内の最初の命令または出力になっていなければならないということです。名前空間宣言の前に何か他のものがあると、致命的なエラーとなります。リスト 2 はこの例を示しています。

リスト 2. 名前空間を定義する方法として不適切な例
/* File1.php */ 
<?php 
echo "hello world!"; 
namespace Bad; 
class NotGood {}
?> 

/* File2.php */ 
 <?php 
namespace Bad; 
class NotGood {} 
?>

リスト 2 の最初の部分では、名前空間を定義する前にコンソールにエコー出力しようとしており、これが致命的なエラーになります。リスト 2 の 2 番目の部分では、<?php 開始タグの前に余分な空白が追加されており、これも致命的なエラーを引き起こします。こうしたエラーは PHP で名前空間を扱う場合に起こりがちなので、コードを作成する際には、こうした状況になっていないかどうか、十分注意することが重要です。

しかし上記の例はどちらも書き直すことができます。名前空間の定義と、名前空間宣言として必要となるコードを独立したファイルに切り離し、そのファイルを元のファイルで読み込むようにするのです。リスト 3 はこの修正を行ったコードを示しています。

リスト 3. 不適切な方法での名前空間の定義を修正する
/* Good.php */ 
<?php 
namespace Good; 
class IsGood() {} 
?> 

/* File1.php */ 
<?php 
echo "hello world!"; 
include './good.php'; 
?>

/* File2.php */ 
 <?php 
include './good.php'; 
?>

これでファイル内のコードで名前空間を定義する方法がわかったので、名前空間が定義されたコードをアプリケーションの中で活用する方法を調べてみましょう。

名前空間を指定したコードを使う

名前空間を定義し、その名前空間の中にコードを記述すれば、その名前空間をアプリケーションの中でとても簡単に使用することができます。名前空間を指定した関数、クラス、定数などを呼び出す方法にはさまざまなものがあります。1 つの方法は、呼び出しの接頭辞として名前空間を明示する方法です。もう 1 つの方法は、名前空間のエイリアスを定義し、そのエイリアスを呼び出しの接頭辞として使用する方法です (こうすると名前空間接頭辞を短くすることができます)。そして最後の方法は、コードの中で単純に名前空間を指定する方法で、この方法では、その名前空間がデフォルト名前空間として、すべての呼び出しで参照されます。リスト 4 は呼び出し方法の違いを示しています。

リスト 4. 名前空間の中で関数を呼び出す
/* Foo.php */ 
<?php 
namespace Foo; 
function bar() 
{ 
    echo "calling bar....";
} 
?> 

/* File1.php */ 
<?php 
include './Foo.php'; 
Foo/bar(); // outputs "calling bar...."; 
?> 

/* File2.php */ 
<?php 
include './Foo.php';
use Foo as ns; 
ns/bar(); // outputs "calling bar...."; 
?> 

/* File3.php */ 
<?php 
include './Foo.php'; 
use Foo; 
bar(); // outputs "calling bar...."; 
?>

リスト 4 は Foo という名前空間の bar() という関数を呼び出すためのさまざまな方法を示しています。File1.php を見ると明示的に呼び出す方法がわかります (呼び出しの前に名前空間の名前を付けます)。File2.php では名前空間の名前のエイリアスを使っています。そのため名前空間の名前をエイリアスで置き換えています。最後に、File3.php では単純に名前空間を指定しています。この場合は接頭辞を付けずに bar() を呼び出すことができます。

また、1 つのファイルの中で複数の名前空間を定義することもできます。そのためにはファイルの中に namespace 呼び出しを追加します。リスト 5 はこのコードを示しています。

リスト 5. ファイルの中で複数の名前空間を定義する
<?php 
namespace Foo; 
class Test {} 

namespace Bar; 
class Test {} 

$a = new Foo\Test; 
$b = new Bar\Test; 

var_dump($a, $b); 

Output: 
object(Foo\Test)#1 (0) {  
}  
object(Bar\Test)#2 (0) {  
}

これで名前空間の内部で呼び出しを行うための基本は理解できたので、名前空間に関するもっと複雑な状況や、こうした呼び出しがどのように動作するのかを調べてみましょう。

名前空間の解決

名前空間を使いこなす上でのハードルの 1 つが、スコープの解決方法を理解することです。リスト 4 のように単純なケースでは理解しやすいのですが、名前空間同士をネストし始めたり、ある名前空間からグローバル空間に対して呼び出しを行おうとしたりすると、問題が起きてきます。PHP V5.3 には、こうした問題を自動的に妥当な方法で解決するためのルールが用意されています。

では新しいインクルード・ファイルをいくつか作成しましょう。それぞれのファイルの中には hello() 関数が定義されています。

リスト 6. 異なる名前空間の中で定義された hello() 関数
/* global.php */ 
<?php 
function hello() 
{ 
    echo 'hello from the global scope!';
} 
?> 

/* Foo.php */ 
<?php 
namespace Foo; 
function hello() 
{ 
    echo 'hello from the Foo namespace!';
} 
?> 

/* Foo_Bar.php */ 
<?php 
namespace Foo/Bar; 
function hello() 
{ 
    echo 'hello from the Foo/Bar namespace!';
} 
?>

リスト 6 は、3 つの異なるスコープの中で hello() 関数を 3 回定義しています (グローバル・スコープの中と Foo 名前空間の中、そして Foo/Bar 名前空間の中)。hello() 関数が呼び出されるスコープがどれかにより、どの hello() 関数が呼び出されるかが決まります。リスト 7 にはこうした呼び出しの例を示しています。ここでは他の名前空間の hello() 関数を呼び出す方法を Foo 名前空間を使用して調べています。

リスト 7. Foo 名前空間からすべての hello() 関数を呼び出す
<?php 
include './global.php'; 
include './Foo.php';
include './Foo_Bar.php';

use Foo; 

hello();         // outputs 'hello from the Foo namespace!' 
Bar\hello();   // outputs 'hello from the Foo/Bar namespace!' 
\hello();       // outputs 'hello from the global scope!' 
?>

これを見ると、現在の名前空間の中で子名前空間を参照する場合には、名前空間の接頭辞を短縮できることがわかります (Foo/Bar/hello() の呼び出しを Bar/hello() に短縮することができます)。また、グローバル・スコープのメソッドを呼び出す場合、その指定の方法は、呼び出しの前に名前空間演算子を付けるだけでよいこともわかります。

これで名前空間の動作の仕組みは理解できたので、この仕組みをコードの中で使う方法を調べてみましょう。


PHP での名前空間の使い方

名前空間の最終的な目的は、グローバル・スコープの中にある定義の量を減らすことによってコードを適切な構成にすることです。このセクションでは、名前空間を使うことによってこの目標を最小限の努力で達成できることを、いくつかの例で説明します。

サードパーティーのコードに名前空間を使う

PHP アプリケーションの多くは、さまざまなソースからのコードを使用しています。こうしたコードには、PEAR リポジトリーの中にある、一貫した方法で設計されたコードもあれば、CakePHP や Zend Framework といったさまざまなフレームワークのコードや、インターネット上のさまざまなサイトで見つかるコードもあります。こうしたコードを統合する際の最大の問題の 1 つは、そうしたコードと既存のコードと混在させることが簡単ではないことです。そうしたコードの中で使われている関数名やクラス名は、アプリケーションの中で既に使われている関数名やクラス名と衝突する可能性があります。

その一例が PEAR の Date パッケージです。このパッケージは Date というクラス名を使用していますが、これは非常に一般的なクラス名であり、コード内の他の場所にも存在することが十分に考えられます。そのため、この問題を適切に回避するためには、このパッケージの中にある Date.php ファイルの先頭に名前空間に関する単純な命令を追加します。こうすれば、独自の Date クラスではなく PEAR の Date クラスを使いたい場合に PEAR の Date クラスを指定することができます。

リスト 8. 名前空間の中で定義された PEAR の Date クラスを使う
<?php 

require_once('PEAR/Date.php'); 

use PEAR\Date;    // the name of the namespace we've specified in PEAR/Date.php

// since the current namespace is PEAR\Date, we don't need to prefix the namespace name
$now = new Date(); 
echo $now->getDate();  // outputs the ISO formatted date 

// this example shows the full namespace specified as part of the class name 
$now = new PEAR\Date\Date(); 
echo $now->getDate();  // outputs the ISO formatted date  
?>

これで PEAR/Date.php ファイル内の PEAR/Date 名前空間の中で PEAR Date クラスを定義できたので、必要なことはファイルの中にこのコードを含めて新しい名前空間を使うか、あるいはクラス名や関数名の前に名前空間の名前を付けるだけです。この方法を使えば、サードパーティーのコードをアプリケーションの中に安全に含めることができます。

名前の衝突はサードパーティーのコードとの間でのみ起こるわけではありません。大規模なコードベースでも、近くに置かれることを想定していない部分同士が接近してしまうことによって名前の衝突が起こる場合があります。次のセクションでは、名前空間を使うことによってこの状況を解決する方法を説明します。


ユーティリティー関数の名前の衝突を回避する

ほとんどすべての PHP アプリケーションには、いくつかのユーティリティー・メソッドがあります。こうしたメソッドはアプリケーション全体の中での役割を担っているにもかかわらず、実際にはアプリケーションのどのオブジェクトの一部にもなっておらず、アプリケーションのどこかに最適な場所があるわけでもありません。しかしこうしたメソッドは、アプリケーションが大きくなるにつれて保守する上での問題を起こすようになります。

このことが問題になる 1 つの例がユニット・テストです (ユニット・テストでは、アプリケーションを実行するコードをテストするためのコードを作成します)。大部分のユニット・テスト・スイートはテスト・スイート全体の中にあるすべてのテストを実行するように設計されています。例えば 2 つのユーティリティー・ファイルがあり、通常はその 2 つが一緒に読み込まれることは絶対にないとしても、テスト・スイートの中ではアプリケーション全体を同時にテストするため、その 2 つが一緒に読み込まれてしまいます。アプリケーションをそのように設計することは長期にわたる保守を考えると決して適切ではありませんが、大規模なレガシー・コードベースには、そうした状況がよくあるものです。

リスト 9 は、この状況を回避する方法を示しています。ここには utils_left.php と utils_right.php という 2 つのファイルがあり、これらは右利きのユーザーと左利きのユーザーのためのユーティリティー関数の集合です。それぞれのファイルを独自の名前空間の中で定義します。

リスト 9. utils_left.php と utils_right.php
/* utils_left.php */
<?php 
namespace Utils\Left;

function whichHand() 
{ 
    echo "I'm using my left hand!";
} 
?> 

/* utils_right.php */
<?php 
namespace Utils\Right; 

function whichHand() 
{ 
    echo "I'm using my right hand!";
} 
?>

ここでは、どちら側の手を使うのかを出力する whichHand() 関数を定義しています。リスト 10 を見ると、両方のファイルを問題なく読み込んで、呼び出す名前空間を切り換えることが、非常に簡単であるのがわかります。

リスト 10. utils_left.php と utils_right.php を一緒に使う例
<?php 
include('./utils_left.php'); 
include('./utils_right.php');

Utils\Left\whichHand();    // outputs "I'm using my left hand!"
Utils\Right\whichHand();  // outputs "I'm using my right hand!" 

use Utils\Left; 
whichHand();                 // outputs "I'm using my left hand!" 

use Utils\Right; 
whichHand();                 // outputs "I'm using my right hand!"

これで両方のファイルを一緒に読み込んでも問題が起こることはなくなったので、どちらの名前空間を使って関数呼び出しを処理するのかを指定します。この方法を利用すれば、既存のコードに対する影響は最小限で済みます。なぜなら、この方法をサポートするようにリファクタリングするには、どちらの名前空間を使うかを示す use 命令をファイルの先頭に追加すればよいだけだからです。

これと同じ考え方を、定義された PHP コードを超えて拡張することもできます。次のセクションでは、名前空間の中にある内部関数をオーバーライドすることさえ可能なことを説明します。


内部関数の名前をオーバーライドする

多くの場合、PHP の内部関数は非常に便利ですが、それらの動作が必ずしも私達が求める動作と完全には同じでないことがあります。そうした場合には関数の動作に手を加え、私達が求める動作に合うようにする必要がありますが、その際、変更によって他のところにまで影響が及ぶことのないように、関数を別の名前で再定義する必要があります。

そうした変更が必要な関数の 1 つがファイルシステム関数です。例えば、file_put_contents() によって作成されるすべてのファイルが特定のパーミッション・セットを持っていることを確認したいとします。例えば file_put_contents() によって作成されるファイルを読み取り専用にしたいとします。そのためには、この関数を下記のように新しい名前空間の中で再定義します。

リスト 11. 名前空間の中で file_put_contents() を定義する
<?php 
namespace Foo; 

function file_put_contents( $filename, $data, $flags = 0, $context = null ) 
{ 
    $return = \file_put_contents( $filename, $data, $flags, $context );
    
    chmod($filename, 0444);

    return $return;
} 
?>

この関数の中で呼び出される内部関数 file_put_contents() は、関数名の前にバックスラッシュが付けられており、それによってグローバル・スコープで処理される関数であることを示しています。つまり、バックスラッシュが付いていることで、内部関数が呼び出されるのです。そして内部関数を呼び出した後、ファイルに chmod() を実行して適切なパーミッションを設定します。

これらの例は、名前空間を利用してコードの機能を強化する方法の一例にすぎません。どの場合も、関数名やクラス名に接頭辞を付けて名前を一意にする、といったスマートさに欠ける変更はしていません。また、名前空間を利用することで大規模なアプリケーションの中にサードパーティーのコードを安全な方法で組み込むことができ、名前が衝突する心配もなくなります。


まとめ

PHP V5.3 における名前空間は、PHP 言語への追加機能として願ってもないものであり、アプリケーション内のコードを実用的な構成にする上で大いに役立ちます。PHP V5.3 の名前空間によって、名前空間を処理するための命名標準を使う必要がなくなり、より効率的なコードを作成できるようになります。登場するまで長い時間がかかったものの、名前の衝突の心配がある大規模な PHP アプリケーションすべてにとって、このような名前空間が追加されたことは、非常に喜ばしいことです。

参考文献

学ぶために

製品や技術を入手するために

  • 皆さんの次期オープンソース開発プロジェクトを IBM ソフトウェアの試用版を使って革新してください。ダウンロード、あるいは DVD で入手することができます。
  • IBM 製品の評価版をダウンロードし、DB2® や Lotus®、Rational®、Tivoli®、WebSphere® などが提供するアプリケーション開発ツールやミドルウェア製品をお試しください。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source
ArticleID=370285
ArticleTitle=PHP V5.3 では何が新しいのか: 第 3 回 名前空間
publish-date=01202009