web - tec / PHP 5 для начинающи
.pdf562 Глава 14
Поскольку все, что в данном случае требуется сделать, это оценить пригодность ком+ понента, в настоящий момент не обязательно включать большое количество элементов в XML+файл ++++++ приведенный здесь код вполне достаточен для целей тестирования.
Следует отметить ссылки на несколько GIF+файлов. Класс HTML_TreeMenu для создания знаков ‘‘плюс’’, ‘‘минус’’ и т.д. использует еще несколько таких файлов, ко+ торые явно в XML+файле не упоминаются. Эти файлы включены в дистрибутив, и их можно скопировать в каталог для изображений, расположенный ниже корневого ка+ талога, в котором сохранен следующий код.
Теперь следует сформировать навигационную страницу. Создайте файл treemenutest.php и введите в него следующий код:
<HTML>
<HEAD>
<SCRIPT LANGUAGE="Javascript" SRC="TreeMenu.js"></SCRIPT> </HEAD>
<BODY>
<?php require_once('HTML/TreeMenu.php'); require_once('xmlhtmltree.phpm');
$objXMLTree = new XMLHTMLTree("treemenutest.xml"); $objXMLTree->GenerateHandOffs(); $objXMLTree->ParseXML();
$objTreeMenu = $objXMLTree->GetTreeHandoff(); ?> <H1>Тестирование древовидного меню</H1>
<HR>
<?php $objTreeMenu->printMenu();
?>
</BODY>
</HTML>
Здесь кроме PEAR+пакета HTML_TreeMenu подключается новый пакет, xmlhtmltree.phpm. Это упомянутый ранее вспомогательный класс; он конверти+ рует XML+код в PHP+операторы, позволяя, таким образом, использовать в классе
HTML_TreeMenu XML+код.
Ниже представлен код вспомогательного компонента. Сохраните этот код в файле xmlhtmltree.phpm в том же каталоге, что и treemenutest.php. Пока разбираться в его работе не стоит, это можно сделать позднее.
<?
class XMLHTMLTree { private $xml_content; private $hanTreeHandoff; private $objHTMLTreeMenu;
function __construct($strXMLSourceFile = "", $strXMLSource = "") { if ($strXMLSourceFile) {
$this->xml_content = implode(", @file($strXMLSourceFile)); } else {
$this->xml_content = $strXMLSource; };
$this->objHTMLTreeMenu = new HTML_TreeMenu(); $this->depth = 0;
}
function ParseXML() {
$strXML = $this->xml_content;
$objDOM = simplexml_load_string($strXML); foreach ($objDOM->node as $thisNode) {
PEAR 563
$this->_ParseNode($thisNode); };
}
private function_ParseNode(&$objNode, & $arPoint = "") { // Добавляем узел
if (!$arPoint) {
$objNewNode = new HTML_TreeNode(array('text' => $objNode['text'], 'link' => $objNode['link'], 'icon' => 'folder.gif', 'expandedIcon' =>
'folder-expanded.gif', 'expanded' => false)); $newArPoint = &$objNewNode;
}else {
$newArPoint = &$arPoint->addItem(new HTML_TreeNode(array('text' =>
$objNode['text'], 'link' => $objNode['link'], 'icon' => 'folder.gif', 'expandedIcon' => 'folder-expanded.gif')));
}; // Проверяем, есть ли в оригинале дочерние узлы
foreach ($objNode->node as $thisNode) {
if ($thisNode['text']) { $this->_ParseNode($thisNode, $newArPoint);
};
};
if (!empty($objNewNode)) { $this->objHTMLTreeMenu->addItem($objNewNode); };
}
function GenerateHandOffs() {
// Создаем класс представления
$this->hanTreeHandoff = &new HTML_TreeMenu_DHTML($this->objHTMLTreeMenu, array('defaultClass' => 'treeMenuDefault'));
$this->hanTreeHandoff->images = 'images'; }
function GetTreeHandoff() { return($this->hanTreeHandoff);
}
}
?>
Единственное что в этом коде можно изменить ++++++ расположение графических файлов для визуализации сворачивающихся меню. Если желательно (или требуется) сохранять эти файлы в другом каталоге, то следует изменить строку:
$this->hanTreeHandoff->images = 'images';
и указать в ней необходимый путь к изображениям. Изображения, показанные на рис. 14.1 ниже, включены в дистрибутив пакета HTML_TreeMenu и находятся в подка+ талоге /usr/local/lib/php/data/HTML_TreeMenu (Unix) или C:\PHP\PEAR\PEAR\ DATA\HTML_TreeMenu (Windows).
Необходимо пройти последний этап, прежде чем класс можно будет посмотреть в действии. В дистрибутив включен файл TreeMenu.js, содержащий библиотеку JavaScript+функций. Она располагается в том каталоге, который доступен PHP, но не+ доступен Web+браузеру (JavaScript+файлы просто передаются Web+браузеру, а PHP+файлы предварительно обрабатываются). Чтобы обойти это препятствие, можно скопиро+ вать JavaScript+файл из каталога /usr/local/lib/php/data/HTML_TreeMenu (или его Windows+эквивалента) в тот каталог, в котором находится только что созданный PHP+ файл treemenutest.php.
PEAR 565
Есть две причины использования require_once вместо других PHP+функций под+ ключения файлов:
если пакет не будет найден, то PHP сгенерирует неисправимое исключение и остановит выполнение сценария;
если пакет уже был подключен другим блоком кода выполняемого сценария, то во второй раз он не будет подключаться, что значительно повышает произво+ дительность.
Работа с классами
После подключения пакета необходимо создать экземпляры классов. Сначала создается объект класса HTML_TreeMenu:
$objXMLTree = new XMLHTMLTree("treemenutest.xml");
Если вместо require использовать include+функцию для подключения пакета, то
вслучае отсутствия соответствующего пакета PHP не остановит выполнение сцена+ рия до тех пор, пока не достигнет этой строки. В данном примере, где пакет исполь+ зуется сразу после подключения, это не большая проблема. Однако в реальных при+ ложениях, где между подключением и использованием пакета создаются записи в базе данных, это действительно очень важно. Примером такого приложения может быть Web+магазин, в котором в результате одного заказа создается несколько записей в базе данных. Если подключенный PEAR+класс не используется, например, в течение вре+ мени, необходимого для создания половины записей в базе данных, то после сбоя приложения будет создана только половина записей. Это не только неаккуратно
втехническом смысле, но и может создать для использующей сайт компании реальные административные проблемы при определении того, насколько полно был оформлен заказ, оплатил ли клиент товар и т.д. На практике для предотвращения таких проблем можно использовать транзакции баз данных, но гораздо лучше гарантировать то, что
вслучае отсутствия необходимого компонента на этапе подключения генерируется неисправимая ошибка и остальная часть сценария не выполняется понапрасну.
Следует подчеркнуть, что здесь PEAR+класс не используется непосредственно ++++++
создается экземпляр вспомогательного класса, которому сообщается путь к входному XML+документу.
Далее, вспомогательный класс должен создать необходимый DHTML
HTML_TreeMenu+объект, т.е. ++++++ ‘‘переключатель’’. Этот объект имеет сложную связь с объектом класса HTML_TreeMenu, поэтому, когда в объект дерева добавляется узел, этот узел также добавляется в переключатель.
$objXMLTree->GenerateHandOffs();
Внутренняя работа этого метода будет рассмотрена далее при подробном изуче+ нии вспомогательного класса.
Затем вспомогательный класс фактически выполняет разбор XML+кода во входном документе. Для этого используется встроенное расширение SimpleXML, удобно пре+ образующее XML+код в иерархию объектов, которую впоследствии можно будет легко обрабатывать. Хотя эту иерархию обрабатывает вспомогательный класс, для добавле+ ния узлов (по сути пунктов в списке) его собственному объекту класса HTML_TreeMenu он использует методы, предоставленные исходным PEAR+классом (HTML_TreeMenu).
Теперь вспомогательный класс имеет объект, готовый к созданию динамического HTML+кода, отображающего привлекательное навигационное меню. Можно было бы
PEAR 567
Конструктор принимает один из двух возможных параметров: либо путь к XML+ файлу, либо строку, содержащую XML+код. Используя оператор = "", можно сооб+ щить PHP о том, что оба параметра являются необязательными. Если указывается ис+ ходный XML+файл, сценарий извлекает его содержимое. В любом случае исходный XML+код сохраняется впоследствии в переменной xml_content и подготавливается, таким образом, к разбору.
function __construct($strXMLSourceFile = "", $strXMLSource = "") { if ($strXMLSourceFile) {
$this->xml_content = implode(", @file($strXMLSourceFile));
}else {
$this->xml_content = $strXMLSource;
};
$this->objHTMLTreeMenu = new HTML_TreeMenu();
}
Кроме того, в конструкторе создается объект класса HTML_TreeMenu, который со+ храняется как переменная экземпляра класса (objHTMLTreeMenu).
Метод GenerateHandOffs создает отображаемый HTML_TreeMenu+объект (DHTML+ код), к которому затем можно получить доступ с помощью метода GetTreeHandoff.
function GenerateHandOffs() { // Создаем класс отображения
$this->hanTreeHandoff = &new HTML_TreeMenu_DHTML($this->objHTMLTreeMenu, array('defaultClass' => 'treeMenuDefault'));
$this->hanTreeHandoff->images = 'images';
}
Синтаксис для отображения DHTML+кода объекта меню взят непосредственно из документации HTML_TreeMenu. Чтобы программа могла находить изображения, при+ меняемые для генерации навигационного меню, здесь также присваивается значение свойству images.
Метод ParseXML начинает разбор XML+содержимого вспомогательного класса.
function ParseXML() {
$strXML = $this->xml_content;
$objDOM = simplexml_load_string($strXML); foreach ($objDOM->node as $thisNode) {
$this->_ParseNode($thisNode); };
}
Сначала этому методу передается XML+код. Это не обязательно, но ясность кода такой прием повышает.
Затем используется SimpleXML+метод simplexml_load_string для загрузки XML+структуры и ее представления в виде псевдообъектной структуры, которая со+ храняется в переменной $objDom.
Далее вызывается метод foreach для обхода семейства узловых ‘‘объектов’’, кото+ рые доступны благодаря SimpleXML. Ссылка на каждый из этих объектов передается ча+ стному методу _ParseNode, который недоступен для кода вне вспомогательного класса.
private function _ParseNode($objNode, & $arPoint = "") { // Добавляем узел
if (!$arPoint) {
$objNewNode = new HTML_TreeNode(array('text' => $objNode['text'], 'link' => $objNode['link'], 'icon' => 'folder.gif', 'expandedIcon' => 'folder-expanded.gif', 'expanded' => false));
$newArPoint = &$objNewNode;
}else {
$newArPoint = &$arPoint->addItem(new HTML_TreeNode(array('text' =>
568 Глава 14
$objNode['text'], 'link' => $objNode['link'], 'icon' => 'folder.gif', 'expandedIcon' => 'folder-expanded.gif')));
}; // Проверяем, есть ли в оригинале дочерние узлы
foreach ($objNode->node as $thisNode) { if ($thisNode['text']) {
$this->_ParseNode($thisNode, $newArPoint); };
};
if ($objNewNode) { $this->objHTMLTreeMenu->addItem($objNewNode); };
}
Метод _ParseNode рекурсивно вызывает сам себя. Рекурсия лучше всего описыва+ ется как средство определенного метода выполнять выборочные вызовы самого себя с целью обхода структуры данных с неизвестной степенью вложенности. Например, если есть двумерный массив чисел, то чтобы обойти его, понадобится два for+ цикла ++++++ вероятно, в одном из них будет использоваться переменная счетчика x, а в дру+ гом y. Аналогично, если есть трехмерный массив чисел, придется использовать три вложенных цикла со счетчиками x, y и z. Однако в этих двух примерах число измерений постоянно и известно. В XML структура может иметь любое число дочерних узлов, каж+ дый их которых в свою очередь тоже может иметь любое количество дочерних узлов и т.д. Использование рекурсии гарантирует, что каждый отдельный узел обрабатывает+ ся новым экземпляром метода каждый раз, когда находится новое множество дочерних узлов; как только множество дочерних узлов обработано, родительский экземпляр про+ должает работу с той точки, в которой прекратилась обработка дочерних узлов.
Эта методика весьма четко и просто работает в примере с генерацией меню. Для каждого узла в XML+коде в HTML_TreeMenu+объекте с помощью методов, описанных в документации к данному пакету, создается соответствующий узел. Затем выполняет+ ся проверка существования в генерируемом в текущий момент времени узле дочерних узлов. Если дочерние узлы есть, каждый из них в свою очередь передается новому эк+ земпляру метода _ParseNode, выполняющему точно такой же процесс с дочерним уз+ лом. Ссылка на родительский узел (если он есть) также передается методу, поэтому метод знает, к какому узлу в создаваемой иерархии следует добавить дочерний объект.
Наконец, рассмотрим метод GetTreeHandoff().
function GetTreeHandoff() { return($this->hanTreeHandoff);
}
Все очень просто. Этот метод возвращает ссылку на DHTML+код, сгенерированный HTML_TreeMenu, доступный благодаря использованному выше методу GenerateHandOffs(). Структура, возвращаемая этим методом, может содержать собственный ме+ тод printMenu, который при необходимости вызывается для генерации HTML+ и JavaScript+кода, делающего меню видимым в окне браузера.
Использование вспомогательных классов иногда бывает неизбежным. Если разра+ ботчик многократно использует собственноручно написанный класс, то он может пе+ редать этот класс в репозиторий PEAR, указав в качестве зависимости исходный PEAR+класс, на котором базируется его собственная разработка. О том, как это дела+ ется, подробно рассказано на Web+сайте проекта PEAR.
PEAR 569
Использование PEAR-пакетов
Итак, вы уже знаете, как найти необходимый PEAR+пакет для использования в разраба+ тываемом проекте, как установить этот пакет и все пакеты, от которых он зависит, а также как интегрировать PEAR+пакеты в собственный код. Теперь можно применить получен+ ные знания на практике и создать работоспособное приложение, используя один PEAR+ компонент и несколько более распространенных встроенных в PHP подпрограмм.
Практика Создание приложения с использованием одного PEAR-компонента
Разрабатываемое в этом примере приложение предназначено для использования на персональном Web+сайте, демонстрирующем посетителям перечень недавно про+ слушанных музыкальных композиций. Приложение работает, проверяя коллекцию MP3+файлов. В настоящее время коллекционирование MP3+музыки весьма популярно, во многих таких коллекциях содержатся тысячи песен. MP3 ++++++ самый популярный формат кодирования музыки для прослушивания на компьютере.
Это PHP+приложение предназначено для работы на настольном компьютере, по+ скольку для его работы требуется доступ к хранилищу ежедневно прослушиваемых MP3+файлов. В силу многих причин такое приложение на практике может оказаться бесполезным, но оно вполне удовлетворительно подходит на роль рабочего примера автономного приложения.
Предположим, пользователь хранит MP3+файлы в каталоге C:\MP3. Если это не так (или приложение должно работать на Unix+системе), то можно изменить в коде значение переменной $MY_MP3_DIR.
PEAR-пакет: MP3_ID
Пакет MP3_ID используется для считывания из MP3+файла информации, которая на+ зывается IDv3+тег. В IDv3+теге содержится информация о жанре данной композиции, дате, стране, где она была написана, а также многие другие сведения. IDv3+тег присутст+ вует в каждом MP3+файле, но может быть пустым ++++++ это в первую очередь зависит от программы, которая использовалась для создания MP3+файла. Для целей данного про+ екта предположим, что все MP3+файлы имеют правильно сформированные IDv3+теги.
Более подробную информацию о пакете MP3_ID можно получить на странице http://pear.php.net/package/MP3_ID.
Пакет предоставляет методы как для чтения, так и для записи IDv3+тегов, но в рас+ сматриваемом здесь примере используются только методы чтения, позволяющие по+ лучить определенную информацию о MP3+файлах в коллекции, а точнее, имя испол+ нителя и название песни.
Пакет устанавливается как обычно:
root@genesis:~# pear install MP3_ID downloading MP3_Id-1.0.tgz ...
...done: 7,517 bytes install ok: MP3_Id 1.0 root@genesis:~#
Приложение
Рассматриваемое приложение создается в виде одной PHP+страницы, на которой генерируется перечень из пяти верхних MP3+файлов в каталоге, отсортированном по времени последнего доступа к файлу (что теоретически должно совпадать с порядком
570 Глава 14
их проигрывания). Если в каталоге найдено больше пяти файлов, то отображаются только первые пять из них.
Создайте файл с именем mp3id.php и поместите его в каталог, доступный Web+ серверу. Введите в файл следующий код. Не забудьте при этом изменить значение пере+ менной $MY_MP3_DIR так, чтобы оно указывало на каталог, в котором действительно хра+ нятся MP3+файлы (убедитесь, что в этом каталоге есть несколько MP3+файлов). Не беспо+ койтесь, если не все понятно ++++++ позднее работа сценария будет рассмотрена подробно.
<?php
require_once 'MP3/Id.php';
static $MY_MP3_DIR = "/home/ed/mp3"; # Измените это значение! $objDir = dir($MY_MP3_DIR);
$intNumFiles = 0;
$arFileTimeHash = Array();
# Пройти по всем найденным файлам в списке
while (false !== ($strEntry = $objDir->read())) {
#Проверить, является ли текущий файл MP3-файлом,
#а не каталогом или файлом другого типа!
if (eregi("\.mp3$", $strEntry)) {
$arFileTimeHash[$strEntry] = fileatime($MY_MP3_DIR . "/" . $strEntry); $intNumFiles++;
};
};
#Отсортировать список файлов по дате последнего доступа и
#поместить список в обычный массив для последующего отображения
arsort($arFileTimeHash); $arFileList = Array(); $intThisArrayIndex = 0;
foreach ($arFileTimeHash as $strFilename => $intAccessTime){ $arFileList[$intThisArrayIndex]["FILENAME"] = $strFilename; $arFileList[$intThisArrayIndex]["ACCESSED"] = $intAccessTime; $intThisArrayIndex ++;
};
# Если найдено более 5 MP3-файлов, показать только первые 5 if ($intNumFiles > 5) {
$intNumFiles = 5;
};
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html>
<head>
<title>Моя MP3-коллекция</title> </head>
<body>
<H1>Моя MP3-коллекция </H1>
<HR>
Ниже перечислены пять верхних песен, которые я недавно прослушал
<BR><BR>
<table border="1" cellpadding="3" cellspacing="3">
<tr> <td>Место</td>
<td>Исполнитель</td> <td>Название композиции</td>
<td>Дата последнего прослушивания:</td> </tr>
<?php
$objMP3ID = new MP3_Id();
for ($i = 0; $i<=($intNumFiles)-1; $i++) {
?>