
web - tec / PHP 5 для начинающи
.pdf
Файлы и каталоги 323
Функция readdir() возвращает следующий пункт в списке открытого каталога. Этот список включает в себя такие пункты, как . (используется для указания текущего каталога) и .. (родительский по отношению к данному каталог). PHP+интерпретатор хранит внутренний указатель, связанный со следующим пунктом в списке. Этот указа+ тель подобен указателю позиции в файле, в которой должна быть выполнена следую+ щая файловая операция.
Практика Создание листинга каталога
Ниже показано, как создать цикл для получения всех пунктов в заданном каталоге:
<?php //dir_list.php
$default_dir ="x:/home/localhost/www/php5/Ch07";
if(!($dp = opendir($default_dir))) die("Невозможно открыть каталог $default_dir.");
while($file = readdir($dp))
if($file != '.' && $file != '..') echo "$file<br>"; closedir($dp);
?>
На рис. 7.8 показан пример выполнения данного сценария.
Рис. 7.8.
Как это работает
Сначала сценарий определяет дескриптор заданного каталога (в данном случае x:/home/localhost/www/php5/Ch07), а затем начинает цикл, который считывает записи из каталога и (если это не "." и не "..") распечатывает их. Цикл обусловлен

324 Глава 7
возвращаемым функцией readdir() значением, которое станет равным False, когда список записей в каталоге закончится:
while($file = readdir($dp))
if($file != '.' && $file != '..') echo "$file<br>";
Наконец, функция closedir() закрывает каталог.
Возвращаемые имена файлов не сортируются. Чтобы отсортировать их, необхо+ димо создать еще два цикла. В первом цикле имена файлов считываются в массив:
<?php
$default_dir = "x:/home/localhost/www/php5/Ch07";
if(!($dp = opendir($default_dir))) die("Невозможно открыть каталог $default_dir.");
while($file = readdir($dp)) $filenames[] = $file; closedir($dp);
Теперь массив $filenames содержит все записи листинга каталога. PHP автома+ тически индексирует массив.
Во втором цикле вызывается функция sort(), которая упорядочивает элементы массива по возрастанию и отображает все элементы, кроме ссылок на текущий и ро+ дительский каталог:
sort($filenames);
for($i=0; $i < count($filenames); $i++) if($filenames[$i] != '.' && $filenames[$i] != '..')
echo $filenames[$i] . "<br>";
?>
Другие функции для обработки каталогов
Как и в случае файлов, PHP обеспечивает множество способов для манипуляции с каталогами, включая следующие функции:
rewinddir()
chdir()
rmdir()
mkdir()
dirname()
(dir)
Функция rewinddir() переустанавливает внутренний указатель в PHP, когда не+ обходимо вернуться к первой записи в заданном каталоге. Эта функция является ана+ логом функции rewind() для файлов и требует указания соответствующего дескрип+ тора каталога. Остальные функции обработки каталогов вместо дескриптора непосредственно принимают строку, содержащую путь к каталогу.
Функция chdir() изменяет текущий каталог:
if(chdir("x:/home/localhost/www/php5/Ch07"))
echo "Текущий каталог: x:/home/localhost/www/php5/Ch07.";
else
echo "Невозможно перейти в каталог x:/home/localhost/www/php5/Ch07.";
Функция rmdir() удаляет заданный каталог. Каталог должен быть пустым, а кроме этого, сценарий должен иметь необходимые права для удаления данного каталога. Например:
rmdir("/tmp/rubbish/");

Файлы и каталоги 325
Функция mkdir() создает каталог, указанный в качестве ее первого аргумента. Можно также задать режим доступа к каталогу в виде трехзначного восьмеричного чис+ ла. Следующий код сначала проверяет, существует ли каталог x:/home/localhost/ www/php5/Ch07/test. Если это так, то сценарий удаляет этот каталог, а затем созда+ ет новый каталог с тем же именем и предоставляет всем пользователям все права дос+ тупа к данному каталогу (права доступа применимы только на Linux+системах):
$default_dir = "x:/home/localhost/www/php5/Ch07"; if(file_exists($default_dir)) rmdir($default_dir); mkdir($default_dir, 0777);
Функция dirname() возвращает путь к каталогу для заданного имени файла. На+ пример:
$filepath = "x:/home/localhost/www/php5/Ch07/index.html"; $dirname = dirname($filepath);
$filename = basename($filepath);
В результате в переменной $dirname теперь содержится строка "x:/home/ localhost/www/php5/Ch07", а в переменной $filename строка "index.html".
PHP предоставляет псевдо+объектно+ориентированный механизм для работы с ка+ талогами: класс dir. Чтобы использовать этот механизм, необходимо сначала создать объект путем вызова конструктора dir() и передачи ему имени каталога, с которым необходимо работать:
$dir = dir("x:/home/localhost/www/php5/Ch07");
Объект dir обладает двумя свойствами: handle и path. Они ссылаются на деск+ риптор каталога и путь соответственно:
echo $dir->handle; # распечатывает дескриптор каталога
echo $dir->path; # распечатывает строку "x:/home/localhost/www/php5/Ch07"
Свойство handle можно использовать с другими функциями обработки каталогов, такими как readdir(), rewinddir() и closedir().
Объект dir поддерживает три метода: read(), rewind() и close(), которые функ+ ционально эквивалентны PHP+функциям readdir(), rewinddir() и closedir() соответственно. Можно переписать сценарий создания листинга каталога, используя в нем описанный объект:
<?php
$default_dir = "x:/home/localhost/www/php5/Ch07"; $dir = dir($default_dir);
while($file = $dir->read()) if($file != '.' && $file != '..') echo $file . "<br>";
$dir->close(); ?>
Обход дерева каталогов
Как уже отмечалось в главе 6, рекурсия особенно полезна, когда сценарий должен выполнять повторяющиеся операции и обрабатывать в цикле определенное множе+ ство данных. Очень хорошим примером такого сценария является сценарий, выпол+ няющий обход дерева каталогов. В каталоге могут содержаться подкаталоги, а также файлы. Чтобы создать сценарий, который выводит имена всех файлов и подкаталогов указанного каталога, необходимо заставить сценарий считывать записи в текущем ка+ талоге. Если запись представляет собой имя файла, то ее следует отобразить. Если за+ пись является именем подкаталога, то необходимо отобразить его имя, а затем войти в него и снова вернуться к подпрограмме считывания записей в текущем каталоге.

326 Глава 7
Очевидно, что второй этап в случае необходимости повторяет весь процесс. Ре+ курсия продолжается до тех пор, пока не останется подкаталогов для обхода. Рас+ смотрим пример такого сценария:
<?php //nav_dir.php
$default_dir = "/home/james";
function traverse_dir($dir) {
echo "Обход каталога $dir....<br>";
chdir($dir);
if(!($dp = opendir($dir))) die("Невозможно открыть каталог $dir."); while($file = readdir($dp)) {
if(is_dir($file)) {
if($file != '.' && $file != '..') { echo "/$file<br>"; traverse_dir("$dir/$file"); chdir($dir);
}
}
else echo "$file<BR>";
}
closedir($dp);
}
traverse_dir($default_dir); ?>
Функция traverse_dir() основывается на идее рекурсии и обходит все дерево каталогов ниже заданного каталога. Во+первых, функция выводит каталог, который она в настоящий момент просматривает. Затем вызов chdir() позволяет убедиться, что строка $dir соответствует пути к текущему каталогу:
function traverse_dir($dir) {
echo "Обход каталога $dir....<br>";
chdir($dir);
if(!($dp = opendir($dir))) die("Невозможно открыть каталог $dir."); while($file = readdir($dp)) {
Рекурсивный вызов происходит, когда следующий пункт в листинге является под+ каталогом. Из возвращаемого списка исключаются файлы . и .. ++++++ в данном случае это крайне важно, иначе сценарий вошел бы в бесконечный цикл.
if(is_dir($file)) {
if($file != '.' && $file != '..') { echo "/$file<br>";
Функция traverse_dir() вызывает сама себя, для того чтобы перемещаться вглубь по иерархии каталогов, и делает первоначальный каталог текущим:
traverse_dir("$dir/$file"); chdir($dir);
}
}
else echo "$file<BR>";
}
closedir($dp);
}
traverse_dir($default_dir); ?>
Мощь рекурсии, продемонстрированная в данном простом сценарии, позволяет создать собственную версию Linux+команды find.

Файлы и каталоги 327
Сценарий для навигации по каталогам
Теперь можно перейти к созданию довольно мощного сценария для навигации по каталогам (назовем его ‘‘навигатором’’), с помощью которого можно сканировать со+ держимое существующих каталогов и создавать новые. Для этого необходимо создать файл common_php5.inc.php с несколькими разделами, которые помогут приложе+ нию выполнять его работу.
Сначала следует установить каталог по умолчанию, имя файла и размер тексто+ вого поля:
<?php
//определение каталога по умолчанию $default_dir = "./docs";
//определение стандартного имени для новых файлов $default_filename = "new.txt";
//определение размера текстового поля $edit_form_cols = 80;
$edit_form_rows = 25;
Определение расширений файлов, которые можно обрабатывать:
//определение расширений файлов для обработки
$text_file_array = array( "txt", "htm", "html", "php", "inc", "dat" ); $image_file_array = array("gif", "jpeg", "jpg", "png");
Три описанные ниже функции создают заголовок, нижний колонтитул и необхо+ димые сообщения об ошибках:
function html_header() { ?>
<html>
<head><title>Добро пожаловать в текстовый редактор на Web-странице</title></head> <body>
<?php
}
function html_footer() { ?>
</body>
</html>
<?php
}
function error_message($msg) { html_header();
echo "<script>alert(\"$msg\"); history.go(-1)</script>"; html_footer();
exit;
}
Обработка информации о датах:
function date_str($timestamp) { $date_str = getdate($timestamp); $year = $date_str["year"];
$mon = $date_str["mon"]; $mday = $date_str["mday"]; $hours = $date_str["hours"];
$minutes = $date_str["minutes"]; $seconds = $date_str["seconds"];
return "$hours:$minutes:$seconds $mday/$mon/$year";
}

328 Глава 7
Следующая функция возвращает информацию о файле, а также проверяет расши+ рение файла:
function file_info($file) { global $text_file_array; $file_info_array["filesize"] =
number_format(filesize($file)) . " bytes."; $file_info_array["filectime"] = date_str(filectime($file));
$file_info_array["filemtime"] = date_str(filemtime($file)); if(!isset($_ENV[WINDIR])) {
$file_info_array["fileatime"] = date_str(fileatime($file)); $file_info_array["filegroup"] = filegroup($file); $file_info_array["fileowner"] = fileowner($file);
} else {
$file_info_array["fileatime"] = "недоступно"; $file_info_array["filegroup"] = "недоступно"; $file_info_array["fileowner"] = "недоступно";
}
$extension = array_pop(explode(".", $file));
if (in_array($extension, $text_file_array)) { $file_info_array["filetype"] = "текстовый";
} else {
$file_info_array["filetype"] = "бинарный";
}
return $file_info_array;
}
Сохраните весь код в файле common_php5.inc.php.
В начале сценария+навигатора необходимо подключить только что сохраненный файл:
<?php
//navigator.php
include "common_php5.inc.php";
Затем необходимо определить несколько функций. Во+первых, функцию mkdir_form(), которая отображает форму для создания нового каталога:
function mkdir_form() { global $dir;
?>
<center>
<form method="POST" action="<?php echo "$_SERVER[PHP_SELF]?action=make_dir&dir=$dir"; ?>"> <input type="hidden" name="action" value="make_dir"> <input type="hidden" name="dir" value="<? echo $dir ?>">
<?php
echo "<strong>$dir</strong>" ?>
<br>
<input type="text" name="new_dir" size="10">
<input type="submit" value="Создать каталог" name="Submit">
</form>
</center>
<?php
}
Функция make_dir() создает заданный каталог:
function make_dir() { global $dir;
if(!@mkdir("$dir/$_POST[new_dir]", 0700)) { error_message("Невозможно создать каталог $dir/$_POST[new_dir].");

Файлы и каталоги 329
}
html_header(); dir_page(); html_footer();
}
Затем функция display() распечатывает в новом окне содержимое заданного фай+ ла. Функция сравнивает расширение файла с элементами массивов $text_file_array и $image_file_array, определяя таким образом тип файла ++++++ текстовый, графиче+ ский или бинарный:
function display() {
global $text_file_array, $image_file_array; $extension = array_pop(explode(".", $_GET[filename]));
Затем расширение файла сравнивается с типами, которые определены в массивах $text_file_array и $image_file_array. Сценарий отказывается отображать двоичные файлы:
if(in_array($extension, $text_file_array)) { readfile("$_GET[dir]/$_GET[filename]");
}
else if(in_array($extension, $image_file_array)) {
echo "<img src=\"$_GET[dir]/$_GET[filename]\">";
}
else echo "Невозможно отобразить. Файл $_GET[dir]/$_GET[filename]
не является текстовым файлом или корректно сформированным графическим файлом.";
}
dir_page() ++++++ главная функция, она сканирует иерархию каталогов и создает листинги каталогов, отображая элементы с завершающими символами косой черты. Эта функция представляет собой ядро сценария:
function dir_page() {
global $dir, $default_dir, $default_filename;
if($dir == ") {
$dir = $default_dir;
}
if (isset($_GET['dir'])) { $dir = $_GET['dir'];
}
$dp = opendir($dir);
?>
<table border="0" width="100%" cellspacing="0" cellpadding="0"> <?php
Создается два цикла для сортировки записей в заданном каталоге: цикл while для чтения всех записей в текущем рабочем каталоге и цикл for для отображения записей после сортировки:
while($file = readdir($dp)) $filenames[] = $file; sort($filenames);
for($i = 0; $i < count($filenames); $i++)
{
$file = $filenames[$i];
Если следующая запись в каталоге является точкой "." (т.е. указывает на текущий каталог), то функция игнорирует данную запись и начинает следующую итерацию

330 Глава 7
цикла. Кроме того, если текущий рабочий каталог является каталогом по умолчанию, то игнорируются как записи ".", так и записи "..":
if($dir == $default_dir && ($file == "." || $file == "..")) continue;
if(is_dir("$dir/$file") && $file == ".") continue;
if(is_dir("$dir/$file")) {
Если следующей является запись ".." (указывающая на родительский каталог), то функция отсекает имя текущего каталога от значения переменной $dir, чтобы соз+ дать гиперссылку на родительский каталог:
if($file == ".."){
В переменную $current_dir с помощью функции basename() записывается крайняя правая (после косой черты) подстрока имени каталога:
$current_dir = basename($dir);
Например, если переменная $dir содержит значение "/home/apache/htdocs/ images", то переменной $current_dir присваивается значение "images".
При создании гиперссылки функция dir_page() с помощью ereg_replace() удаляет вхождения этого значения в переменную $dir:
$parent_dir = ereg_replace("/$current_dir$","",$dir);
После этого переменная $parent_dir содержит значение "/home/apache/ htdocs", так как образец /$current_dir$ совпадает с подстрокой "/images" в конце строки.
echo "<tr><td width=\"100%\" nowrap>
<a href=\"$_SERVER[PHP_SELF]?dir=$parent_dir\">$file/ </a></td></tr>\n";
}
Если следующая запись является подкаталогом, то функция создает гиперссылку на этот подкаталог:
else echo "<tr><td width=\"100%\" nowrap>
<a href=\"$_SERVER[PHP_SELF]?dir=$dir/$file\"> $file/</a></td></tr>\n";
}
Если следующая запись является файлом, то функция создает гиперссылку для отображения содержимого данного файла в новом окне браузера:
else echo "<tr><td width=\"100%\" nowrap>
<a href=\"$_SERVER[PHP_SELF]?action=display&dir=$dir&filename=$file\" target=\"_blank\">$file</a></td></tr>\n";
}
?>
</table>
<?php mkdir_form();
}
Когда сценарий запускается, сначала он проверяет, не находится ли заданный ка+ талог выше каталога по умолчанию (по причинам безопасности пользователь не дол+ жен иметь доступа к файлам, которые находятся выше его каталога по умолчанию). Функция сравнения с образцом ereg() возвращает False, если переменная $dir не содержит значения $default_dir, и если это так, то переменной $dir присваивает+ ся значение $default_dir:

Файлы и каталоги 331
if(empty($dir) || !ereg($default_dir, $dir)) { $dir = $default_dir;
}
Наконец, вызываются функции, соответствующие значению переменной $action. По умолчанию вызывается функция dir_page():
if (!empty($_POST['action'])) { $action = $_POST['action'];
}
if (!empty($_GET['action'])) { $action = $_GET['action'];
}
switch ($action) { case "make_dir":
make_dir(); break;
case "display": display(); break;
default: html_header(); dir_page(); html_footer(); break;
}
?>
Примерный результат работы сценария показан на рис. 7.9. В данном случае сце+ нарий распечатал имена двух файлов и одного нового каталога в текущем каталоге.
На рис. 7.10 показано содержимое файла new01.txt.
Рис. 7.9.

332 Глава 7
Рис. 7.10.
Создание текстового редактора
Зная базовые возможности PHP по обработке файлов и каталогов, можно присту+ пить к созданию простого текстового редактора. Описанный ниже сценарий в качестве аргумента будет принимать имя файла, редактировать, а затем сохранять данный файл. Если сценарий не получает имя существующего файла, то он создает новый файл.
Во+первых, следует продумать, как в итоге будет выглядеть сценарий. Эта картина
ввоображении разработчика в конечном итоге трансформируется в пользователь+ ский интерфейс. Так как это текстовый редактор, необходимо определить место,
вкотором сценарий сможет сохранять текст и давать пользователю возможность ра+ ботать с этим текстом. Нет необходимости беспокоиться о реализации таких специ+ фических функций редактирования, как ввод и удаление символов, перемещение кур+ сора и т.д., поскольку HTML+дескриптор <textarea> способен поддерживать все необходимые функции редактирования. Все, что требуется сделать, это создать фор+ му, на которой будет отображаться область ввода с полосой прокрутки, текстовое по+ ле для ввода имени файла и кнопка для сохранения файла.
Затем необходимо решить, как уведомлять пользователя об ошибках и как полу+ чать от него подтверждение на выполнение возможных деструктивных действий, та+ ких как перезапись существующего файла. Добиться этой цели позволяют весьма про+ стые JavaScript+сценарии. В HTML+страницу можно поместить фрагменты JavaScript+ кода, заключив их в следующую комбинацию тегов:
<script> JavaScript-код </script>