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

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

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

564

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

Формально каждая библиотека (или модуль), которую мы должны создать, состоит из трех секций:

подключение других библиотек (если это требуется);

определение основного класса (пространства имен) библиотеки;

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

Не стоит рассчитывать, что код последней секции будет выполнен в глобальной области видимости. Это означает, что ему не доступны напрямую глобальные переменные. Ведь подключение модуля при помощи require_once может произойти и внутри какой-нибудь функции (дальше мы как раз столкнемся с таким примером), а значит, активна будет область видимости этой функции. Соответственно, глобальные переменные в последней секции лучше либо вообще не использовать, либо работать с ними через массив $GLOBALS, например: $GLOBALS['имяПеременной'].

Приведем еще один пример библиотеки, которая использует в своей работе модуль File_Find из PEAR. Она содержит в себе простую функцию, предназначенную для получения всех имен файлов в указанном каталоге (листинг 31.5).

Листинг 31.5. Файл lib/MyFileFind.php

<?php ## Пример библиотечного файла.

##

## Секция подключения необходимых библиотек.

##

# Подключаем библиотеку PEAR File_Find для поиска файлов. require_once "File/Find.php";

##

## Секция определения главного пространства имен.

##

class MyFileFind { const EXT = "php";

static $cache = array(); function readdir($dir) {

#Уже запрашивали содержимое этого каталога?..

#Если да, то берем предыдущий результат.

if (isset(self::$cache[$dir])) return self::$cache[$dir];

#Иначе вызываем функцию из File_Find, сохраняем

#данные в кэше и возвращаем результат.

return self::$cache[$dir] = File_Find::glob(".*", $dir, self::EXT);

}

}

##

## Секция автозапуска при подключении.

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

565

##

# Печатаем диагностическое сообщение (для примера).

echo "File ".__FILE__." loaded.<br>";

?>

Использующий эту библиотеку код может выглядеть так, как представлено в листинге 31.6.

Листинг 31.6. Файл t_myfilefind.php

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

require_once "lib/config.php";

#Если у вас не установлен PEAR, используем PEAR-модули,

#входящие в этот архив (в данном случае PEAR и File_Find). require_once "lib-pear/config.php";

require_once "MyFileFind.php";

echo "<pre>Содержимое корневого каталога:\n"; print_r(MyFileFind::readdir("/")); # подгружает содержимое каталога print_r(MyFileFind::readdir("/")); # второй раз данные взяты из кэша echo "Вот что в итоге находится в кэше модуля:\n";

print_r(MyFileFind::$cache);

# выводит содержимое кэша

?>

Обратите внимание на несколько деталей:

в качестве имени функции используется readdir(), а ведь в PHP уже есть такая встроенная функция. Тем не менее конфликта имен не возникает;

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

синтаксис MyFileFind::readdir() служит для обращения к функциям библиотеки, а синтаксис MyFileFind::$cache — для обращения к ее переменным.

Нужно заметить, что запись MyFileFind::$cache выглядит не совсем логично. Дело в том, что в PHP синтаксис $obj->$func() используется для вызова функции-члена объекта $obj, имя которой задано в переменной $func. Обращение же к переменной всегда предваряется знаком $. По аналогии можно было бы предположить, что MyFileFind::$cache — это тоже вызов функции, а обращение к переменной должно выглядеть как $MyFileFind::cache (именно так, кстати, и сделано в Perl). Однако разработчики PHP пошли иным путем, чем, на наш взгляд, внесли некоторую путаницу.

Соглашение 3: использование подкаталогов

Возможно, выше вы уже обратили внимание, что подключение библиотеки File_Find из PEAR происходит несколько странным образом. Вот строки:

# Подключаем библиотеку PEAR File_Find для поиска файлов.

566

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

require_once "File/Find.php";

Это объясняется тем, что обычно библиотеки не "сваливают в кучу", как мы делали

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

вMail и т. д. Еще бы: ведь в PEAR не одна сотня модулей; представьте себе, как бы выглядел их полный список без дополнительного подразделения.

Учитывая все это, группа поддержки PEAR рекомендует придерживаться следующих правил именования и расположения библиотек.

Если вы решили расположить модуль в некотором подкаталоге основного библиотечного каталога, имя модуля (его пространства имен или основного класса) должно это отражать: вместо слэшей (/ или \) используется подчерк ("_"). Расширение файла (PHP) в имени класса, конечно, опускается. Таким образом, по имени модуля всегда можно сказать, в каком каталоге он находится, и наоборот. Примеры:

require_once "Mail/sendmail.php";

# Mail_sendmail

require_once "PEAR/Command/Auth.php"; # PEAR_Command_Auth

Если ваша библиотека состоит из многих файлов, которые она подключает (как чаще всего и бывает), целесообразно выделить для нее отдельный каталог и хранить все модули (и вспомогательные файлы, если они есть) там. При этом предыдущее соглашение о составных именах классов остается в силе. Если понадобится из библиотеки обратиться к какому-то ее внутреннему файлу, это удобно сделать при помощи конструкции dirname(__FILE__)."/имяФайла.txt":

#Предположим, что рядом с файлом MyBigLibrary/Main.php

#расположен файл данных main.dtd.

class MyBigLibrary_Main {

const DTD_FILE = "main.dtd";

static function getDtd() {

return file_get_contents(dirname(__FILE__)."/".self::DTD_FILE);

}

}

Такая схема работает только в случае, если в именах файлов отсутствуют символы подчерка "_". Старайтесь их всячески избегать.

Автоматическая загрузка классов

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

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

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

567

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

Библиотека поддержки автозагрузки

Можно заметить, что концепция autoload-функции в PHP достаточно "сыровата". Так, определив __autoload() один раз, мы уже не можем ее переопределить. Не существует ничего подобного списку autoload-функций (по аналогии с shutdownфункциями, см. register_shutdown_function() в гл. 23). Вероятно, в ближайшем будущем разработчики PHP исправят ситуацию к лучшему или напишут модуль PEAR для работы с неограниченным числом autoload-функций.

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

Листинг 31.7. Файл t_autoload.php

<?php ## Проверка __autoload(). function __autoload($cls) {

echo "Запрос на загрузку класса $cls<br>";

}

echo NonExisted::$a; ?>

Данная программа выводит:

Запрос на загрузку класса NonExisted

Fatal error: Class 'NonExisted' not found in t_autoload.php on line 5

Хотя PHP не различает регистр символов в именах процедур и классов (например, myClass и MyClass для него — одно и то же), функции __autoload() передается регистрозависимое имя — в том виде, в котором оно указано в программе. Как говорят, PHP нечувствителен, но уважителен к регистру букв.

Приведенный в листинге 31.8 модуль предоставляет средства для работы с autoloadвозможностями совместно в нескольких библиотеках. Главное его преимущество — возможность регистрировать сразу несколько autoload-функций, которые будут вызываться одна за другой до тех пор, пока класс не загрузится. Это удобно, если сразу несколько библиотек сторонних разработчиков затребуют себе сервис автозагрузки.

Детальное описание возможностей модуля вы найдете в комментариях к коду.

Листинг 31.8. Файл lib/PHP/Autoload.php

568

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

<?php ## Модуль PHP_Autoload.

#Библиотека поддержки множественной автозагрузки классов, призванная

#компенсировать неудобство работы с функцией __autoload() в PHP 5.

#В отличие от невозможности переопределения __autoload(), вы можете

#регистрировать сразу несколько обработчиков.

#Пример использования:

#require_once "PHP/Autoload.php";

#...

#PHP_Autoload::register("MyAutoloadFunction1");

#...

#PHP_Autoload::register("MyAutoloadFunction2");

#Функция-обработчик должна возвращать true в случае, если класс

#(по ее мнению) загружен, и остальные обработчики в списке следует

#пропустить. Вернув false, она сигнализирует, что можно передать

#управление следующему обработчику в списке.

class PHP_Autoload {

#Список функций, вызывающихся при запросе на autoload. static $funcs = array();

#Успешно ли установлен главный обработчик __autoload(). static $ok = true;

#static void register(FunctionName $func)

#Регистрирует новую функцию в списке обработчиков.

#При запросе на autoload вызываются все обработчики по порядку,

#начиная с последнего, до тех пор, пока класс не загрузится.

#Допустимо передавать в параметрах

#массив в одном из следующих форматов:

#— array(className, staticMethodName)

#— array($object, methodName)

#Функция-обработчик должна возвращать true в случае,

#если класс по ее мнению загружен, и false, если можно

#передать управление следующему обработчику в списке.

static function register($func) { self::$funcs[] =& $func;

}

#static void unregister(FunctionName $func)

#Удаляет функцию из списка зарегистрированных обработчиков. static function unregister($func) {

$f =& self::$funcs;

for ($i=0; $i<count($f); $i++) if ($f[$i] === $func) {

array_splice($f, $i, 1); break;

}

}

#void autoload(string $classname)

#Вызывается в момент запроса на autoload (см. ниже). static function autoload($classname) {

static $loading = array();

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

569

#Если класс еще не загружен, а вызывается class_exists(),

#происходит повторный запрос на autoload, и программа зацикливается.

#Чтобы этого избежать, проверяем, чтобы вход в autoload()

#с тем же именем класса не происходил дважды.

if (@$loading[$classname]) return;

#Идет загрузка. Если autoload() будет вызвана рекурсивно,

#сработает предыдущая строчка.

$loading[$classname] = true;

foreach (array_reverse(self::$funcs) as $f) {

#Вот здесь происходит рекурсивный вызов autoload(),

#когда класс еще не загружен.

if (class_exists($classname)) break;

#Вызываем обработчик. Если он вернет false, значит,

#произошла какая-то ошибка, и необходимо запустить

#следующий по списку обработчик.

if (call_user_func($f, $classname)) break;

}

# Загрузка окончена. $loading[$classname] = false;

}

}

#Код, выполняемый при подключении библиотеки.

#Устанавливает собственный ГЛОБАЛЬНЫЙ обработчик

#на __autoload, но только в случае, если такой

#обработчик еще не был установлен где-то еще. if (!function_exists("__autoload")) {

function __autoload($c) { PHP_Autoload::autoload($c); } } else {

PHP_Autoload::$ok = false;

}

?>

Каталог модуля

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

Однако данное соглашение вовсе не означает, что вы должны размещать свои модули прямо в каталог PEAR (/usr/local/lib/php/PHP). Стандартный набор модулей PEAR вообще лучше не трогать. Вам достаточно создать каталог lib/PHP и поместить модуль Autoload.php туда. В этом случае он будет сразу же доступен по команде

require_once "PHP/Autoload.php".

PEAR: преобразование имени класса в имя файла

Модуль, поддерживающий автозагрузку библиотек, создан. Осталось лишь написать код, который по имени класса определяет, в каком файле он располагается. Затем

570

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

следует заставить этот код выполняться при запросе на autoload. При определении имени файла предполагается, что пользователь учитывает соглашения PEAR для именования модулей и классов, которые были описаны выше.

Для универсальности разместим код в отдельной библиотеке. Назовем ее PEAR_NameScheme и поместим в файл lib/PEAR/NameScheme.php.

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

Листинг 31.9. Файл lib/PEAR/NameScheme.php

<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */

// +--------------------------------------------------------------------

+

// | PHP version 4

|

// +--------------------------------------------------------------------

+

// | Copyright (c) 1997-2004 The PHP Group

|

// +--------------------------------------------------------------------

+

// | This source file is subject to version 3.0 of the PHP license,

|

// | that is bundled with this package in the file LICENSE, and is

|

// | available through the world-wide-web at the following url:

|

// | http://www.php.net/license/3_0.txt.

|

// | If you did not receive a copy of the PHP license and are unable to |

// | obtain it through the world-wide-web, please send a note to

|

// | license@php.net so we can mail you a copy immediately.

|

// +--------------------------------------------------------------------

+

// | Authors: Dmitry Koteroff <dmitry SOBAKA koteroff TOCHKA ru>

|

// +--------------------------------------------------------------------

+

//

 

// $Id: standards.xml,v 1.24 2004/05/31 04:25:36 danielc Exp $

 

// {{{ constants

/**

* PHP files extension. */

define("PEAR_NameScheme_ext", "php");

/**

* Delimiter used to split packages inside full classname. */

define("PEAR_NameScheme_bar", "_");

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

571

//}}}

//{{{ PEAR_NameScheme

/**

*Namespace contains functions to translate classnames to filenames (and

*vice versa) according to PEAR naming standards. These functions could

*be used in PHP's __autoload() method.

*

*@category PEAR

*@package PEAR

* @author Dmitry Koteroff <dmitry SOBAKA koteroff TOCHKA ru>

*@version $Id: standards.xml,v 1.24 2004/05/31 04:25:36 danielc Exp $

*@access public

*/

class PEAR_NameScheme

{

var $VERSION = "1.00"; // {{{ name2Path()

/**

*Translate classname to PEAR naming standard's filename which this

*class could be load from. Searching is performed in include_path

*variable.

*

*Function could be used, for example, in PHP5's __autoload() method

*to load classes on demand (see PEAR_NameScheme_Autoload).

*

*Convertion is CASE SENSITIVE!

*Samples:

*

name2Path("PEAR", true)

-> /usr/lib/php/PEAR.php

*

name2Path("pEaR", true)

-> no match (on Unix)

*

name2Path("PEAR")

-> PEAR.php

*

name2Path("XML_Parser")

-> XML/Parser.php

*

 

 

* @param string $classname

case-insensetive name of class to

*

 

translate to filename.

* @param bool $absolutize

if TRUE, function absolutizes pathes

*

 

before returning value.

*

 

 

* @return string

filename corresponding to classname. FALSE if

*

file could not be found.

*

 

*@access public

*@static

*/

function name2Path($classname, $absolutize = false)

{

$fname = str_replace(PEAR_NameScheme_bar, '/', $classname) . '.' . PEAR_NameScheme_ext;

foreach (PEAR_NameScheme::getInc($absolutize) as $libDir) {

572

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

$path = $libDir . '/' . $fname; if (file_exists($path)) {

if (!$absolutize) return $fname; else return $path;

}

}

return false;

}

//}}}

//{{{ path2Name()

/**

*Translate PEAR naming standard's filename to name of class which

*is held in this file. You may specify absolute or relative

*filenames.

*

*If filename is relative, function does not check existance of the

*file. For absolute pathes it returns FALSE if file is not found.

*Samples:

*path2Name("/usr/lib/php/XML/Parser.php") -> XML_Parser

*

name2Path("XML/Parser")

-> XML_Parser

*

name2Path("XML/parser.php")

-> XML_parser

*

 

 

*@param string $path case-sensetive absolute or relative pathname.

*@return string classname corresponding to filename. FALSE

*

if $path is absolute and could not be found.

*

 

*@access public

*@static

*/

function path2Name($path)

{

if (preg_match('{^\w:|^[/\\\\]}s', $path)) {

$path = str_replace("\\", "/", realpath($path)); $inc = PEAR_NameScheme::getInc(true);

$found = false; foreach ($inc as $i) {

if (strpos($path, $i.'/') === 0) { $path = substr($path, strlen($i)+1); $found = true;

break;

}

}

if (!$found) return false;

}

$name = preg_replace("{[/\\\\]}s", PEAR_NameScheme_bar, $path); $name = preg_replace('/\.'.PEAR_NameScheme_ext.'$/s', '', $name); return $name;

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

573

}

//}}}

//{{{ getInc()

/**

*Returns PHP's include_path as array (list), not as string.

*Also function can absolutize pathes found in include_path.

*@param bool $absolutize if TRUE, returned elements are converted

*

to absolute pathnames.

*

 

*@return array list of include_path entries.

*@access public

*@static

*/

function getInc($absolutize = false)

{

$sep = defined("PATH_SEPARATOR")? PATH_SEPARATOR : ((strtoupper(substr(PHP_OS, 0, 3)) == 'WIN')? ";" : ":");

$inc = explode($sep, ini_get("include_path"));

if ($absolutize) $inc = array_map("realpath", $inc); return str_replace("\\", "/", $inc);

}

// }}}

}

// }}}

/*

*Local variables:

*tab-width: 4

*c-basic-offset: 4

*c-hanging-comment-ender-p: nil

*End:

*/

?>

Для того чтобы модуль PEAR_NameScheme работал не только в PHP версии 5, но и в PHP 4, мы не используем специфичные для пятой версии ключевые слова const, public и т. д.

Неприятная особенность require_once

Есть одна особенность PHP при работе в Windows, которую необходимо здесь отметить. Хотя PHP и не учитывает регистр символов в именах классов, зато он учиты-

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

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