objects05 12

Сборки и пространства имен

Как мы уже видели, в PHP поддержка объектно-ориентированного программирования осуществляется через конструкции языка, такие как классы и методы. Кроме того, в PHP также предусмотрен большой набор функций и классов, предназначенных для облегчения работы с объектами программисту.

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

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

PHP и сборки

Несмотря на то что, по сути, в PHP концепция сборок не поддерживалась, разработчики традиционно использовали различные схемы наименования и файловую систему для организации своего кода в такие структуры. Чуть ниже я опишу способ использования файлов и каталогов операционной системы для организации кода. А пока что я познакомлю вас со схемами наименования, а также с новой, но связанной с этим, возможностью — поддержкой пространств имен.

До появления PHP 5.3 разработчики были вынуждены подбирать для файлов и классов своего проекта уникальные имена, поскольку с точки зрения интерпретатора все они находились в одном глобальном контексте. Другими словами, если вы, например, определяли класс ShoppingBasket, он тот час же становился доступным всему вашему приложению. Это порождало две большие проблемы. Первая и самая неприятная проблема была связана с потенциальным конфликтом имен в проекте. Наверняка, вы считали эту проблему несущественной, правда? В конце концов, все, что вам нужно было сделать — так это запомнить, что всем классам нужно было назначать уникальные имена! Однако проблема состояла в том, что все чаще и чаще в своих проектах нам приходилось использовать библиотечный код. Это очень хороший подход, поскольку он способствовал повторному использованию кода. Но что происходило, если в своем проекте вы использовали такую конструкцию:

Код PHP
// Допустим в этом файле содержится определение класса DBConnector
require_once 'useful/DBConnector.php';

class DBConnector { 
	// Какой-то код
}

Ну, наверное, вы уже догадались, правда? Это приводило к неустранимой ошибке переопределения класса DBConnector. Естественно, что существовал традиционный обходной маневр, о котором я сейчас расскажу. Он заключался в том, что перед именем класса нужно было указать имя сборки, чтобы в результате получились уникальные имена классов:

Код PHP
// Допустим в этом файле содержится определение класса DBConnector
require_once 'useful/DBConnector.php';

class myclass_DBConnector { 
	// Какой-то код
}
// my.php

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

С этой проблемой вам предстоит столкнуться и сейчас, поскольку большинству из нас часто приходится сопровождать в той или иной форме устаревший код на протяжении длительного периода времени.

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

Пространства имен были в списке требований пользователей PHP на протяжении длительного времени. Они появились в версии PHP 5.3. Итак, что же такое пространство имен? По существу — это корзина, в которую вы можете поместить классы, функции и переменные. В пределах одного пространства имен вы можете обращаться к ним без всякого уточнения. Чтобы обратиться к этим элементам за пределами пространства имен, вам придется либо импортировать в код целое пространство имен, либо использовать ссылку на него.

Запутались? Поясним все на примере. Ниже я переписал предыдущий пример с использованием пространств имен:

Код PHP
<php
namespace my;
class DBConnector {
	// ...	
}

namespace useful;
class DBConnector {
	// ...
}
?>

Обратите внимание на ключевое слово namespace. Как вы уже, наверное, догадались, оно устанавливает указанное пространство имен. При использовании пространств имен в коде их объявление должно быть выполнено в первом операторе в файле (т.е. в приведенном коде перед <php не должно ничего быть, иначе возникнет ошибка из-за BOM). В приведенном выше примере я создал два пространства имен: my и useful. Однако обычно в программах используются более длинные пространства имен. Как правило, они начинаются с названия организации или проекта, а далее указывается название сборки. В PHP допускаются вложенные пространства имен. Для разделения их на уровни используется символ обратной косой черты '\', как показано ниже:

Код PHP
namespace ru\addphp\util;

class Debug {
    static function helloWorld() {
        print "Привет из класса Debug";
    }
}

При помещении этого кода в хранилище от меня требовалось указать название одного из моих доменов: addphp.ru. Поэтому я решил воспользоваться именем этого домена как собственным пространством имен. Этот прием хорошо знаком разработчикам на Java и C#, которые обычно присваивают своим сборкам подобные имена. При этом они изменяют порядок следования поддоменов от самого общего к частному. После создания собственного хранилища кода я должен определить помещаемые в него сборки. В данном случае после имени домена я использовал ключевое слово util.

Как же теперь я могу вызвать метод? На самом деле все зависит от того, из какого контекста я собираюсь это сделать. Если я вызываю метод из текущего пространства имен, то все происходит как и раньше, т.е. метод вызывается напрямую, как показано ниже:

Код PHP
Debug::helloWorld();

При этом имя класса остается не полностью определенным. В самом деле, поскольку мы и так находимся в пространстве имен ru\addphp\util, нам не нужно указывать перед ним какой-либо путь. Другое дело, если нам нужно вызвать метод нашего класса за пределами текущего пространства имен. Тогда нужно указать следующее:

Код PHP
namespace ru\addphp\util;

class Debug {
    static function helloWorld() {
        print "Привет из класса Debug";
    }
}

// Это уже другое пространство имен
namespace main;

\ru\addphp\util\Debug::helloWorld();

В данном примере я указал символ-разделитель перед ссылкой на пространство имен (т.е \ru\addphp\util\Debug а не просто ru\addphp\util\Debug). Это сделано чтобы указать абсолютное имя пространства имен, иначе возникнет ошибка интерпретации. Все происходит примерно так же, как и при указании абсолютных путей на файлы и URL.

Посмотрев код приведенного примера вы можете задать вопрос, разве введение пространств имен не должно было сократить имена классов и упростить вам набор программы? Само собой, имя класса Debug очень краткое и понятное, но такой "многословный" вызов метода этого класса получился потому, что мы работаем в рамках старого пространства имен. Все можно упростить, воспользовавшись ключевым словом use. Оно позволяет вам создать псевдонимы других пространств имен в текущем пространстве, как показано ниже:

Код PHP
namespace ru\addphp\util;

class Debug {
    static function helloWorld() {
        print "Привет из класса Debug";
    }
}

// Это уже другое пространство имен
namespace main;
use ru\addphp\util;

util\Debug::helloWorld();

При этом импортируется пространство имен ru\addphp\util, и ему неявно назначается псевдоним util. Обратите внимание на то, что перед его названием я не использую символ обратной косой черты. Все дело в том, что поиск указанного после ключевого слова use пространства имен выполняется из глобального, а не текущего контекста. Если из нового пространства имен мне нужен только класс Debug, я могу импортировать только его, а не все пространство, как показано ниже:

Код PHP
// ...
namespace main;
use ru\addphp\util\Debug;

Debug::helloWorld();

Но вот что произойдет, если в пространстве имен main уже определен другой класс Debug? Я думаю, вы уже догадались - возникнет ошибка. Итак, я снова наступил на те же грабли, столкнувшись с конфликтом имен классов. К счастью, из подобной ситуации очень легко выбраться, явно указав псевдоним класса, как показано ниже:

Код PHP
namespace ru\addphp\util;

class Debug {
    static function helloWorld() {
        print "Привет из класса Debug";
    }
}

namespace main;
use ru\addphp\util\Debug as uDebug;

class Debug { /* Это класс Debug в пространстве имен main */ }

uDebug::helloWorld();

Воспользовавшись директивой as в операторе use я явно изменил псевдоним класса Debug на uDebug. Если вы пишете код в текущем пространстве имен и хотите обратиться к классу, находящемуся в глобальном (без имени) пространстве, просто укажите перед его именем обратную косую черту. Ниже приведен пример:

Код PHP
<?php
// Глобальный контекст - пространство имен не используется
namespace {
	class Debug {
    	static function helloWorld() {
        	print "Привет из глобального класса Debug";
    	}
	}
}

namespace main {
	class Debug {
		static function helloWorld() {
        	print "Привет из ".__NAMESPACE__;
    	}	
	}
	
	Debug::helloWorld();	// Доступ к локальному классу
	\Debug::helloWorld();	// Доступ к глобальному классу
}
?>

В локальном пространстве имен определен собственный класс Debug. Это очень ценный пример, поскольку в нем показано, как пользоваться константой __NAMESPACE__. Она содержит имя текущего пространства имен, что может очень пригодиться при отладке кода.

В показанном примере применен альтернативный синтаксис определения пространств имен, в котором используются фигурные скобки после названия пространства имен. Если вам приходится объединять несколько пространств имен в одном файле, то этот синтаксис предпочтительнее. Хотя обычно рекомендуется в одном файле определять только одно пространство имен. Синтаксис с фигурными скобками позволяет сделать еще одну полезную вещь — переключиться к глобальному пространству имен в том же файле.

Автозагрузка

В некоторых ситуациях вам может понадобиться так организовать классы, чтобы определение каждого из них находилось в отдельном файле. У этого подхода есть свои недостатки, поскольку процесс включения файла влечет за собой некоторые издержки. Однако подобный вид организации может быть очень полезным, особенно при расширении системы, когда новые классы должны включаться во время выполнения программы. В таких случаях можно связать имя класса с именем файла, содержащего определение этого класса. Например, мы можем определить класс ShopProduct в файле с именем ShopProduct.php. С другой стороны, при использовании соглашения PEAR, для этого класса, скорее всего, было бы выбрано имя в соответствии с назначением сборки: business_ShopProduct.php.

В PHP 5 введена функция-перехватчик __autoload(), помогающая автоматизировать включение файлов классов. Ей передается один строковый аргумент. Когда интерпретатор PHP сталкивается с попыткой создания экземпляра неизвестного класса, он вызывает функцию __autoload() (если она определена), передавая ей имя класса в качестве аргумента. От программиста зависит как он определит стратегию обнаружения и включения недостающего файла класса. Ниже показан пример простой функции __autoload():

Код PHP
function __autoload( $classname ) {
    require_once( "{$classname}.php" );
}

$product1 = new ShopProduct('Собачье сердце', 'Михаил', 'Булгаков', 5.99);

Если предположить, что мы еще не включили файл, в котором определяется класс с именем ShopProduct, то создание экземпляра ShopProduct, похоже, должно закончиться неудачей. Интерпретатор PHP "видит", что мы определили функцию __autoload(), он вызывает ее и передает ей строку "ShopProduct". В нашей реализации мы просто пытаемся включить файл ShopProduct.php. Конечно, это получится, только если файл находится в текущем рабочем каталоге или в одном из каталогов, включенных в путь поиска. В этой ситуации не существует простого способа работы с сборками. И это еще один случай, когда схема наименования, принятая в PEAR, себя оправдывает.

В зависимости от способа организации классов и файлов, функция __autoload() может с успехом использоваться для управления включениями библиотечных файлов. Функция __autoload() является довольно мощным средством, но и у нее есть свои ограничения. В частности, эта функция может быть определена в проекте только один раз. Если вы хотите динамически изменить функцию __autoload() во время выполнения программы, используйте функцию spl_autoload_register(), с помощью которой это можно сделать.

Расширенные возможности использования объектов
Функции для работы с классами и объектами

Комментарии (0)

Результаты поиска по запросу

Система Orphus