Что нового в PHP V5.3: Часть 3. Пространства имен

В ближайшее время должна быть выпущена версия PHP V5.3. Серия статей "Что нового в PHP V5.3" посвящена замечательным новым функциям этого выпуска. В части 1 рассматривались новинки РНР 5.3 в сфере объектно-ориентированного программирования и управления объектами, а в части 2— лямбда-функции и замыкания. В третьей части мы рассмотрим пространства имен, одну из наиболее ожидаемых и обсуждаемых особенностей новой версии РНР. Концепция пространства имен помогает избежать проблем, возникающих при многократном определении функций, классов и констант с одним и тем же именем.

Джон Мертик, инженер-программист, SugarCRM

Джон Мертик (John Mertic) получил диплом инженера по вычислительной технике в университете Kent State University и в настоящее время работает инженером-программистом в компании SugarCRM. Он участвовал во многих проектах open source, главным образом в РНР-проектах; является создателем и хранителем PHP Windows Installer.



08.12.2009

Пространства имен существуют во многих языках, включая языки программирования C++ и Java™. Они помогают организовать крупные базы кода, в которых часто возникает проблема пересечения имен функций или классов внутри приложения. При использовании пространства имен можно определить, что делает та или иная функция или утилита и даже выяснить ее источник. Примером служит пространство имен System в С#, которое содержит все функции и классы среды .NET.

В других языках, лишенных формальных пространств имен (таких как PHP V5.2 и более ранние версии), пространство имен часто пытаются эмулировать, используя специальные соглашения об именах внутри имен классов или функций. Так поступает Zend Framework, где каждое из имен классов начинается с Zend, а каждое дочернее пространство имен отделяется знаком подчеркивания. Например, определение класса Zend_Db_Table— это класс, составляющий часть Zend Framework и выполняющий функции базы данных. Проблема такого подхода заключается в том, что результирующий код может стать слишком многословным, особенно если класс или функция имеет глубину в несколько уровней (примером из Zend Framework Zend_Cache_Backend_Apc). Другая проблема связана с тем, что такому стилю должен соответствовать весь код, а этого трудно достичь в случае интеграции в приложение стороннего кода, который не соответствует такому соглашению об именах.

Путь, который прошла идея пространств имен в РНР, довольно извилист. Первоначально их планировалось включить в PHP V5, но на стадии разработки от этого отказались из-за невозможности достичь нужной реализации. На стадии PHP V6 эта работа возобновилась, а после принятого в 2007 году решения собрать все усовершенствования, не связанные с Unicode, еще в одной версии PHP V5.х пространства имен оказались в версии PHP V5.3. Хотя многие свойства пространств имен остались неизменными с момента их первоначальной разработки, самой большой проблемой оказался вопрос о том, какой оператор для этого использовать: мнения членов сообщества разделились. Наконец, в октябре 2008 года было принято окончательное решение об использовании обратной косой черты.

Пространства имен в PHP

PHP перенял большую часть синтаксиса и конструкции пространств имен из других языков — главным образом, С++. Однако в некоторых обстоятельствах он работает с пространствами имен уникальным образом, что может стать препятствием для пользователей, ожидающих полного клонирования пространств имен из других языков. В этом разделе мы рассмотрим, как пространства имен работают в РНР.

Определение пространства имен

Определение новых пространств имен осуществляется тривиально. Для этого достаточно добавить в качестве первой команды или вывода в файл строку, приведенную в листинге 1.

Листинг 1. Определение пространства имен
<?php 
namespace Foo; 
class Example {} 
?>

Важно понимать, что приведенная выше декларация namespace должна быть первой командой или выводом в файл. Наличие перед ней чего-то другого приведет к фатальной ошибке. Некоторые примеры этого содержатся в листинге 2.

Листинг 2. Неправильное определение пространства имен
/* File1.php */ 
<?php 
echo "hello world!"; 
namespace Bad; 
class NotGood {}
?> 

/* File2.php */ 
 <?php 
namespace Bad; 
class NotGood {} 
?>

В первой части листинга 2 мы попытались перед определением пространства имен вывести сообщение на консоль, что привело к фатальной ошибке. Во второй части листинга мы добавили дополнительное пространство перед открывающим тегом <?php, что также привело к фатальной ошибке. Важно следить за этим по мере написания кода, так как это типичная ошибка при работе с пространствами имен в РНР.

Однако оба приведенных выше примера можно переписать, выделив определение пространства имен и код, который должен составлять часть декларации пространства имен, в отдельный файл, который можно включить в оригинальные файлы (листинг 3).

Листинг 3. Исключение неправильного определения пространства имен
/* Good.php */ 
<?php 
namespace Good; 
class IsGood() {} 
?> 

/* File1.php */ 
<?php 
echo "hello world!"; 
include './good.php'; 
?>

/* File2.php */ 
 <?php 
include './good.php'; 
?>

Теперь, когда мы знаем, как определить пространство имен для кода внутри файла, рассмотрим, как использовать этот код с пространством имен в приложении.

Использование кода с пространством имен

После того как пространство имен определено и код помещен в него, им можно легко пользоваться внутри приложения. Существуют разные способы обращения к функции, классу или константе в пространстве имен. Один из них – явная ссылка на пространство имен в виде префикса к вызову. Другая возможность - определить псевдоним для пространства имен и добавлять к вызову этот псевдоним, что укорачивает префикс пространства имен. Наконец, можно просто указать пространство имен внутри кода, что делает его пространством имен по умолчанию, и все вызовы по умолчанию будут ссылаться на это пространство имен. Различия между этими способами вызова иллюстрируются в листинге 4.

Листинг 4. Вызов функции внутри пространства имен
/* Foo.php */ 
<?php 
namespace Foo; 
function bar() 
{ 
    echo "calling bar....";
} 
?> 

/* File1.php */ 
<?php 
include './Foo.php'; 
Foo/bar(); // outputs "calling bar...."; 
?> 

/* File2.php */ 
<?php 
include './Foo.php';
use Foo as ns; 
ns/bar(); // outputs "calling bar...."; 
?> 

/* File3.php */ 
<?php 
include './Foo.php'; 
use Foo; 
bar(); // outputs "calling bar...."; 
?>

Листинг 4 демонстрирует разные способы вызова функции bar() в пространстве имен Foo. В File1.php мы выполняем явное обращение, предваряя вызов именем пространства имен. В File2.php используется псевдоним для пространства имен, так что имя пространства имен заменяется этим псевдонимом. Наконец, в File3.php просто указано пространство имен, что позволяет нам обращаться к bar() без всякого префикса.

В файле можно определить более одного пространства имен, добавив в него другие вызовы namespace (листинг 5).

Листинг 5. Множество пространств имен в файле
<?php 
namespace Foo; 
class Test {} 

namespace Bar; 
class Test {} 

$a = new Foo\Test; 
$b = new Bar\Test; 

var_dump($a, $b); 

Выход: 
object(Foo\Test)#1 (0) {  
}  
object(Bar\Test)#2 (0) {  
}

Теперь, имея основу для обращений внутри пространства имен, рассмотрим некоторые более сложные ситуации.

Разрешение пространств имен

Одна из трудностей освоения пространств имен заключается в том, чтобы разобраться в работе механизма их разрешения. Хотя простые случаи вроде рассмотренных в листинге 4 понятны без разъяснений, при попытке вложить одно пространство имен в другое или, находясь в одном пространстве имен, обратиться к глобальному пространству, начинаются проблемы. В HP V5.3 есть правила автоматического разрешения таких ситуаций.

Создадим командой include несколько файлов, в каждом из которых определена функция hello().

Листинг 6. Функция hello(), определенная в разных пространствах имен
/* global.php */ 
<?php 
function hello() 
{ 
    echo 'hello from the global scope!';
} 
?> 

/* Foo.php */ 
<?php 
namespace Foo; 
function hello() 
{ 
    echo 'hello from the Foo namespace!';
} 
?> 

/* Foo_Bar.php */ 
<?php 
namespace Foo/Bar; 
function hello() 
{ 
    echo 'hello from the Foo/Bar namespace!';
} 
?>

В листинге 6 функция hello() определяется трижды в трех разных областях действия: в глобальной области действия, в пространстве имен Foo и в пространстве имен Foo/Bar. Область действия, в которой совершается вызов функции hello(), определяет, какая из функций hello() будет вызвана. В листинге 7 приведен пример таких вызовов. Здесь мы используем пространство имен Foo, чтобы проиллюстрировать, как вызвать функцию hello() в других пространствах имен.

Листинг 7. Вызов всех функций hello() из пространства имен Foo
<?php 
include './global.php'; 
include './Foo.php';
include './Foo_Bar.php';

use Foo; 

hello();         // выводит 'hello from the Foo namespace!' 
Bar\hello();   // выводит 'hello from the Foo/Bar namespace!' 
\hello();       // выводит 'hello from the global scope!' 
?>

Отсюда видно, что при ссылках на дочернее пространство имен внутри текущего пространства имен префикс можно сократить (обращение Foo/Bar/hello() можно сократить до Bar/hello()). Также видно, как указать, что мы хотим вызвать метод в глобальной области действия: достаточно просто предварить вызов оператором пространства имен.

Теперь, когда у нас есть механизмы для работы с пространствами имен, рассмотрим, как можно применять их в собственном коде.


Примеры применения пространств имен в PHP

Общая задача пространств имен — помочь программисту лучше организовать свой код, исключив ряд определений, которые остаются в глобальной области действия. В этом разделе мы познакомимся с некоторыми примерами того, как пространства имен помогают достичь этой цели при минимальных усилиях.

Пространство имен для стороннего кода

Во многих приложениях РНР используется код из разных источников. Это может быть тщательно разработанный код, такой как тот, что хранится в репозитории PEAR, код из различных интегрированных сред разработки, таких как CakePHP или Zend Framework, другой код, взятый из разных мест в Интернете. Одна из самых больших проблем, возникающих при интеграции такого кода, заключается в том, что он может плохо сочетаться с существующим кодом; имена функций или классов могут конфликтовать с теми, что уже используются в приложении.

Примером этого служит пакет PEAR Date. В нем используется класс с именем Date— очень распространенным именем класса, которое с большой вероятностью может встречаться в других местах кода. Хорошим решением этой проблемы будет добавление простой команды пространства имен в самом начале файла Date.php внутри этого пакета. Теперь можно определить, где именно мы хотим использовать класс PEAR Date вместо своего собственного.

Листинг 8. Использование класса PEAR Date в соответствии с определением пространства имен
<?php 

require_once('PEAR/Date.php'); 

use PEAR\Date;    // имя пространства имен, указанное в PEAR/Date.php

// так как текущее пространство имен PEAR\Date, префикс нам не нужен 
// имя пространства имен
$now = new Date(); 
echo $now->getDate();  // выводит дату в формате ISO

// этот пример иллюстрирует полное пространство имен, указанное как часть имени класса 
$now = new PEAR\Date\Date(); 
echo $now->getDate();  // выводит дату в формате ISO
?>

Мы определили класс PEAR Date внутри пространства имен PEAR/Date в файле PEAR/Date.php, так что теперь достаточно включить этот код в наш файл и использовать новое пространство имен или префикс с именем пространства имен для имен классов или функций. Таким способом можно безопасно включать в свое приложение сторонний код.

Конфликты имен – проблема не только стороннего кода. Она может возникнуть и в крупных базах кода, в которых есть части, не предназначенные для одновременного использования. В следующем разделе мы увидим, как пространства имен упрощают жизнь в этой ситуации.


Исключение конфликтов между именами служебных функций

Почти каждое приложение РНР содержит ряд служебных методов. Они не входят ни в какие объекты приложения и не имеют в нем собственного места, но обслуживают приложение в целом. Однако по мере роста самого приложения такие утилиты могут вызвать проблемы.

Подобные проблемы могут возникнуть в процессе поблочного тестирования, когда мы пишем код, тестирующий код самого приложения. Большинство инструментов поблочного тестирования устроено так, что каждый тест выполняется со всем тестовым комплексом целиком. Например, у нас есть два служебных файла, которые никогда не работают вместе, но в тестовом комплексе они должны присутствовать, так как мы тестируем все приложение целиком. Хотя подобный подход к разработке приложений вообще не сулит ничего хорошего в долгосрочном плане, в крупных унаследованных базах кода он часто имеет место.

Листинг 9 показывает, как этого избежать. У нас есть два файла, utils_left.php и utils_right.php, которые представляют собой наборы служебных функций для пользователей-левшей и правшей. Для каждого файла мы определили его собственное пространство имен.

Листинг 9. utils_left.php и utils_right.php
/* utils_left.php */
<?php 
namespace Utils\Left;

function whichHand() 
{ 
    echo "I'm using my left hand!";
} 
?> 

/* utils_right.php */
<?php 
namespace Utils\Right; 

function whichHand() 
{ 
    echo "I'm using my right hand!";
} 
?>

Определим функцию whichHand(), которая указывает, какой рукой мы пользуемся. В листинге 10 видно, как можно легко и безопасно включить оба файла и переключаться между этими пространствами имен.

Листинг 10. Пример совместного использования utils_left.php и utils_right.php
<?php 
include('./utils_left.php'); 
include('./utils_right.php');

Utils\Left\whichHand();    // выход "I'm using my left hand!" (Я левша)
Utils\Right\whichHand();  // выход "I'm using my right hand!" (Я правша)

use Utils\Left; 
whichHand();                 // выход "I'm using my left hand!" (Я левша)

use Utils\Right; 
whichHand();                 // выход "I'm using my right hand!"(Я правша)

Теперь оба файла можно безопасно включать в код вместе, и мы указываем, какое пространство имен использовать для обработки вызова функции. При этом влияние на уже существующий код минимально, так как для поддержки этого достаточно всего лишь добавить в начале файла оператор use, чтобы указать, какое пространство имен нужно использовать.

Эту же идею можно распространить за пределы определенного нами кода РНР. В следующем разделе мы рассмотрим, как в пространстве имен можно переопределить даже внутренние функции.


Переопределение имен внутренних функций

В РНР внутренние функции часто бывают очень полезными, но иногда они делают не совсем то, что вам нужно. Чтобы добиться желаемого, может понадобиться изменение их поведения, однако во избежание конфликтов в будущем хотелось бы переопределить новую функцию под другим именем.

Одно из мест, где это может потребоваться, — файловая система. Допустим, мы хотим гарантировать, что любой файл, созданный функцией file_put_contents(), имеет определенный набор разрешений. Например, если нужно, чтобы создаваемые этим способом файлы были файлами только для чтения, можно переопределить эту функцию в новом пространстве имен, как показано в листинге 11.

Листинг 11. Определение функции file_put_contents() внутри пространства имен
<?php 
namespace Foo; 

function file_put_contents( $filename, $data, $flags = 0, $context = null ) 
{ 
    $return = \file_put_contents( $filename, $data, $flags, $context );
    
    chmod($filename, 0444);

    return $return;
} 
?>

Мы обращаемся к внутренней функции file_put_contents() внутри этой функции и предваряем имя функции обратной косой чертой, чтобы указать, что ее нужно обрабатывать в глобальной области действия. Это значит, что будет вызываться внутренняя функция. После обращения к внутренней функции мы устанавливаем нужные разрешения для файла при помощи chmod().

Это лишь несколько примеров того, как пространства имен можно использовать для повышения качества кода. В каждом случае мы избегаем грубых приемов, таких как префексирование имени функции или класса, чтобы сделать это имя уникальным. И теперь мы знаем, как использовать пространство имен, чтобы обезопасить включение в крупные приложения стороннего кода и не беспокоиться о конфликтах имен.


Заключение

Пространства имен в PHP V5.3 служат хорошим дополнением к языку, помогая разработчикам разумно организовать код внутри приложения. Они позволяют избежать использования стандартных соглашений об именах для управления пространствами имен, помогая писать эффективный код. Хотя его пришлось долго ждать, добавление пространств имен стало желанным дополнением для любого крупного проекта РНР, страдающего от конфликтов имен.

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source
ArticleID=454490
ArticleTitle= Что нового в PHP V5.3: Часть 3. Пространства имен
publish-date=12082009