2024 日本 PHP 研討會

常見問題:您需要了解的命名空間相關知識

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

此常見問題解答分為兩個部分:常見問題,以及一些有助於完全理解的實作細節。

首先,是常見問題。

  1. 如果我不使用命名空間,我需要關心這些嗎?
  2. 如何在命名空間中使用內建或全域類別?
  3. 如何在它們自己的命名空間中使用命名空間的類別、函式或常數?
  4. \my\name\name 這樣的名稱是如何解析的?
  5. my\name 這樣的名稱是如何解析的?
  6. name 這樣的非限定類別名稱是如何解析的?
  7. name 這樣的非限定函式名稱或非限定常數名稱是如何解析的?

有一些命名空間實作的細節有助於理解。

  1. 匯入名稱不得與同一個檔案中定義的類別衝突。
  2. 不允許巢狀命名空間。
  3. 動態命名空間名稱(引號標識符)應跳脫反斜線。
  4. 使用反斜線參考未定義的常數將導致致命錯誤
  5. 特殊常數 nulltruefalse 無法被覆寫

如果我不使用命名空間,我需要關心這些嗎?

不會。命名空間不會以任何方式影響任何現有程式碼,也不會影響任何尚未編寫且不包含命名空間的程式碼。如果您願意,您可以編寫以下程式碼

範例 #1 在命名空間外存取全域類別

<?php
$a
= new \stdClass;
?>

這在功能上等同於

範例 #2 在命名空間外存取全域類別

<?php
$a
= new stdClass;
?>

如何在命名空間中使用內建或全域類別?

範例 #3 在命名空間內存取內建類別

<?php
namespace foo;
$a = new \stdClass;

function
test(\ArrayObject $parameter_type_example = null) {}

$a = \DirectoryIterator::CURRENT_AS_FILEINFO;

// 繼承內建或全域類別
class MyException extends \Exception {}
?>

如何在它們自己的命名空間中使用命名空間的類別、函式或常數?

範例 #4 在命名空間內存取內建類別、函式或常數

<?php
namespace foo;

class
MyClass {}

// 使用目前命名空間中的類別作為參數類型
function test(MyClass $parameter_type_example = null) {}
// 使用目前命名空間中的類別作為參數類型的另一種方法
function test(\foo\MyClass $parameter_type_example = null) {}

// 繼承目前命名空間中的類別
class Extended extends MyClass {}

// 存取全域函式
$a = \globalfunc();

// 存取全域常數
$b = \INI_ALL;
?>

\my\name\name 這樣的名稱是如何解析的?

\ 開頭的名稱永遠會解析為它們看起來的樣子,因此 \my\name 實際上是 my\name,而 \ExceptionException

範例 #5 完整限定名稱 (Fully Qualified names)

<?php
namespace foo;
$a = new \my\name(); // 初始化 "my\name" 類別
echo \strlen('hi'); // 呼叫函式 "strlen"
$a = \INI_ALL; // 將常數 "INI_ALL" 的值賦予 $a
?>

my\name 這樣的名稱是如何解析的?

包含反斜線但不以反斜線開頭的名稱,例如 my\name,可以透過兩種方式解析。

如果有 import 陳述式將另一個名稱設為 my 的別名,則該 import 別名會套用至 my\name 中的 my

否則,目前的命名空間名稱會加在 my\name 的前面。

範例 #6 限定名稱(Qualified names)

<?php
namespace foo;
use
blah\blah as foo;

$a = new my\name(); // 初始化 "foo\my\name" 類別
foo\bar::name(); // 呼叫 "blah\blah\bar" 類別中的靜態方法 "name"
my\bar(); // 呼叫函式 "foo\my\bar"
$a = my\BAR; // 將常數 "foo\my\BAR" 的值賦予 $a
?>

name 這樣的非限定類別名稱是如何解析的?

不包含反斜線的類別名稱,例如 name,可以透過兩種方式解析。

如果有 import 陳述式將另一個名稱設為 name 的別名,則會套用該 import 別名。

否則,目前的命名空間名稱會加在 name 的前面。

範例 #7 非限定類別名稱(Unqualified class names)

<?php
namespace foo;
use
blah\blah as foo;

$a = new name(); // 初始化 "foo\name" 類別
foo::name(); // 呼叫 "blah\blah" 類別中的靜態方法 "name"
?>

name 這樣的非限定函式名稱或非限定常數名稱是如何解析的?

不包含反斜線的函式或常數名稱,例如 name,可以透過兩種方式解析。

首先,目前的命名空間名稱會加在 name 的前面。

最後,如果目前的命名空間中不存在常數或函式 name,則會使用全域常數或函式 name(如果存在的話)。

範例 #8 非限定函式或常數名稱(Unqualified function or constant names)

<?php
namespace foo;
use
blah\blah as foo;

const
FOO = 1;

function
my() {}
function
foo() {}
function
sort(&$a)
{
\sort($a); // 呼叫全域函式 "sort"
$a = array_flip($a);
return
$a;
}

my(); // 呼叫 "foo\my"
$a = strlen('hi'); // 呼叫全域函式 "strlen",因為 "foo\strlen" 不存在
$arr = array(1,3,2);
$b = sort($arr); // 呼叫函式 "foo\sort"
$c = foo(); // 呼叫函式 "foo\foo" - import 並未套用

$a = FOO; // 將 $a 設為常數 "foo\FOO" 的值 - import 並未套用
$b = INI_ALL; // 將 $b 設為全域常數 "INI_ALL" 的值
?>

匯入名稱不得與同一個檔案中定義的類別衝突。

以下的腳本組合是合法的

file1.php

<?php
namespace my\stuff;
class
MyClass {}
?>

another.php

<?php
namespace another;
class
thing {}
?>

file2.php

<?php
namespace my\stuff;
include
'file1.php';
include
'another.php';

use
another\thing as MyClass;
$a = new MyClass; // 建立命名空間 another 中的類別 "thing" 的實例
?>

即使類別 MyClass 存在於 my\stuff 命名空間中,也沒有名稱衝突,因為 MyClass 的定義在不同的檔案中。然而,下一個範例會因為 MyClass 與 use 陳述式定義在同一個檔案中而導致名稱衝突的致命錯誤。

<?php
namespace my\stuff;
use
another\thing as MyClass;
class
MyClass {} // 嚴重錯誤:MyClass 與 import 陳述式衝突
$a = new MyClass;
?>

不允許巢狀命名空間。

PHP 不允許巢狀命名空間。

<?php
namespace my\stuff {
namespace
nested {
class
foo {}
}
}
?>
然而,可以很容易地像這樣模擬巢狀命名空間:
<?php
namespace my\stuff\nested {
class
foo {}
}
?>

動態命名空間名稱(引號標識符)應將反斜線跳脫。

務必理解,由於反斜線在字串中被用作跳脫字元,因此在字串中使用時應始終加倍。否則會有產生非預期結果的風險。

範例 #9 在雙引號字串中使用命名空間名稱的危險

<?php
$a
= "dangerous\name"; // \n 在雙引號字串中是換行符號!
$obj = new $a;

$a = 'not\at\all\dangerous'; // 這裡沒有問題。
$obj = new $a;
?>
在單引號字串中,反斜線跳脫序列的使用更安全,但最佳實務仍然建議在所有字串中跳脫反斜線。

使用反斜線參考未定義的常數將導致致命錯誤

任何未定義且未限定的常數(例如 FOO)都會產生一個通知,說明 PHP 假設 FOO 是常數的值。任何包含反斜線的常數(無論是否限定或完全限定),如果找不到,都會產生嚴重錯誤。

範例 #10 未定義的常數

<?php
namespace bar;
$a = FOO; // 產生提示 - 未定義的常數 "FOO",假設為 "FOO"
$a = \FOO; // 致命錯誤,未定義的命名空間常數 FOO
$a = Bar\FOO; // 致命錯誤,未定義的命名空間常數 bar\Bar\FOO
$a = \Bar\FOO; // 致命錯誤,未定義的命名空間常數 Bar\FOO
?>

無法覆寫特殊常數 nulltruefalse

任何嘗試定義與內建特殊常數名稱相同的命名空間常數都會導致致命錯誤。

範例 #11 未定義的常數

<?php
namespace bar;
const
NULL = 0; // 致命錯誤;
const true = 'stupid'; // 同樣是致命錯誤;
// 等等。
?>

新增註釋

使用者貢獻的註釋 6 則註釋

manolachef at gmail dot com
12 年前
有一種方法可以定義與內建特殊常數名稱相同的命名空間常數,使用 define 函式並將第三個參數 case_insensitive 設定為 false

<?php
namespace foo;
define(__NAMESPACE__ . '\NULL', 10); // 在目前的命名空間中定義常數 NULL
var_dump(NULL); // 將顯示 10
var_dump(null); // 將顯示 NULL
?>

不需要在呼叫 define() 時指定命名空間,就像通常那樣
<?php
namespace foo;
define(INI_ALL, 'bar'); // 會產生通知 - 常數 INI_ALL 已經定義。但是:

define(__NAMESPACE__ . '\INI_ALL', 'bar'); // 在目前的命名空間中定義常數 INI_ALL
var_dump(INI_ALL); // 將會顯示 string(3)"bar"。目前為止沒有任何異常。但是:

define('NULL', 10); // 在目前的命名空間中定義常數 NULL...
var_dump(NULL); // 將會顯示 10
var_dump(null); // 將會顯示 NULL
?>

如果參數 case_insensitive 設定為 true
<?php
namespace foo;
define (__NAMESPACE__ . '\NULL', 10, true); // 會產生通知 - 常數 null 已經定義
?>
shaun at slickdesign dot com dot au
8 年前
在命名空間內使用變數建立類別或呼叫靜態方法時,您需要注意它們需要完整的命名空間才能使用適當的類別;即使在相同的命名空間內呼叫,您也不能使用別名或簡稱。忽略這一點可能會導致您的程式碼使用錯誤的類別、拋出找不到類別的致命例外,或是拋出錯誤或警告。

在這些情況下,您可以使用魔術常數 __NAMESPACE__,或直接指定完整的命名空間和類別名稱。函式 class_exists 也需要完整的命名空間和類別名稱,並且可以用於確保不會因為缺少類別而拋出致命錯誤。

<?php

命名空間 Foo;
類別
Bar {
public static function
test() {
return
get_called_class();
}
}

命名空間
Foo\Foo;
類別
Bar extends \Foo\Bar {
}

var_dump( Bar::test() ); // 字串(11) "Foo\Foo\Bar"

$bar = 'Foo\Bar';
var_dump( $bar::test() ); // 字串(7) "Foo\Bar"

$bar = __NAMESPACE__ . '\Bar';
var_dump( $bar::test() ); // 字串(11) "Foo\Foo\Bar"

$bar = 'Bar';
var_dump( $bar::test() ); // 致命錯誤:找不到類別 'Bar' 或使用了錯誤的類別 \Bar
theking2 at king dot ma
2 年前
就像類別名稱目前命名空間不區分大小寫。所以這裡不會顯示錯誤

<?php declare(strict_types=1);
命名空間
Foo;
類別
Bar {
public function
__construct() {
echo
'Map constructed';
}
}

$foobar = new \foo\bar();
teohad at NOSPAM dot gmail dot com
8 年前
[編者註:該行為是由 PHP 7.0 中的一個錯誤引起的,該錯誤已在 PHP 7.0.7 中修復。]

關於「匯入的名稱不能與同一檔案中定義的類別衝突」的條目。
- 我發現從 PHP 7.0 開始,情況不再如此。
在 PHP 7.0 中,您可以擁有一個名稱與匯入的類別(或命名空間,或同時與兩者)匹配的類別。

<?php
命名空間 ns1 {
class
ns1 {
public static function
write() {
echo
"ns1\\ns1::write()\n";
}
}
}

命名空間
ns1\ns1 {
class
ns1c {
public static function
write() {
echo
"ns1\\ns1\\ns1c::write()\n";
}
}
}

命名空間
ns2 {
use
ns1\ns1 as ns1; // ns1 中的一個類別,同時也是一個命名空間 ns1\ns1

// 下一個類別在 php 5.6 中會造成致命錯誤,但在 7.0 中不會
class ns1 {
public static function
write() {
echo
"ns2\\ns1::write()\n";
}
}

ns1::write(); // 呼叫引入的 ns1\ns1::write()
ns1\ns1c::write(); // 呼叫引入的 ns1\ns1\ns1c::write()
namespace\ns1::write(); // 呼叫 ns2\ns1::write()
}
?>
phpcoder
9 年前
關於「函式和常數都不能透過 use 陳述式引入。」實際上在 PHP 5.6+ 中可以這樣做

<?php

// 引入函式 (PHP 5.6+)
use function My\Full\functionName;

// 替函式取別名 (PHP 5.6+)
use function My\Full\functionName as func;

// 引入常數 (PHP 5.6+)
use const My\Full\CONSTANT;
?>
okaresz
11 年前
更正 manolachef 的答案:define() 總是在全域命名空間中定義常數。

如同 nl-x at bita dot nl 在 https://php.dev.org.tw/manual/en/function.define.php 的註解中所述,常數「NULL」可以使用 define() 以區分大小寫的方式定義,但只能使用 constant() 擷取,使 NULL 大寫關鍵字的含義作為 null 類型的唯一值。
To Top