PHPのnamespace(名前空間)

PHPでは、定義済みのクラス名、関数名、定数名は、名前の衝突が起こるので使用できません。そのため、識別子は長くなりがちですが、PHP5.3から追加された名前空間という機能を利用することで名前の衝突を避けることができます。名前空間とは、OSのディレクトリに似た概念で、同じ名前でも名前空間(ディレクトリ)が違えば、使用することができます。

namespace宣言

名前空間を定義するには、namespaceキーワードを使用して名前空間を宣言します。名前空間の影響を受けるのは、クラス、インターフェイス、関数、constで定義された定数です。変数やdefine()関数で定義された定数は影響を受けません。

下記の例では、namespaceキーワードでTestという名前空間を宣言しています。そのため、グローバルに定義済みのクラス名、関数名、定数名を使用してもエラーになりません。

PHP

<?php
namespace Test;
class Directory
{
  public function __construct()
  {
    echo 'Test\Directory', PHP_EOL;
  }
}
function strlen()
{
  echo 'Test\strlen()', PHP_EOL;
}
const E_USER_ERROR = 'Test\E_USER_ERROR';
?>

namespace宣言の前に記述できるのは、declare文のみです。PHPコード以外でも、namespace宣言の前に記述することはできません。下記の例では、namespace宣言の前にHTMLを記述しているため、Fatal errorとなります。

PHP

<html>
<body>
<?php
namespace Test;
/*
Fatal error: Namespace declaration statement has to be the very first statement
*/
?>
</body>
</html>

名前空間の階層構造

名前空間には、ディレクトリと同じように階層構造を持たせることができます。階層化させるには、名前空間をバックスラッシュ(\)で区切ります。下記の例では、Fooがサブ名前空間になります。

PHP

<?php
namespace Test\Foo;
class Directory
{
  public function __construct()
  {
    echo 'Test\Foo\Directory', PHP_EOL;
  }
}
function strlen()
{
  echo 'Test\Foo\strlen()', PHP_EOL;
}
const E_USER_ERROR = 'Test\Foo\E_USER_ERROR';
?>

同一ファイル内での複数の名前空間の使用

同一ファイル内で複数の名前空間を使用する方法が2通りあります。1つ目の方法は、名前空間を変更したい箇所でnamespaceキーワードで名前空間を宣言します。しかし、この書式では名前空間に属さないグローバルな空間を宣言することができません。また、第3者が見ても非常に解りにくいので、この書式は推奨されていません。

PHP

<?php
namespace Test;
const E_USER_ERROR = 'Test\E_USER_ERROR';
echo E_USER_ERROR, PHP_EOL; // 出力:Test\E_USER_ERROR

namespace Test\Foo;
const E_USER_ERROR = 'Test\Foo\E_USER_ERROR';
echo E_USER_ERROR, PHP_EOL; // 出力:Test\Foo\E_USER_ERROR
?>

2つ目の方法は、namespace宣言に続く文を波括弧{}で囲う方法です。この書式では、グローバルな空間も宣言することができますが、波括弧の外側に書くことができるのは最初のdeclare文だけです。また根本的な問題として、同一ファイル内で複数の名前空間を使用するのは、第3者が見て解りにくいので、できる限り避けるべきでしょう。

PHP

<?php
namespace Test {
  const E_USER_ERROR = 'Test\E_USER_ERROR';
  echo E_USER_ERROR, PHP_EOL; // 出力:Test\E_USER_ERROR
}

namespace Test\Foo {
  const E_USER_ERROR = 'Test\Foo\E_USER_ERROR';
  echo E_USER_ERROR, PHP_EOL; // 出力:Test\Foo\E_USER_ERROR
}

namespace {
  echo E_USER_ERROR, PHP_EOL; // 出力:256
}
?>

名前空間の参照

名前空間に定義されたクラス、インターフェイス、関数、定数にアクセスするには、下記3つの方法があります。

非修飾名
名前空間の区切り文字(\)を含まない識別子は、非修飾名と呼ばれます。非修飾名では、現在の名前空間の識別子にアクセスします。ディレクトリに例えると現在のディレクトリ内のファイル名といった指定になります。
修飾名
名前空間の区切り文字(\)を含む識別子は、修飾名と呼ばれます。修飾名では、現在の名前空間からの相対位置にある名前空間の識別子にアクセスします。ディレクトリに例えると相対パスに似ています。
完全修飾名
名前空間の区切り文字(\)で始まる識別子は、完全修飾名と呼ばれます。完全修飾名では、グローバルな名前空間からの絶対位置にある名前空間の識別子にアクセスします。ディレクトリに例えると絶対パスに似ています。

PHP

<?php
namespace Test\Foo {
  const E_USER_ERROR = 'Test\Foo\E_USER_ERROR';
  // 非修飾名
  echo E_USER_ERROR, PHP_EOL; // 出力:Test\Foo\E_USER_ERROR
}

namespace Test {
  const E_USER_ERROR = 'Test\E_USER_ERROR';
  // 非修飾名
  echo E_USER_ERROR, PHP_EOL; // 出力:Test\E_USER_ERROR
  // 修飾名
  echo Foo\E_USER_ERROR, PHP_EOL; // 出力:Test\Foo\E_USER_ERROR
  // 完全修飾名
  echo \E_USER_ERROR, PHP_EOL; // 出力:256
}

namespace {
  // 非修飾名
  echo E_USER_ERROR, PHP_EOL; // 出力:256
  // 修飾名
  echo Test\E_USER_ERROR, PHP_EOL; // 出力:Test\E_USER_ERROR
  // 完全修飾名
  echo \Test\Foo\E_USER_ERROR, PHP_EOL; // 出力:Test\Foo\E_USER_ERROR
}
?>

注意すべき点として、非修飾名の関数や定数が現在の名前空間に見つからなかった場合、PHPは自動的にグローバル空間にアクセスして関数や定数を探します。一方、非修飾名のクラスが現在の名前空間に見つからなかった場合は、グローバル空間にアクセスすることなくオートロードを試み、それでもクラスが見つからなければFatal errorとなります。

PHP

<?php
namespace Test\Foo {
  echo strlen('hoge'), PHP_EOL; // 出力:4
  echo E_USER_ERROR, PHP_EOL; // 出力:256
  $dir = new Directory(); // 出力:Fatal error: Class 'Test\Foo\Directory' not found
}
?>

動的な名前空間の参照

可変変数や可変関数と同じように変数に文字列を代入して、動的に名前空間を参照することができます。動的な名前空間の参照では、常に完全修飾名として扱われるため、先頭のバックスラッシュ(\)はなくても構いません。

PHP

<?php
namespace Test\Foo {
  function hoge()
  {
    echo 'Test\Foo\hoge()', PHP_EOL;
  }
  $name = '\Test\Bar';
  $bar = new $name(); // 出力:Test\Bar
}

namespace Test {
  class Bar
  {
    public function __construct()
    {
      echo 'Test\Bar', PHP_EOL;
    }
  }
  $name = 'Test\Foo\hoge';
  $name(); // 出力:Test\Foo\hoge()
}
?>

__NAMESPACE__ マジック定数とnamespaceキーワード

__NAMESPACE__ マジック定数は、現在の名前空間を文字列で保持しています。グローバルな名前空間では空文字列となります。また、識別子にnamespaceキーワードを付けると現在の名前空間やサブ名前空間を明示的に指定することができます。

PHP

<?php
namespace Test\Foo {
  const E_USER_ERROR = 'Test\Foo\E_USER_ERROR';
  echo '"' . __NAMESPACE__ . '"', PHP_EOL; // 出力:"Test\Foo"
}

namespace Test {
  const E_USER_ERROR = 'Test\E_USER_ERROR';
  echo '"' . __NAMESPACE__ . '"', PHP_EOL; // 出力:"Test"
  echo namespace\E_USER_ERROR, PHP_EOL; // 出力:Test\E_USER_ERROR
  echo namespace\Foo\E_USER_ERROR, PHP_EOL; // 出力:Test\Foo\E_USER_ERROR
}

namespace {
  echo '"' . __NAMESPACE__ . '"', PHP_EOL; // 出力:""
  echo namespace\E_USER_ERROR, PHP_EOL; // 出力:256
  echo namespace\Test\E_USER_ERROR, PHP_EOL; // 出力:Test\E_USER_ERROR
}
?>

名前空間のエイリアス・インポート

PHPでは、別の名前空間やそれに属するクラス、インターフェイスのエイリアスを作ることができます。これは、ディレクトリのシンボリックリンクの概念に似ています。エイリアスを作るには、useキーワードでインポートする名前空間、クラス名、インターフェイス名を指定し、asキーワードで別名を指定します。asキーワードを省略した場合、useで指定した1番右側の非修飾名が別名となります。useで指定する名前空間は、常に完全修飾名として扱われるため、先頭のバックスラッシュは不要です。

includeやrequireで読み込んだファイルは、読み込み元のファイルのエイリアスを引き継ぎません。同一ファイル内で名前空間を変更した場合もエイリアスを引き継ぎません。また、関数や定数のエイリアスはサポートされていませんが、名前空間のエイリアス経由でアクセスすることはできます。

PHP

<?php
namespace Test\Foo {
  class Hoge
  {
    public function __construct()
    {
      echo 'Test\Foo\Hoge', PHP_EOL;
    }
  }
  function bar()
  {
    echo 'Test\Foo\bar()', PHP_EOL;
  }
  // Test\Bazクラスを別名Bazでインポート
  use Test\Baz as Baz;
  $baz = new Baz(); // 出力:Test\Baz
  // グローバルのPiyoクラスを別名Piyoでインポート
  use Piyo as Piyo;
  $piyo = new Piyo(); // 出力:Piyo
}

namespace Test {
  class Baz
  {
    public function __construct()
    {
      echo 'Test\Baz', PHP_EOL;
    }
  }
  // 名前空間Test\Fooを別名Fugaでインポート
  use Test\Foo as Fuga;
  $hoge = new Fuga\Hoge(); // 出力:Test\Foo\Hoge
  // Test\Foo\bar()関数の呼び出し
  Fuga\bar(); // 出力:Test\Foo\bar()
  // グローバルのPiyoクラスを別名PiyoPiyoでインポート
  use Piyo as PiyoPiyo;
  $piyo = new PiyoPiyo(); // 出力:Piyo
}

namespace {
  class Piyo
  {
    public function __construct()
    {
      echo 'Piyo', PHP_EOL;
    }
  }
  // 名前空間Test\Fooを別名Fooでインポート(use Test\Foo as Fooと等価)
  use Test\Foo;
  $hoge = new Foo\Hoge(); // 出力:Test\Foo\Hoge
  // Test\Bazクラスを別名Bazでインポート(use Test\Baz as Bazと等価)
  use Test\Baz;
  $baz = new Baz(); // 出力:Test\Baz
}
?>

インポートはコンパイル時に行われるため、名前空間のエイリアスを動的に参照することはできません。下記の例では、文字列を代入した変数で動的にエイリアスを参照しようとしていますが、結果としてグローバルのHogeクラスと解釈され、Fatal errorとなります。

PHP

<?php
namespace Test\Foo {
  class Hoge
  {
  }
}

namespace {
  use Test\Foo\Hoge as Hoge;
  $name = 'Hoge';
  $hoge = new $name(); // 出力:Fatal error: Class 'Hoge' not found
}
?>