Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Котеров Д. В., Костарев А. Ф. - PHP 5. 2-е издание (В подлиннике) - 2008

.pdf
Скачиваний:
6114
Добавлен:
29.02.2016
Размер:
11.36 Mб
Скачать

ГЛАВА 31

Организация библиотек

Листинги данной главы можно найти в подкаталоге libraries.

Если вы читали знаменитый труд автора C++ Б. Страуструпа "Язык программирования C++", то знаете, что свою книгу он предваряет внушительного размера главой, в которой описываются возможности C++, не связанные напрямую с объектноориентированной парадигмой. Оказывается, писать на C++ можно и без классов, наследования и т. д., и даже в этом случае язык оказывается чрезвычайно удобным и предоставляет множество полезных возможностей.

Нам приятно сообщить, что PHP версии 5 обладает аналогичной особенностью. Развиваясь по той же экстенсивной схеме, что и C++, он вначале вобрал в себя возможности "объектно-неориентированного" подхода. Затем в третьей версии языка начали появляться некоторые "зачатки" ООП, но в то время они были настолько, не побоимся этого слова, смехотворны, что ими так никто толком и не пользовался. Четвертая версия внесла разнообразие: ядро языка было полностью переписано с применением новой технологии Zend Engine, однако объектная ориентированность и, в частности, работа с объектами и ссылками все еще оставляли желать лучшего. Наконец, пятая версия окончательно поставила точку: новый механизм работы со ссылками, поддержка исключений, работа с интерфейсами, которых так не хватало в ранних версиях PHP, наконец-то появились и могут быть использованы "на полную мощность".

Материал данной главы в большой степени может быть распространен на PHP версии 4, а точнее, на его поздние модификации — версии 4.1.0 и старше. Особенности PHP 5 будут оговариваться особо.

Один из самых важных вопросов в программировании — грамотное разделение исходного кода программ на относительно независимые группы функций — библиотеки. Библиотека может храниться в одном или нескольких файлах; случаются также ситуации, когда несколько библиотек хранятся в одном документе для ускорения загрузки.

Библиотека атомарна: достаточно в одном месте программы написать код ее подключения, и ниже этого места можно пользоваться предоставляемым сервисом.

556

Часть V. Объектно-ориентированное программирование на PHP

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

Конечно, существуют и другие инструкции загрузки: include_once, require и include. Однако в данной главе мы будем использовать исключительно require_once из соображений единообразия.

Подключение файла библиотеки

Предыдущее издание этой книги содержало целую главу, посвященную технике создания и подключения библиотек. Дело в том, что PHP 4 ранних версий (младше 4.0.1) не имели удобных возможностей для работы с библиотечными файлами, а самостоятельное написание таких средств на PHP представляло собой достаточно неприятную задачу. В частности, инструкция require, подключающая внешний файл к программе, не проверяла, подключен ли уже этот файл, а следовательно, часто приводила к ошибке при работе над крупным проектом независимых разработчиков. Инструкция require_once, появившаяся позже, частично решала эту проблему, однако ставила новую: требовалось явно указывать путь к библиотечному каталогу, что весьма утомительно, если скрипт насчитывает несколько десятков включающих друг друга файлов.

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

Корневой каталог библиотек

Чтобы добиться некоторого единообразия, введем ряд правил, которых будем в дальнейшем придерживаться. Все необходимые библиотеки будем хранить в одном каталоге — lib. Этот каталог находится в каталоге документов сервера (его имя всегда доступно через getenv("DOCUMENT_ROOT")) и для него запрещен просмотр через браузер. Запрета можно добиться при использовании сервера Apache, создав в каталоге файл .htaccess следующего содержания (листинг 31.1).

Листинг 31.1. Файл lib/.htaccess

## Запретить доступ через браузер.

deny from all

Теперь необходимо добиться, чтобы при вызове require_once "library_name.php" происходил поиск этого файла в указанном каталоге. Для этого следует модифицировать внутреннюю переменную PHP с именем include_path, которая обычно задается в файле php.ini.

Глава 31. Организация библиотек

557

Переменная include_path подобна переменной окружения PATH систем MS DOS и Windows (или Unix). Она содержит несколько разделенных через точку с запятой (или двоеточие) абсолютных путей поиска библиотек, которые последовательно просматриваются при выполнении команд require_once, require и т. д.

Так как изменять php.ini мы в большинстве случаев не можем (ведь он находится во власти хостинг-провайдера), в скрипте, которому требуется библиотека, воспользуемся функцией ini_set():

# добавить путь поиска библиотек ini_set("include_path", getenv("DOCUMENT_ROOT")."/lib");

#...

#теперь можно подключать: require_once "library_name.php";

Несколько путей поиска

Однако мы тут не учли, что PHP может искать библиотеки не только в одном, но и в нескольких каталогах. Например, чаще всего значение include_path устанавливается в php.ini таким (рассмотрен пример для Unix):

.: /usr/local/lib/php

Как видите, во-первых, просматривается текущий каталог, а во-вторых, "стандартный" каталог для библиотек PEAR.

PEAR (PHP Extension and Application Repository, репозиторий расширений и приложений для PHP) является структурированным набором "стандартных" библиотек для PHP, доступным по адресу http://pear.php.net. Фактически PEAR — это то, чего так не хватало PHP до определенного времени: ведь у пользователей Perl был CPAN (Comprehensive Perl Archive Network, всеобщий архив модулей Perl), http://www.cpan.org. PEAR, в отличие от CPAN, значительно лучше структурирован, однако, пока он содержит не так много модулей, как хотелось бы. Кроме того, сейчас он больше нацелен на PHP версии 4; не все библиотеки оттуда будут работать в пятой версии.

Следовательно, нам нужно лишь добавить наш путь поиска к уже существующим. Это можно сделать так:

$sep = getenv("COMSPEC")? ";" : ":";

ini_set("include_path", ini_get("include_path").$sep.getenv("DOCUMENT_ROOT")."/lib");

#...

#теперь можно подключать: require_once "library_name.php";

В Unix и Windows используются различные символы-разделители в списке путей поиска. Поэтому мы вынуждены на основе наличия переменной окружения COMSPEC (она характерна для Windows и нетипична для Unix) определять разделитель в первой строке листинга 31.2.

558

Часть V. Объектно-ориентированное программирование на PHP

Файл конфигурации

Чтобы не тянуть в каждом скрипте один и тот же код, приведенный в листинге 31.2, лучше всего выделить его в отдельный файл, который подключается в начале работы. Давайте так и сделаем — создадим файл lib/config.php и запишем в него указанные строчки.

Листинг 31.2. Файл lib/config.php

<?php ## Главный конфигурационный файл сайта.

// Подключается ко всем сценариям (автоматически или вручную)

if (!defined("PATH_SEPARATOR"))

define("PATH_SEPARATOR", getenv("COMSPEC")? ";" : ":");

ini_set("include_path", ini_get("include_path").PATH_SEPARATOR.dirname(__FILE__));

?>

Обратите внимание на одну деталь. Ранее для определения полного названия каталога с библиотеками мы использовали конструкцию getenv("DOCUMENT_ROOT")."/lib". Теперь же вместо нее применяется просто dirname(__FILE__). Это связано с тем, что файл config.php и так находится в каталоге библиотек, поэтому полный путь к каталогу удобнее будет определить через путь к файлу. Это позволит в будущем переименовывать библиотечный каталог и перемещать его в любое место, не затрагивая содержимое.

Теперь мы можем писать фрагменты такого вида:

require_once getenv("DOCUMENT_ROOT")."/lib/config.php";

...

require_once "library_name.php";

Преимущества использования путей подключения

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

require_once getenv("DOCUMENT_ROOT")."/lib/library_name.php";

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

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

require_once "$libRoot/library_name.php";

# или то же, но через заранее определенную константу: require_once LIB_ROOT."/library_name.php";

Глава 31. Организация библиотек

559

Мало того, что данный способ чрезмерно многословен, он еще и требует дополнительного согласования между различными разработчиками библиотек. Откуда же mr. John Doe, написавший свою библиотеку и открывший ее для всеобщего пользования, узнает, что вы в своей программе используете константу LIB_ROOT?.. Он о вас даже и не слышал, когда писал модуль.

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

модели MVC (Model – View – Controller, Модель – Вид – Контроллер) (см. гл. 4746). Такой скрипт можно назвать менеджером запросов; он вызывается при любом обращении к сайту. Не стоит и говорить в этом контексте о преимуществах переопределения include_path.

Старайтесь избегать динамического построения аргументов require-директив. Используйте только строковые константы. Помните, что это упрощает программу (или библиотеку) и делает ее универсальнее. Кроме того, такой подход позволит в будущем воспользоваться оптимизатором, однократно транслирующим PHP-программу во внутреннее представление для дальнейшей быстрой работы (например, Zend Optimizer).

Разрешение конфликтов имен

До этого момента ООП даже и не упоминалось. Что ж, настал и его черед.

Проблема именования функций

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

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

Похожая проблема встала перед разработчиком PHP ранних версий. Пока функций было немного, их называли как придется: current(), key(), sort(), range() и т. д. Однако со временем число функций настолько выросло, что давать им подобные названия стало нецелесообразно. Уж слишком велика вероятность, что при введении новой встроенной функции (что происходит довольно часто) перестанут работать многие пользовательские программы, использующие точно такое же имя в своих целях.

Одно из решений — добавлять в имена функций некоторый префикс, отвечающий их назначению. Так появились array_keys(), array_merge(), array_splice() и т. д. В их именах применяется префикс array_, свидетельствующий о том, что речь идет

560

Часть V. Объектно-ориентированное программирование на PHP

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

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

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

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

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

Пространство имен (namespace) — это имеющий имя фрагмент программы, содержащий в себе: функции, переменные, константы и другие именованные сущности. Для получения "извне" доступа к идентификатору из некоторого пространства имен служит синтаксис:

имяПространстваИмен::имяИдентификатора

Это так называемое полностью квалифицированное имя идентификатора.

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

Удобство такого построения в том, что оно позволяет переложить проблему именования идентификатора на плечи языка программирования. Представьте, что у нас есть два пространства имен: main (наша основная программа) и lib (библиотека стороннего производителя). И там, и там может быть определена функция length(). При этом доступ к одной функции будет выглядеть как lib::length(), а к другой — main::length(). Причем для кода, который сам находится в пространстве имен main, префикс main:: можно не указывать. Чтобы подкрепить описание примером, представим программу на "псевдоязыке", поддерживающем работу с пространствами имен (к таким языкам относятся, например, C++, C#, Perl, Java и т. д.):

пространство_имен lib { function length($str) { ... }

}

пространство_имен main { function length($arr) { ... }

...

echo length($a); # вызывается main::length() echo lib::length($s); # вызывается lib::length()

}

Глава 31. Организация библиотек

561

В различных языках программирования пространства имен называются по-разному. Например, в Perl и Java они известны как пакеты, в C++ — как namespace. В одних языках пространства имен могут быть вложенными друг в друга: идентификаторы охватывающих пространств доступны для внутренних. В других языках (в их числе и PHP) это не так.

Соглашения PEAR

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

Некоторые "стандарты" были переняты у программистов на Java. В данном случае Java — образцовый пример языка, стиля которого можно и даже нужно придерживаться при программировании на PHP. Это позволяет сделать программы и библиотеки аккуратными и автономными.

Соглашение 1: классы вместо пространств имен

К сожалению, PHP не имеет встроенной поддержки пространств имен, однако ее нетрудно реализовать через классы и статические члены.

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

Вместо того чтобы создавать пространство имен, мы напишем класс, в который поместим требуемые функции и переменные библиотеки, помечая их ключевым словом static (листинг 31.3). Это ключевое слово означает, что функция или переменная в действительности является членом класса, а не членом объекта этого класса. Иными словами, класс используется как пространство имен, а не как тип объектов в ООП.

Листинг 31.3. Файл lib/TestLib.php

<?php ## Пример библиотеки.

 

class TestLib {

 

const POW = 2;

# константа библиотеки

static $sumLength = 0;

# переменная библиотеки

static function length($s) { # функция библиотеки $len = strlen($s);

self::$sumLength += $len; return $len;

}

static function poweredLength($s) { $len = self::length($s);

return pow($len, self::POW); # возведение в степень

562

Часть V. Объектно-ориентированное программирование на PHP

}

}

?>

Не будем пока вдаваться в подробности библиотеки, а рассмотрим, как ее использовать в рядовом скрипте (или в другой библиотеке) — листинг 31.4.

Листинг 31.4. Файл t_testlib.php

<?php ## Пример использования библиотеки.

require_once "lib/config.php";

require_once "TestLib.php";

# ...

echo TestLib::length("abcd")."<br>";

echo TestLib::poweredLength("abcd");

?>

Обратите еще раз внимание, что в данном контексте класс выступает только в качестве пространства имен. Мы не используем такие возможности ООП, как, например, наследование, конструкторы и т. д.

Функции библиотеки

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

Переменные в библиотеке

Чтобы определить в библиотеке переменную, ее также необходимо предварить ключевым словом static. Наличие его в данном случае критично: если случайно пропустите, будет ошибка во время выполнения. Писать static var $a нельзя, вместо этого пишите просто static $a.

Константы в библиотеке

В библиотечном пространстве имен можно также определять и константы, содержащие число или строку. Константы сложного типа (например, массивы), к сожалению, недопустимы.

Данная возможность впервые появилась в PHP версии 5.

Константы в PHP, как и в C, принято записывать прописными буквами. В связи с этим использование "техники префиксов" для констант было бы не особенно удобно

Глава 31. Организация библиотек

563

(пришлось бы переводить префикс в верхний регистр), и работа с пространствами имен оказывается как нельзя кстати.

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

define("prefix", $_SERVER['DOCUMENT_ROOT']."/music"); class Test {

const filename = prefix . "/Faraway.mp3"; // Ошибка!

var $property = prefix . "/Clubbed_to_Death.mp3"; // Так тоже нельзя.

}

То есть, вы можете присваивать константе: число или строку, заданные явно. Допустимо также применение идентификаторов NULL, true и false, а также констант, определенных ранее вызовом define().

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

Рассмотрим листинг 31.3 чуть внимательнее. Возможно, вы уже заметили, что в нем присутствует одно неизвестное до этого момента слово — self. Выглядит оно так, будто бы является идентификатором некоторого пространства имен. Что же это?

Ранее уже говорилось, что внутри текущего пространства имен обычно его имя можно опускать, обращаясь к функциям без всякого префикса. К сожалению, в PHP это не так: префикс должен присутствовать обязательно (в том числе и при обращении к константам модуля). Чтобы сократить код, вместо имени текущего пространства имен можно указывать ключевое слово self, которое обозначает имя текущего класса.

Ключевое слово self именно обозначает имя текущего класса, это не константа. Например, попытка выполнения команды echo self приведет к ошибке: константа self не определена.

Константы не являются исключением из правила: их имена нужно использовать внутри класса с префиксом self:: (хотя в ранних версиях PHP 5 его и можно было опускать):

echo self::POW;

Конечно, и при обращении к константе извне префикс (имя класса) также обязателен:

echo TestLib::POW;

Соглашение 2: формат библиотеки

В языке Java принято, чтобы каждый класс располагался в отдельном файле. При этом имя класса должно полностью совпадать с именем файла. Рекомендации PEAR предлагают придерживаться точно такой же стратегии. Пример приведен в листинге 31.3.

Тут вы можете оставить комментарий к выбранному абзацу или сообщить об ошибке.

Оставленные комментарии видны всем.