命名空间

概览

在管理大型项目时,会遇到一个问题:如何避免在同一作用域中使用相同的名称来表示不同的目的。这在支持模块化设计和组件库的语言中尤其成问题。

命名空间是用于存放一组(通常是相关)类、接口、特性、函数和常量定义的容器。命名空间有两个作用:

  • 它们有助于避免名称冲突。
  • 它们允许通过更短、更方便、更易读的名称来访问某些长名称。

命名空间可以有子命名空间,其中子命名空间名称与另一个命名空间共享一个公共前缀。例如,命名空间Graphics可能具有子命名空间Graphics\D2Graphics\D3,分别用于二维和三维设施。除了它们的公共前缀之外,命名空间及其子命名空间之间没有特殊关系。子命名空间的父命名空间不需要实际存在才能使子命名空间存在。也就是说,NS1\Sub可以在没有NS1的情况下存在。

在没有命名空间定义的情况下,后续类、接口、特性、函数和常量的名称位于默认命名空间中,它本身没有名称。

命名空间PHPphp以及以这些前缀开头的子命名空间保留供 PHP 使用。

定义命名空间

语法

namespace-definition:
   namespace   namespace-name   ;
   namespace   namespace-nameopt   compound-statement

约束

除了空格和declare-语句之外,脚本中命名空间定义的第一次出现必须是该脚本中的第一件事。

脚本中所有命名空间定义的出现必须采用复合语句形式或必须不采用这种形式;两种形式不能在同一个脚本文件中混合使用。

当一个脚本包含不在命名空间内的源代码和在一个或多个命名空间内的源代码时,命名空间代码必须使用复合语句形式的命名空间定义

复合语句不能包含命名空间定义

语义

虽然命名空间可以包含任何 PHP 源代码,但该代码被包含在命名空间中的事实只影响类、接口、特性、函数和常量的声明和名称解析。对于其中每个,如果它们是使用非限定名称或限定名称定义的,则当前命名空间名称将附加到指定的名称之前。请注意,虽然定义有一个简短的名称,但引擎已知的名称始终是完整名称,并且可以作为完全限定名称(由当前命名空间名称和指定的名称组成)或导入指定。

命名空间和子命名空间名称不区分大小写。

预定义常量__NAMESPACE__包含当前命名空间的名称。

当同一个命名空间在多个脚本中定义,并且这些脚本被组合到同一个程序中时,该命名空间被认为是其各个贡献的合并。

非复合语句形式的命名空间定义的作用域一直持续到脚本结束,或直到词法上出现的下一个命名空间定义,以先出现者为准。复合语句形式的作用域是复合语句

示例

Script1.php

namespace NS1;
...       // __NAMESPACE__ is "NS1"
namespace NS3\Sub1;
...       // __NAMESPACE__ is "NS3\Sub1"

Script2.php

namespace NS1
{
...       // __NAMESPACE__ is "NS1"
}
namespace
{
...       // __NAMESPACE__ is ""
}
namespace NS3\Sub1;
{
...       // __NAMESPACE__ is "NS3\Sub1"
}

命名空间使用声明

语法

namespace-use-declaration:
   use   namespace-function-or-constopt   namespace-use-clauses   ;
   use   namespace-function-or-const   \opt   namespace-name   \   {   namespace-use-group-clauses-1   }   ;
   use   \opt   namespace-name   \   {   namespace-use-group-clauses-2   }   ;

namespace-use-clauses:
   namespace-use-clause
   namespace-use-clauses   ,   namespace-use-clause

namespace-use-clause:
   qualified-name   namespace-aliasing-clauseopt

namespace-aliasing-clause:
   as   name

namespace-function-or-const:
   function
   const

namespace-use-group-clauses-1:
   namespace-use-group-clause-1
   namespace-use-group-clauses-1   ,   namespace-use-group-clause-1

namespace-use-group-clause-1:
   namespace-name   namespace-aliasing-clauseopt

namespace-use-group-clauses-2:
   namespace-use-group-clause-2
   namespace-use-group-clauses-2   ,   namespace-use-group-clause-2

namespace-use-group-clause-2:
   namespace-function-or-constopt   namespace-name   namespace-aliasing-clauseopt

约束

命名空间使用声明只能在顶层或直接在命名空间定义的上下文中出现。

如果在同一作用域中多次导入相同的限定名称名称命名空间名称,则每次出现必须具有不同的别名。

语义

如果命名空间使用声明具有值为function命名空间函数或常量,则该语句将导入一个或多个函数。如果命名空间使用声明具有值为const命名空间函数或常量,则该语句将导入一个或多个常量。否则,命名空间使用声明没有命名空间函数或常量。在这种情况下,如果存在命名空间使用子句,则被导入的名称被认为是类/接口/特性。否则,存在命名空间使用组子句 2,在这种情况下,被导入的名称被认为是函数、常量或类/接口/特性,具体取决于相应的命名空间函数或常量在从属命名空间使用组子句 2中的存在或不存在。

请注意,常量、函数和类导入存在于不同的空间中,因此相同的名称可以用作函数和类导入,并应用于类和函数使用的相应情况,而不会相互干扰。

命名空间使用声明导入(即,使可用)一个或多个名称到一个作用域中,可以选择为它们分别提供一个别名。这些名称中的每一个都可以指定一个命名空间、一个子命名空间、一个类、一个接口或一个特性。如果存在命名空间别名子句,则其名称限定名称名称命名空间名称的别名。否则,限定名称中最右边的名称组件是限定名称的隐式别名。

示例

namespace NS1
{
  const CON1 = 100;
  function f() { ... }
  class C { ... }
  interface I { ... }
  trait T { ... }
}

namespace NS2
{
  use \NS1\C, \NS1\I, \NS1\T;
  class D extends C implements I
  {
    use T;  // trait (and not a namespace use declaration)
  }
  $v = \NS1\CON1; // explicit namespace still needed for constants
  \NS1\f();   // explicit namespace still needed for functions

  use \NS1\C as C2; // C2 is an alias for the class name \NS1\C
  $c2 = new C2;

  // importing a group of classes and interfaces
  use \NS\{C11, C12, I10};

  // importing a function
  use function \My\Full\functionName;

  // aliasing a function
  use function \NS1\f as func;
  use function \NS\{f1, g1 as myG};

  // importing a constant
  use const \NS1\CON1;
  use \NS\{const CON11, const CON12};

  $v = CON1; // imported constant
  func();   // imported function

  // importing a class, a constant, and a function
  use \NS\ { C2 as CX, const CON2 as CZ, function f1 as FZ };
}

请注意,即使限定名称没有以\开头,它也被视为绝对名称。例如

namespace b
{
  class B
  {
    function foo(){ echo "goodbye"; }
  }
}

namespace a\b
{
  class B
  {
    function foo(){ echo "hello"; }
  }
}

namespace a
{
  $b = new b\B();
  $b->foo(); // hello
  use b\B as C;
  $b = new C();
  $b->foo(); // goodbye
}

名称查找

当在源代码中使用现有名称时,引擎必须确定该名称是如何在命名空间查找方面找到的。为此,名称可以采用以下三种形式之一:

  • 非限定名称:此类名称只是简单的名称,没有任何前缀,例如以下表达式中的类名Point$p = new Point(3,5)。如果当前命名空间是NS1,则名称Point解析为NS1\Point。如果当前命名空间是默认命名空间,则名称Point解析为Point。对于非限定函数或常量名称,如果该名称在当前命名空间中不存在,则使用全局函数或该名称的常量。
  • 限定名称:此类名称有一个前缀,由命名空间名称和/或一个或多个子命名空间名称级别组成,在类、接口、特性、函数或常量名称之前。此类名称是相对的。例如,D2\Point可以用于引用当前命名空间的子命名空间D2中的类Point。这的一种特殊情况是,名称的第一个组件是关键字namespace。这意味着“当前命名空间”。
  • 完全限定名称:此类名称以反斜杠 (\) 开头,之后可以有一个命名空间名称和一个或多个子命名空间名称级别,最后是一个类、接口、特性、函数或常量名称。此类名称是绝对的。例如,\Graphics\D2\Point可以用于明确引用命名空间Graphics、子命名空间D2中的类Point

但是,如果在非默认命名空间内的上下文中使用非限定名称来表示常量或函数的名称,如果该命名空间没有定义此类常量或函数,则使用全局非限定名称。

例如

<?php
namespace A\B\C;
function strlen($str)
{
    return 42;
}
print strlen("Life, Universe and Everything"); // prints 42
print mb_strlen("Life, Universe and Everything"); // calls global function and prints 29

标准类型(如Exception)、常量(如PHP_INT_MAX)和库函数(如is_null)的名称是在任何命名空间之外定义的。要明确引用这些名称,可以在它们前面加上一个反斜杠 (\),如\Exception\PHP_INT_MAX\is_null