Пространство имён PHP
Пространства имён PHP были введены в PHP 5.3 для решения проблемы дублирования имён. В PHP две функции или класса не могут иметь одинаковое имя; в противном случае возникает фатальная ошибка.
Пространства имён PHP могут решить следующие два типа проблем:
- Конфликты имён между кодом, написанным пользователем, и внутренними классами/функциями/константами PHP или сторонними классами/функциями/константами.
- Создание псевдонима (или короткого) имени для очень длинных имён идентификаторов (обычно определяемых для смягчения первой проблемы), повышающее читаемость исходного кода.
Определение пространств имён
По умолчанию все константы, классы и имена функций помещаются в глобальное пространство, точно так же, как до того, как PHP стал поддерживать пространства имён.
Пространства имён объявляются с помощью ключевого слова `namespace`. Если файл содержит пространство имён, он должен объявить пространство имён до всего остального кода.
Синтаксис выглядит так:
<?php // 定义代码在 'MyProject' 命名空间中 namespace MyProject; // ... 代码 ...
Вы также можете определять код разных пространств имён в одном файле, например:
<?php
namespace MyProject;
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */ }
namespace AnotherProject;
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */ }
?>
Не рекомендуется использовать этот синтаксис для определения нескольких пространств имён в одном файле. Рекомендуется использовать синтаксис с фигурными скобками, приведённый ниже.
<?php
namespace MyProject {
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */ }
}
namespace AnotherProject {
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */ }
}
?>
Чтобы объединить глобальный код, не относящийся к пространствам имён, с кодом пространства имён, можно использовать только синтаксис с фигурными скобками. Глобальный код должен быть заключён в безымянный оператор `namespace` с фигурными скобками, например:
<?php
namespace MyProject {
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */ }
}
namespace { // 全局代码
session_start();
$a = MyProject\connect();
echo MyProject\Connection::start();
}
?>
Единственный допустимый код перед объявлением пространства имён — это оператор `declare`, используемый для определения кодировки исходного файла. Любой не-PHP код, включая пробельные символы, не может находиться перед объявлением пространства имён.
<?php
declare(encoding='UTF-8'); //定义多个命名空间和不包含在命名空间中的代码
namespace MyProject {
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */ }
}
namespace { // 全局代码
session_start();
$a = MyProject\connect();
echo MyProject\Connection::start();
}
?>
Следующий код вызовет синтаксическую ошибку:
<html> <?php namespace MyProject; // 命名空间前出现了“<html>” 会致命错误 - 命名空间必须是程序脚本的第一条语句 ?>
Подпространства имён
Подобно связи между каталогами и файлами, пространства имён PHP также позволяют задавать иерархические имена пространств имён. Поэтому имена пространств имён можно определять иерархическим способом:
<?php
namespace MyProject\Sub\Level; //声明分层次的单个命名空间
const CONNECT_OK = 1;
class Connection { /* ... */ }
function Connect() { /* ... */ }
?>
Приведённый выше пример создаёт константу `MyProject\Sub\Level\CONNECT_OK`, класс `MyProject\Sub\Level\Connection` и функцию `MyProject\Sub\Level\Connect`.
Использование пространств имён
На имена классов в пространствах имён PHP можно ссылаться тремя способами:
Неквалифицированные имена, или имена классов без префикса,Например, `$a = new foo();` или `foo::staticmethod();`. Если текущее пространство имён — `currentnamespace`, `foo` будет разрешено в `currentnamespace\foo`. Если код, использующий `foo`, является глобальным и не содержится ни в каком пространстве имён, `foo` будет разрешено в `foo`. Предупреждение: если функция или константа в пространстве имён не определена, неквалифицированное имя функции или имя константы будет разрешено в глобальное имя функции или имя константы.
Квалифицированные имена, или имена с префиксом,Например, `$a = new subnamespace\foo();` или `subnamespace\foo::staticmethod();`. Если текущее пространство имен — `currentnamespace`, `foo` будет разрешено в `currentnamespace\subnamespace\foo`. Если код, использующий `foo`, является глобальным и не содержится ни в каком пространстве имен, `foo` будет разрешено в `subnamespace\foo`.
Полностью квалифицированные имена, или имена, включающие оператор глобального префикса,Например, `$a = new \currentnamespace\foo();` или `\currentnamespace\foo::staticmethod();`. В этом случае `foo` всегда разрешается в буквальное имя `currentnamespace\foo` в коде.
Ниже приведен пример использования этих трех методов:
код файла file1.php
<?php
namespace Foo\Bar\subnamespace;
const FOO = 1;
function foo() {}
class foo
{
static function staticmethod() {}
}
?>
код файла file2.php
<?php
namespace Foo\Bar;
include 'file1.php';
const FOO = 2;
function foo() {}
class foo
{
static function staticmethod() {}
}
/* 非限定名称 */
foo(); // 解析为函数 Foo\Bar\foo
foo::staticmethod(); // 解析为类 Foo\Bar\foo ,方法为 staticmethod
echo FOO; // 解析为常量 Foo\Bar\FOO
/* 限定名称 */
subnamespace\foo(); // 解析为函数 Foo\Bar\subnamespace\foo
subnamespace\foo::staticmethod(); // 解析为类 Foo\Bar\subnamespace\foo,
// 以及类的方法 staticmethod
echo subnamespace\FOO; // 解析为常量 Foo\Bar\subnamespace\FOO
/* 完全限定名称 */
\Foo\Bar\foo(); // 解析为函数 Foo\Bar\foo
\Foo\Bar\foo::staticmethod(); // 解析为类 Foo\Bar\foo, 以及类的方法 staticmethod
echo \Foo\Bar\FOO; // 解析为常量 Foo\Bar\FOO
?>
Обратите внимание, что к любому глобальному классу, функции или константе можно обратиться, используя полностью квалифицированное имя, например `\strlen()`, `\Exception` или `\INI_ALL`.
Доступ к глобальным классам, функциям и константам внутри пространства имен:
<?php
namespace Foo;
function strlen() {}
const INI_ALL = 3;
class Exception {}
$a = \strlen('hi'); // 调用全局函数strlen
$b = \INI_ALL; // 访问全局常量 INI_ALL
$c = new \Exception('error'); // 实例化全局类 Exception
?>
Пространства имен и динамические возможности языка
На реализацию пространств имен PHP влияют динамические возможности самого языка. Поэтому, чтобы преобразовать следующий код в пространства имен, нужно динамически обращаться к элементам.
код файла example1.php:
<?php
class classname
{
function __construct()
{
echo __METHOD__,"\n";
}
}
function funcname()
{
echo __FUNCTION__,"\n";
}
const constname = "global";
$a = 'classname';
$obj = new $a; // prints classname::__construct
$b = 'funcname';
$b(); // prints funcname
echo constant('constname'), "\n"; // prints global
?>
Вы должны использовать полностью квалифицированные имена (имена классов, включая префикс пространства имен). Обратите внимание, что, поскольку в динамических именах классов, именах функций или именах констант нет разницы между квалифицированными и полностью квалифицированными именами, ведущий обратный слеш не нужен.
Динамический доступ к элементам пространства имен
<?php
namespace namespacename;
class classname
{
function __construct()
{
echo __METHOD__,"\n";
}
}
function funcname()
{
echo __FUNCTION__,"\n";
}
const constname = "namespaced";
include 'example1.php';
$a = 'classname';
$obj = new $a; // 输出 classname::__construct
$b = 'funcname';
$b(); // 输出函数名
echo constant('constname'), "\n"; // 输出 global
/* 如果使用双引号,使用方法为 "\\namespacename\\classname"*/
$a = '\namespacename\classname';
$obj = new $a; // 输出 namespacename\classname::__construct
$a = 'namespacename\classname';
$obj = new $a; // 输出 namespacename\classname::__construct
$b = 'namespacename\funcname';
$b(); // 输出 namespacename\funcname
$b = '\namespacename\funcname';
$b(); // 输出 namespacename\funcname
echo constant('\namespacename\constname'), "\n"; // 输出 namespaced
echo constant('namespacename\constname'), "\n"; // 输出 namespaced
?>
Ключевое слово namespace и константа __NAMESPACE__
PHP поддерживает два абстрактных метода для доступа к элементам в текущем пространстве имён: магическую константу `__NAMESPACE__` и ключевое слово `namespace`.
Значение константы `__NAMESPACE__` — это строка, содержащая имя текущего пространства имён. В глобальном коде, не включённом ни в какое пространство имён, она содержит пустую строку.
Пример `__NAMESPACE__`, код в пространстве имён
<?php namespace MyProject; echo '"', __NAMESPACE__, '"'; // 输出 "MyProject" ?>
Пример `__NAMESPACE__`, глобальный код
<?php echo '"', __NAMESPACE__, '"'; // 输出 "" ?>
Константа `__NAMESPACE__` полезна при динамическом создании имён, например:
Использование `__NAMESPACE__` для динамического создания имён
<?php
namespace MyProject;
function get($classname)
{
$a = __NAMESPACE__ . '\\' . $classname;
return new $a;
}
?>
Ключевое слово `namespace` можно использовать для явного доступа к элементам в текущем пространстве имён или подпространстве имён. Оно эквивалентно оператору `self` в классах.
Оператор `namespace`, код в пространстве имён
<?php namespace MyProject; use blah\blah as mine; // 引入了 blah\blah 命名空间,并定义了个别名mine mine\mine(); // 调用函数 blah\blah\mine() namespace\blah\mine(); // 调用函数 MyProject\blah\mine() namespace\func(); // 调用函数 MyProject\func() namespace\sub\func(); // 调用函数 MyProject\sub\func() namespace\cname::method(); // 调用 MyProject\cname 类的静态方法 $a = new namespace\sub\cname(); // 实例化 MyProject\sub\cname 类的对象 $b = namespace\CONSTANT; // 将常量 MyProject\CONSTANT 的值赋给 $b ?>
Оператор `namespace`, глобальный код
<?php namespace\func(); // calls function func() namespace\sub\func(); // calls function sub\func() namespace\cname::method(); // calls static method "method" of class cname $a = new namespace\sub\cname(); // instantiates object of class sub\cname $b = namespace\CONSTANT; // assigns value of constant CONSTANT to $b ?>
Использование пространств имён: псевдонимы/импорты
Поддержка пространств имён в PHP имеет два способа использования псевдонимов или импортов: использование псевдонима для имён классов или использование псевдонима для имён пространств имён.
В PHP псевдонимы реализуются с помощью оператора `use`. Ниже приведён пример, использующий все три возможных способа импорта:
1. Импорт/использование псевдонимов с помощью оператора `use`
<?php namespace foo; use My\Full\Classname as Another; // 下面的例子与 use My\Full\NSname as NSname 相同 use My\Full\NSname; // 导入一个全局类 use \ArrayObject; $obj = new namespace\Another; // 实例化 foo\Another 对象 $obj = new Another; // 实例化 My\Full\Classname 对象 NSname\subns\func(); // 调用函数 My\Full\NSname\subns\func $a = new ArrayObject(array(1)); // 实例化 ArrayObject 对象 // 如果不使用 "use \ArrayObject" ,则实例化一个 foo\ArrayObject 对象 ?>
2. Включение нескольких операторов `use` в одну строку
<?php use My\Full\Classname as Another, My\Full\NSname; $obj = new Another; // 实例化 My\Full\Classname 对象 NSname\subns\func(); // 调用函数 My\Full\NSname\subns\func ?>
Операция импорта выполняется на этапе компиляции, но не для динамических имён классов, функций или констант.
3. Импорт и динамические имена
<?php use My\Full\Classname as Another, My\Full\NSname; $obj = new Another; // 实例化一个 My\Full\Classname 对象 $a = 'Another'; $obj = new $a; // 实际化一个 Another 对象 ?>
Кроме того, операция импорта влияет только на неквалифицированные и квалифицированные имена. Полностью квалифицированные имена, будучи детерминированными, не подвержены влиянию импорта.
4. Импорт и полностью квалифицированные имена
<?php use My\Full\Classname as Another, My\Full\NSname; $obj = new Another; // 实例化 My\Full\Classname 类 $obj = new \Another; // 实例化 Another 类 $obj = new Another\thing; // 实例化 My\Full\Classname\thing 类 $obj = new \Another\thing; // 实例化 Another\thing 类 ?>
Использование пространств имён: резервные глобальные функции/константы
В пространстве имён, когда PHP встречает неквалифицированное имя класса, функции или константы, он использует разные стратегии приоритета для разрешения имени. Имена классов всегда разрешаются в имена в текущем пространстве имён. Поэтому при доступе к внутренним системным именам классов или именам классов, не содержащимся в пространстве имён, необходимо использовать полностью квалифицированные имена, например:
1. Доступ к глобальным классам в пространстве имён
<?php
namespace A\B\C;
class Exception extends \Exception {}
$a = new Exception('hi'); // $a 是类 A\B\C\Exception 的一个对象
$b = new \Exception('hi'); // $b 是类 Exception 的一个对象
$c = new ArrayObject; // 致命错误, 找不到 A\B\C\ArrayObject 类
?>
Для функций и констант, если функция или константа не существует в текущем пространстве имён, PHP выполнит откат к использованию функции или константы в глобальном пространстве.
2. Резервные глобальные функции/константы в пространствах имён
<?php
namespace A\B\C;
const E_ERROR = 45;
function strlen($str)
{
return \strlen($str) - 1;
}
echo E_ERROR, "\n"; // 输出 "45"
echo INI_ALL, "\n"; // 输出 "7" - 使用全局常量 INI_ALL
echo strlen('hi'), "\n"; // 输出 "1"
if (is_array('hi')) { // 输出 "is not array"
echo "is array\n";
} else {
echo "is not array\n";
}
?>
Глобальное пространство
Если пространство имён не определено, все определения классов и функций находятся в глобальном пространстве, как и до того, как PHP ввёл концепцию пространств имён. Добавление префикса `\` перед именем указывает, что имя принадлежит глобальному пространству, даже если имя находится в других пространствах имён.
Объяснение использования глобального пространства
<?php
namespace A\B\C;
/* 这个函数是 A\B\C\fopen */
function fopen() {
/* ... */
$f = \fopen(...); // 调用全局的fopen函数
return $f;
}
?>
Порядок разрешения пространств имён
С появлением пространств имён самым частым источником ошибок стало использование классов: каков путь поиска класса?
<?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"
new D(); // 使用导入规则,创建命名空间 "B" 中定义的类 "D" 的一个对象
// 如果未找到,则尝试自动装载类 "B\D"
new F(); // 使用导入规则,创建命名空间 "C" 中定义的类 "E" 的一个对象
// 如果未找到,则尝试自动装载类 "C\E"
new \B(); // 创建定义在全局空间中的类 "B" 的一个对象
// 如果未发现,则尝试自动装载类 "B"
new \D(); // 创建定义在全局空间中的类 "D" 的一个对象
// 如果未发现,则尝试自动装载类 "D"
new \F(); // 创建定义在全局空间中的类 "F" 的一个对象
// 如果未发现,则尝试自动装载类 "F"
// 调用另一个命名空间中的静态方法或命名空间函数
B\foo(); // 调用命名空间 "A\B" 中函数 "foo"
B::foo(); // 调用命名空间 "A" 中定义的类 "B" 的 "foo" 方法
// 如果未找到类 "A\B" ,则尝试自动装载类 "A\B"
D::foo(); // 使用导入规则,调用命名空间 "B" 中定义的类 "D" 的 "foo" 方法
// 如果类 "B\D" 未找到,则尝试自动装载类 "B\D"
\B\foo(); // 调用命名空间 "B" 中的函数 "foo"
\B::foo(); // 调用全局空间中的类 "B" 的 "foo" 方法
// 如果类 "B" 未找到,则尝试自动装载类 "B"
// 当前命名空间中的静态方法或函数
A\B::foo(); // 调用命名空间 "A\A" 中定义的类 "B" 的 "foo" 方法
// 如果类 "A\A\B" 未找到,则尝试自动装载类 "A\A\B"
\A\B::foo(); // 调用命名空间 "A" 中定义的类 "B" 的 "foo" 方法
// 如果类 "A\B" 未找到,则尝试自动装载类 "A\B"
?>
Разрешение имён подчиняется следующим правилам:
- Вызовы функций, классов и констант с полностью квалифицированными именами разрешаются во время компиляции. Напримерnew \A\Bразрешается в классA\B。
- Все неквалифицированные имена и квалифицированные имена (не полностью квалифицированные имена) преобразуются во время компиляции в соответствии с текущими правилами импорта. Например, если пространство имёнA\B\Cимпортируется какC, то вызовC\D\e()будет преобразован вA\B\C\D\e()。
- Внутри пространства имён все квалифицированные имена, которые не были преобразованы в соответствии с правилами импорта, получают префикс в виде имени текущего пространства имён. Например, в пространстве имёнA\Bпри вызовеC\D\e(), тогдаC\D\e()будет преобразован вA\B\C\D\e() 。
- Неквалифицированные имена классов преобразуются во время компиляции в соответствии с текущими правилами импорта (полное имя заменяет краткое импортированное имя). Например, если пространство имёнA\B\Cимпортируется как C, тогдаnew C()преобразуется вnew A\B\C() 。
- Внутри пространства имён (например, A\B) вызовы функций с неквалифицированными именами разрешаются во время выполнения. Например, для функцииfoo()вызов разрешается следующим образом:
- Поиск в текущем пространстве имён функции с именемA\B\foo()функция
- Попробуйте найти и вызвать функциюGlobalв глобальном пространствеfoo()。
- Внутри пространства имен (например,A\B), вызовы неквалифицированных или квалифицированных имен классов (не полностью квалифицированных имен) разрешаются во время выполнения. Ниже приведен процесс разрешения для вызоваnew C()иnew D\E()процесс разрешения: new C()разрешение:
- ПоискA\B\Cкласса в текущем пространстве имен.
- Попытка автозагрузки классаA\B\C。
- Припишите имя текущего пространства имен к имени класса, в результате получится:A\B\D\E, затем ищите этот класс.
- Попытка автозагрузки классаA\B\D\E。