CakeFest 2024: The Official CakePHP Conference

名前解決のルール

(PHP 5 >= 5.3.0, PHP 7, PHP 8)

名前解決のルールを説明するにあたって、いくつかの重要な定義を示しておきます。

名前空間名の定義
非修飾名

これは名前空間区切り文字を含まない識別子で、Foo のようなものです。

修飾名

これは名前空間区切り文字を含む識別子で、Foo\Bar のようなものです。

完全修飾名

これは名前空間区切り文字を含む識別子のうち先頭が名前空間区切り文字で始まるもので、 \Foo\Bar のようなものです。名前空間 \Foo も完全修飾名です。

相対名

namespace\Foo\Bar のように、 namespace で始まる識別子です。

名前解決は、これらの解決ルールによって行われます。

  1. 完全修飾名は、先頭の名前空間区切り文字を除いた名前に常に解決されます。 たとえば、\A\BA\B と解釈されます。
  2. 相対名は、namespace という名前を、 現在の名前空間に置き換えたものに常に解決されます。 現在の名前空間がグローバル名前空間だった場合、 namespace\ 名前空間は取り除かれます。 たとえば、名前空間 X\Y の中にある namespace\A は、 X\Y\A に解決されます。 グローバル名前空間の中にある同じ名前は、 A に解決されます。
  3. 修飾名の場合は、名前の最初の識別子を、 現在のクラス/名前空間のインポートテーブルに従って翻訳します。 たとえば、名前空間 A\B\CC としてインポートされた場合、 C\D\E という名前は、 A\B\C\D\E と翻訳されます。
  4. 修飾名の場合で、適用すべきインポートルールがない場合、 現在の名前空間が名前の先頭に付加されます。 たとえば、名前空間 A\B の中にある C\D\E という名前は A\B\C\D\E に解決されます。
  5. 非修飾名の場合、名前はそれぞれのシンボルタイプの 現在のインポートテーブルに従って翻訳されます。 これは、クラスのような名前は、クラス/名前空間のインポートテーブルに従って 翻訳されるし、関数名は、関数のインポートテーブルに従うし、 定数は定数のインポートテーブルに従うということになります。 たとえば、use A\B\C; の後に、 new C() のようなことをすると、 C は A\B\C() に解決されます。 同じように、use function A\B\foo; の後に foo() のようなことをすると、 A\B\foo という名前に解決されます。
  6. 非修飾名について、適用すべきインポートルールが存在せず、 名前がクラスのようなシンボルを参照している場合、 現在の名前空間が先頭に付加されます。 たとえば、名前空間 A\B の内部にある new C() は、 A\B\C という名前に解決されます。
  7. 非修飾名について、適用すべきインポートルールが存在せず、 名前が関数や定数を参照しており、 コードがグローバル名前空間の外に存在する場合は 名前は実行時に解決されます。 コードが名前空間 A\B の中にあると仮定すると、 関数 foo() のコールは、次のように解決されます。
    1. まず現在の名前空間から関数 A\B\foo() を探します。
    2. 次に グローバル 関数 foo() を探します。

例1 名前解決の例

<?php
namespace A;
use
B\D, C\E as F;

// 関数のコール

foo(); // まず名前空間 "A" で定義されている "foo" のコールを試み、
// 次にグローバル関数 "foo" をコールします

\foo(); // グローバルスコープで定義されている関数 "foo" をコールします

my\foo(); // 名前空間 "A\my" で定義されている関数 "foo" をコールします

F(); // まず名前空間 "A" で定義されている "F" のコールを試み、
// 次にグローバル関数 "F" をコールします

// クラスの参照

new B(); // 名前空間 "A" で定義されているクラス "B" のオブジェクトを作成します
// 見つからない場合は、クラス "A\B" の autoload を試みます

new D(); // インポートルールを使用し、名前空間 "B" で定義されているクラス "D" のオブジェクトを作成します
// 見つからない場合は、クラス "B\D" の autoload を試みます

new F(); // インポートルールを使用し、名前空間 "C" で定義されているクラス "E" のオブジェクトを作成します
// 見つからない場合は、クラス "C\E" の autoload を試みます

new \B(); // グローバルスコープで定義されているクラス "B" のオブジェクトを作成します
// 見つからない場合は、クラス "B" の autoload を試みます

new \D(); // グローバルスコープで定義されているクラス "D" のオブジェクトを作成します
// 見つからない場合は、クラス "D" の autoload を試みます

new \F(); // グローバルスコープで定義されているクラス "F" のオブジェクトを作成します
// 見つからない場合は、クラス "F" の autoload を試みます

// 別の名前空間から使用するstaticメソッド/関数

B\foo(); // 名前空間 "A\B" の関数 "foo" をコールします

B::foo(); // 名前空間 "A" で定義されているクラス "B" のメソッド "foo" をコールします
// クラス "A\B" が見つからない場合はクラス "A\B" の autoload を試みます

D::foo(); // インポートルールを使用し、名前空間 "B" で定義されているクラス "D" のメソッド "foo" をコールします
// クラス "B\D" が見つからない場合はクラス "B\D" の autoload を試みます

\B\foo(); // 名前空間 "B" の関数 "foo" をコールします

\B::foo(); // グローバルスコープのクラス "B" のメソッド "foo" をコールします
// クラス "B" が見つからない場合はクラス "B" の autoload を試みます

// 現在の名前空間から使用するstaticメソッド/関数

A\B::foo(); // 名前空間 "A\A" のクラス "B" のメソッド "foo" をコールします
// クラス "A\A\B" が見つからない場合はクラス "A\A\B" の autoload を試みます

\A\B::foo(); // 名前空間 "A" のクラス "B" のメソッド "foo" をコールします
// クラス "A\B" が見つからない場合はクラス "A\B" の autoload を試みます
?>
add a note

User Contributed Notes 9 notes

up
36
kdimi
13 years ago
If you like to declare an __autoload function within a namespace or class, use the spl_autoload_register() function to register it and it will work fine.
up
32
rangel
14 years ago
The term "autoload" mentioned here shall not be confused with __autoload function to autoload objects. Regarding the __autoload and namespaces' resolution I'd like to share the following experience:

->Say you have the following directory structure:

- root
| - loader.php
| - ns
| - foo.php

->foo.php

<?php
namespace ns;
class
foo
{
public
$say;

public function
__construct()
{
$this->say = "bar";
}

}
?>

-> loader.php

<?php
//GLOBAL SPACE <--
function __autoload($c)
{
require_once
$c . ".php";
}

class
foo extends ns\foo // ns\foo is loaded here
{
public function
__construct()
{
parent::__construct();
echo
"<br />foo" . $this->say;
}
}
$a = new ns\foo(); // ns\foo also loads ns/foo.php just fine here.
echo $a->say; // prints bar as expected.
$b = new foo; // prints foobar just fine.
?>

If you keep your directory/file matching namespace/class consistence the object __autoload works fine.
But... if you try to give loader.php a namespace you'll obviously get fatal errors.
My sample is just 1 level dir, but I've tested with a very complex and deeper structure. Hope anybody finds this useful.

Cheers!
up
5
safakozpinar at NOSPAM dot gmail dot com
13 years ago
As working with namespaces and using (custom or basic) autoload structure; magic function __autoload must be defined in global scope, not in a namespace, also not in another function or method.

<?php
namespace Glue {
/**
* Define your custom structure and algorithms
* for autoloading in this class.
*/
class Import
{
public static function
load ($classname)
{
echo
'Autoloading class '.$classname."\n";
require_once
$classname.'.php';
}
}
}

/**
* Define function __autoload in global namespace.
*/
namespace {

function
__autoload ($classname)
{
\Glue\Import::load($classname);
}

}
?>
up
-1
Kavoir.com
10 years ago
For point 4, "In example, if the namespace A\B\C is imported as C" should be "In example, if the class A\B\C is imported as C".
up
-4
llmll
9 years ago
The mentioned filesystem analogy fails at an important point:

Namespace resolution *only* works at declaration time. The compiler fixates all namespace/class references as absolute paths, like creating absolute symlinks.

You can't expect relative symlinks, which should be evaluated during access -> during PHP runtime.

In other words, namespaces are evaluated like __CLASS__ or self:: at parse-time. What's *not* happening, is the pendant for late static binding like static:: which resolves to the current class at runtime.

So you can't do the following:

namespace Alpha;
class Helper {
public static $Value = "ALPHA";
}
class Base {
public static function Write() {
echo Helper::$Value;
}
}

namespace Beta;
class Helper extends \Alpha\Helper {
public static $Value = 'BETA';
}
class Base extends \Alpha\Base {}

\Beta\Base::Write(); // should write "BETA" as this is the executing namespace context at runtime.

If you copy the write() function into \Beta\Base it works as expected.
up
-6
rangel
14 years ago
The term "autoload" mentioned here shall not be confused with __autoload function to autoload objects. Regarding the __autoload and namespaces' resolution I'd like to share the following experience:

->Say you have the following directory structure:

- root
| - loader.php
| - ns
| - foo.php

->foo.php

<?php
namespace ns;
class
foo
{
public
$say;

public function
__construct()
{
$this->say = "bar";
}

}
?>

-> loader.php

<?php
//GLOBAL SPACE <--
function __autoload($c)
{
require_once
$c . ".php";
}

class
foo extends ns\foo // ns\foo is loaded here
{
public function
__construct()
{
parent::__construct();
echo
"<br />foo" . $this->say;
}
}
$a = new ns\foo(); // ns\foo also loads ns/foo.php just fine here.
echo $a->say; // prints bar as expected.
$b = new foo; // prints foobar just fine.
?>

If you keep your directory/file matching namespace/class consistence the object __autoload works fine.
But... if you try to give loader.php a namespace you'll obviously get fatal errors.
My sample is just 1 level dir, but I've tested with a very complex and deeper structure. Hope anybody finds this useful.

Cheers!
up
-8
dn dot permyakov at gmail dot com
9 years ago
Can someone explain to me - why do we need p.4 if we have p.2 (which covers both unqualified and qualified names)?
up
-8
CJ Taylor
10 years ago
It took me playing with it a bit as I had a hard time finding documentation on when a class name matches a namespace, if that's even legal and what behavior to expect. It IS explained in #6 but I thought I'd share this with other souls like me that see it better by example. Assume all 3 files below are in the same directory.

file1.php
<?php
namespace foo;

class
foo {
static function
hello() {
echo
"hello world!";
}
}
?>

file2.php
<?php
namespace foo;
include(
'file1.php');

foo::hello(); //you're in the same namespace, or scope.
\foo\foo::hello(); //called on a global scope.
?>

file3.php
<?php
include('file1.php');

foo\foo::hello(); //you're outside of the namespace
\foo\foo::hello(); //called on a global scope.
?>

Depending upon what you're building (example: a module, plugin, or package on a larger application), sometimes declaring a class that matches a namespace makes sense or may even be required. Just be aware that if you try to reference any class that shares the same namespace, omit the namespace unless you do it globally like the examples above.

I hope this is useful, particularly for those that are trying to wrap your head around this 5.3 feature.
up
-6
anrdaemon at freemail dot ru
8 years ago
Namespaces may be case-insensitive, but autoloaders most often do.
Do yourself a service, keep your cases consistent with file names, and don't overcomplicate autoloaders beyond necessity.
Something like this should suffice for most times:

<?php

namespace org\example;

function
spl_autoload($className)
{
$file = new \SplFileInfo(__DIR__ . substr(strtr("$className.php", '\\', '/'), 11));
$path = $file->getRealPath();
if(empty(
$path))
{
return
false;
}
else
{
return include_once
$path;
}
}

\spl_autoload_register('\org\example\spl_autoload');
?>
To Top