PHP Conference Japan 2024

名稱解析規則

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

為了這些解析規則的目的,以下是一些重要的定義

命名空間名稱定義
非限定名稱

這是一個沒有命名空間分隔符號的識別符號,例如 Foo

限定名稱

這是一個帶有命名空間分隔符號的識別符號,例如 Foo\Bar

完全限定名稱

這是一個以命名空間分隔符號開頭且帶有命名空間分隔符號的識別符號,例如 \Foo\Bar。命名空間 \Foo 也是一個完全限定名稱。

相對名稱

這是以 namespace 開頭的識別符號,例如 namespace\Foo\Bar

命名空間解析規則如下:

  1. 完全限定名稱總是解析為不帶前導命名空間分隔符的名稱。例如,\A\B 解析為 A\B
  2. 相對名稱總是解析為將 namespace 替換為當前命名空間的名稱。如果名稱出現在全域命名空間中,則移除 namespace\ 前綴。例如,在命名空間 X\Y 內的 namespace\A 解析為 X\Y\A。在全域命名空間內的相同名稱解析為 A
  3. 對於限定名稱,名稱的第一個區段會根據目前的類別/命名空間匯入表進行轉換。例如,如果命名空間 A\B\C 匯入為 C,則名稱 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() 會解析為名稱 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;

// function calls

foo(); // first tries to call "foo" defined in namespace "A"
// then calls global function "foo"

\foo(); // calls function "foo" defined in global scope

my\foo(); // calls function "foo" defined in namespace "A\my"

F(); // first tries to call "F" defined in namespace "A"
// then calls global function "F"

// class references

new B(); // creates object of class "B" defined in namespace "A"
// if not found, it tries to autoload class "A\B"

new D(); // using import rules, creates object of class "D" defined in namespace "B"
// if not found, it tries to autoload class "B\D"

new F(); // using import rules, creates object of class "E" defined in namespace "C"
// if not found, it tries to autoload class "C\E"

new \B(); // creates object of class "B" defined in global scope
// if not found, it tries to autoload class "B"

new \D(); // creates object of class "D" defined in global scope
// if not found, it tries to autoload class "D"

new \F(); // creates object of class "F" defined in global scope
// if not found, it tries to autoload class "F"

// static methods/namespace functions from another namespace

B\foo(); // calls function "foo" from namespace "A\B"

B::foo(); // calls method "foo" of class "B" defined in namespace "A"
// if class "A\B" not found, it tries to autoload class "A\B"

D::foo(); // using import rules, calls method "foo" of class "D" defined in namespace "B"
// if class "B\D" not found, it tries to autoload class "B\D"

\B\foo(); // calls function "foo" from namespace "B"

\B::foo(); // calls method "foo" of class "B" from global scope
// if class "B" not found, it tries to autoload class "B"

// static methods/namespace functions of current namespace

A\B::foo(); // calls method "foo" of class "B" from namespace "A\A"
// if class "A\A\B" not found, it tries to autoload class "A\A\B"

\A\B::foo(); // calls method "foo" of class "B" from namespace "A"
// if class "A\B" not found, it tries to autoload class "A\B"
?>
新增註解

使用者貢獻的註解 7 則註解

kdimi
14 年前
如果您想要在命名空間或類別內宣告 __autoload 函式,請使用 spl_autoload_register() 函式來註冊它,它就能正常運作。
rangel
15 年前
這裡提到的「自動載入」一詞不應與用於自動載入物件的 __autoload 函式混淆。關於 __autoload 和命名空間的解析,我想分享以下經驗:

->假設您有以下目錄結構:

- 根目錄
| - loader.php
| - ns
| - foo.php

->foo.php

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

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

}
?>

-> loader.php

<?php
//全域空間 <--
function __autoload($c)
{
require_once
$c . ".php";
}

class
foo extends ns\foo // ns\foo 會在此處載入
{
public function
__construct()
{
parent::__construct();
echo
"<br />foo" . $this->say;
}
}
$a = new ns\foo(); // ns\foo 也能在此正確載入 ns/foo.php。
echo $a->say; // 如預期印出 bar。
$b = new foo; // 正確印出 foobar。
?>

如果您的目錄/檔案與命名空間/類別保持一致,則物件 __autoload 可以正常運作。
但是...如果您嘗試賦予 loader.php 一個命名空間,顯然會出現致命錯誤。
我的範例只有一個層級的目錄,但我已經用一個非常複雜和更深層的結構進行了測試。希望對大家有所幫助。

乾杯!
safakozpinar at NOSPAM dot gmail dot com
14 年前
在使用命名空間和使用(自定義或基本)自動載入結構時,魔術函數 __autoload 必須定義在全域範圍內,而不是在命名空間內,也不是在其他函數或方法內。

<?php
namespace Glue {
/**
* 在此類別中定義您的自定義結構和
* 自動載入演算法。
*/
class Import
{
public static function
load ($classname)
{
echo
'正在自動載入類別 '.$classname."\n";
require_once
$classname.'.php';
}
}
}

/**
* 在全域命名空間中定義函數 __autoload。
*/
namespace {

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

}
?>
Kavoir.com
10 年前
關於第四點,「在範例中,如果命名空間 A\B\C 被導入為 C」應該改為「在範例中,如果類別 A\B\C 被導入為 C」。
llmll
9 年前
提到的檔案系統類比在一個重要的點上失敗了

命名空間解析*僅*在宣告時有效。編譯器會將所有命名空間/類別參考固定為絕對路徑,就像建立絕對符號連結一樣。

您不能期望相對符號連結,它應該在存取期間 -> 在 PHP 執行期間進行評估。

換句話說,命名空間的解析方式類似 `__CLASS__` 或 `self::`,是在解析時期進行的。但*並非*像 `static::` 那樣進行後期靜態綁定,在執行時期才解析為目前的類別。

所以你無法執行以下程式碼

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(); // 預期會輸出 "BETA",因為這是執行時期的命名空間上下文。

如果你把 Write() 函式複製到 \Beta\Base 中,它就會如預期般運作。
rangel
15 年前
這裡提到的「自動載入」一詞不應與用於自動載入物件的 __autoload 函式混淆。關於 __autoload 和命名空間的解析,我想分享以下經驗:

->假設您有以下目錄結構:

- 根目錄
| - loader.php
| - ns
| - foo.php

->foo.php

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

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

}
?>

-> loader.php

<?php
//全域空間 <--
function __autoload($c)
{
require_once
$c . ".php";
}

class
foo extends ns\foo // ns\foo 會在此處載入
{
public function
__construct()
{
parent::__construct();
echo
"<br />foo" . $this->say;
}
}
$a = new ns\foo(); // ns\foo 也能在此正確載入 ns/foo.php。
echo $a->say; // 如預期印出 bar。
$b = new foo; // 正確印出 foobar。
?>

如果您的目錄/檔案與命名空間/類別保持一致,則物件 __autoload 可以正常運作。
但是...如果您嘗試賦予 loader.php 一個命名空間,顯然會出現致命錯誤。
我的範例只有一個層級的目錄,但我已經用一個非常複雜和更深層的結構進行了測試。希望對大家有所幫助。

乾杯!
anrdaemon at freemail dot ru
8 年前
命名空間可能不區分大小寫,但自動載入器通常會區分。
為了方便起見,請保持程式碼的大小寫與檔案名稱一致,並且不要讓自動載入器過於複雜。
大多數情況下,像這樣的程式碼就足夠了

<?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