
web - tec / PHP 5 для начинающи
.pdf
Файлы и каталоги 303
и возвращает только один символ из связанного с данным дескриптором файла; если функция достигает конца файла, то она возвращает False. По существу, она работает так же, как и fread() ++++++ вызов:
$one_char = fgetc($fp)
эквивалентен вызову:
$one_char = fread($fp,1)
Модифицируем первоначальный счетчик с использованием в нем функции fgetc(). Для этого можно использовать такой код:
<?php //hit_counter03.php
$counter_file = "./count.dat";
if(!($fp = fopen($counter_file, "r"))) die ("Невозможно открыть файл $counter_file.");
do {
$one_char = fgetc($fp); $counter .= $one_char;
} while($one_char); $counter = (int) $counter; fclose($fp);
Чтобы считать все содержимое файла данных, используется цикл while, потому что функция fgetc() считывает из файла только один символ за раз. Кроме того, не+ обходимо знать, когда прекращать чтение, поэтому последний считанный символ за+ писывается в холостую переменную, и когда она будет равна False, цикл чтения можно прекратить. Однако в таком коде есть один дефект. Как только из файла будет считано значение "0" или " ", условие цикла не выполнится и цикл чтения прекра+ тится. Если планируется, что значение счетчика посещений будет превышать 9, то данная особенность может оказаться серьезной проблемой.
Существует другой способ определить момент завершения цикла чтения ++++++ ис+ пользование функции feof().
Функция feof() служит одной простой цели: она возвращает True, когда достига+ ет конца заданного файла (или если возникает ошибка), и False ++++++ в противном слу+ чае. Функция принимает только один аргумент ++++++ дескриптор файла:
feof($fp)
Поэтому в качестве условия цикла можно использовать логическое отрицание и проверить, таким образом, не достиг ли указатель конца данного файла:
<?php //hit_counter04.php
$counter_file = "./count.dat";
if(!($fp = fopen($counter_file, "r"))) die ("Невозможно открыть файл $counter_file."); while(!feof($fp)) $counter .= fgetc($fp);
$counter = (int) $counter; fclose($fp);
$counter++;
echo "Вы – посетитель № $counter.";
$fp = fopen($counter_file, "w"); fwrite($fp, $counter); fclose($fp);
?>
Функция feof() сообщает оператору while, когда необходимо завершить цикл (когда функция fgetc() в процессе посимвольного считывания достигает конца

304 Глава 7
заданного файла). Затем значение переменной $counter с помощью оператора (int)приводится к целому типу.
Чтение больших файлов с помощью функции fgetc() может отнимать очень много времени, потому что данная функция считывает только один символ за раз. Считывать множество символов позволяет функция fgets(). Она принимает два ар+ гумента, $fp и $length, и возвращает прочитанную из файла, заданного дескрипто+ ром $fp, строку, длина которой не превышает значения $length ($length ++++1). Функция прекращает чтение по одной из трех перечисленных ниже причин:
прочитано заданное количество байтов;
достигнут конец строки;
достигнут конец файла.
Различие между fgets() и fread() заключается в том, что fgets() прекращает чтение после того, как был достигнут конца строки или считано length ++++1 байтов, тогда как функция fread() игнорирует конец строки и считывает $length байтов. Функцию fgets() можно применить в счетчике, используя следующий код:
<?php //hit_counter05.php
$counter_file = "./count.dat";
if(!($fp = fopen($counter_file, "r"))) die ("Невозможно открыть файл $counter_file."); $counter = (int) fgets($fp, 20);
fclose($fp);
...
Сценарий должен считывать из файла данных только одну строку, поэтому вызов fgets() возвращает именно то, что нужно, т.е. последнее значение счетчика.
Тот, кто сталкивался с импортированием и экспортированием данных, знаком с форматом CSV (comma+separated+value ++++++ значения, разделенные запятыми). (CSV даже имеет собственное расширение файлов: .csv.) В CSV+файлах значения данных разделены запятыми, а строковые значения часто заключаются в двойные кавычки между запятыми. Функция fgetcsv() считывает данные из файлов, предполагая, что эти данные отформатированы в правильном CSV+формате, и помещает найденные в строке данные в массив. Естественно, после этого можно легко использовать полу+ ченный массив с данными.
Функции fgetcsv() необходимо передать корректный дескриптор файла ++++++ чис+ ленное значение, которое должно превышать длину каждой строки (включая символы конца строки), а также необязательные разделители данных (по умолчанию запятая) и ограничители данных (по умолчанию двойные кавычки). Следующий фрагмент ко+ да показывает, как можно получить строку значений данных из CSV+файла:
$file_handle = fopen("my_csv_text_file.csv", "r"); $my_data_values_array = fgetcsv($file_handle, 1000, ",");
В результате работы этого кода формируется массив $my_data_values_array; если бы первым значением в строке текста было чье+нибудь имя (например, Боб), то в элемент $my_data_values_array[0] было бы записано значение ‘‘Боб’’. Следует отметить, что пустая строка в текстовом файле интерпретируется не как ошибка, а как одно NULL+поле.
Функция fputs() представляет собой синоним функции fwrite(); две эти функ+ ции идентичны.

Файлы и каталоги 305
Чтение файлов целиком
Следующие функции предоставляют доступ ко всему содержимому файла за один раз:
file()
fpassthru()
readfile()
Функция file() принимает только один аргумент: строку, содержащую имя фай+ ла. Например, вызов
file("/home/chris/myfile.txt")
возвращает все содержимое файла myfile.txt (находящегося в каталоге /home/chris/) в виде массива, каждый элемент которого представляет собой одну строку файла, включая символ конца строки (CRLF на Windows+платформе). Функция не требует указания дескриптора файла, а позволяет задавать имя файла явно; она автоматически открывает файл, считывает из него данные и по завершении чтения закрывает файл.
Рассмотрим file()+версию счетчика посещений:
<?php //hit_counter06.php
$counter_file = "./count.dat"; $lines = file($counter_file); $counter = (int) $lines[0];
$counter++;
echo "Вы – посетитель № $counter.";
if(!($fp = fopen($counter_file, "w"))) die ("Невозможно открыть файл $counter_file."); fwrite($fp, $counter);
fclose($fp); ?>
Все содержимое файла данных считывается в массив $lines, после чего извлекается только первый элемент данного массива (первая строка файла). То же самое можно бы+ ло бы сделать с помощью комбинации функций feof() и fgets(), например, так:
$fp = fopen ("./count.dat", "r");
while (!feof($fp)) $lines[] = fgets($fp, 1024); fclose ($fp);
Однако делать это нет необходимости, потому что функция file() делает все это автоматически. Как и fopen(), функция file() способна открывать файлы на уда+ ленном узле:
$file_lines = file("http://www.whatyoumaycallit.com/index.html"); foreach($file_lines as $line) echo $line;
Хотя описываемая функция может оказаться очень полезной для чтения всего содер+ жимого файлов, ее следует использовать осторожно ++++++ если попытаться с ее помощью прочитать очень большой файл, то она может занять всю выделенную для PHP память.
Функция fpassthru() оказывается полезной, если нужно прочесть файл и распе+ чатать его целиком в Web+браузер. Она принимает единственный аргумент, дескриптор файла, который использует для считывания из файла оставшихся данных (т.е. с теку+ щей позиции до конца файла). Затем она записывает результаты в стандартный вы+ вод. Ниже приведена соответствующая версия счетчика посещений.

306 Глава 7
<?php //hit_counter07.php
$counter_file = "./count.dat";
if(!($fp = fopen($counter_file, "r"))) die ("Невозможно открыть файл $counter_file."); $counter = (int) fread($fp, 20);
fclose($fp);
$counter++;
if(!($fp = fopen($counter_file, "w"))) die ("Невозможно открыть файл $counter_file."); fwrite($fp, $counter);
fclose($fp);
if(!($fp = fopen($counter_file, "r"))) die ("Невозможно открыть файл $counter_file."); echo "Вы – посетитель № ";
fpassthru($fp); ?>
В поток стандартного вывода записываются данные, начиная с текущей позиции. Например, если перед вызовом fpassthru() прочитать из файла несколько строк, то будет распечатано только содержимое последующих строк. Файл закрывается по+ сле того, как функция заканчивает чтение, поэтому нет необходимости вызывать fclose() ++++++ фактически, если это сделать, то появится предупреждение о некор+ ректном указателе файла.
Функция readfile() позволяет распечатывать содержимое файла, даже не вызы+ вая fopen(). В качестве своего единственного аргумента она принимает имя файла, считывает этот файл целиком, а затем распечатывает его в стандартный вывод, воз+ вращая при этом количество считанных байтов (или False в случае ошибки). Рас+ смотрим ее применение в счетчике посещений:
<?php //hit_counter08.php
$counter_file = "./count.dat";
if(!($fp = fopen($counter_file, "r"))) die ("Невозможно открыть файл $counter_file."); $counter = (int) fread($fp, 20);
fclose($fp);
$counter++;
if(!($fp = fopen($counter_file, "w"))) die ("Невозможно открыть файл $counter_file."); fwrite($fp, $counter);
fclose($fp);
echo "Вы – посетитель № "; readfile($counter_file); ?>
Как и в предыдущем сценарии, все операции чтения и записи данных в файл вы+ полняются до вывода на страницу последнего значения счетчика.
Произвольный доступ к данным файла
Вероятно, было бы более эффективно открывать файл для чтения и записи с по+ мощью одного вызова fopen(), однако рассмотренные выше функции лишь позво+ ляют манипулировать данными последовательно, т.е. в том же порядке, в котором эти данные организованы (или будут организованы в файле). Это сильно ограничивает возможности их применения; как только указатель позиции в файле переместился к определенной точке, приходится закрывать файл и открывать его снова, прежде

Файлы и каталоги 307
чем можно будет получить доступ к данным в этой точке. Если доступ к данным не был заблаговременно и идеально организован (что чаще всего и бывает; невозможно пре+ дугадать, что потребуется в будущем), то, в конце концов, файл будет открываться столько же раз (если не больше), как представленных ранее в примерах.
Следовательно, нужно найти способ перемещать указатель позиции в файле без необходимости закрывать и заново открывать данный файл. Для этого существует две функции:
fseek()
ftell()
PHP предоставляет множество функций, которые предназначены для того, чтобы прочитать данные из определенной позиции или записать их в определенную пози+ цию файла. Если передать функции fseek() в качестве аргументов дескриптор файла ($fp) и целое смещение (в следующем примере 5), то данная функция переместит указатель позиции в файле, связанном с дескриптором $fp, в позицию, которая опре+ деляется смещением. По умолчанию смещение отсчитывается в байтах от начала файла. Рассмотрим пример:
fseek($fp, 5); $one_char = fgetc($fp);
Данный код помещает указатель позиции в файле, связанном с дескриптором $fp, сразу после пятого байта в этом файле. Следовательно, вызов fgetc() возвращает содержимое шестого байта. Для расчета относительного смещения с помощью одного из следующих значений можно задать третий необязательный аргумент (который на+ зывается в документации whence (точка отсчета)):
SEEK_SET: начало файла плюс смещение;
SEEK_CUR (используется по умолчанию): текущая позиция плюс смещение;
SEEK_END: конец файла плюс смещение.
Функция fseek() необычна, потому что является целочисленной PHP+функцией, которая возвращает 0, а не 1 в случае успеха (а в случае неудачи возвращает -1). Ее нельзя использовать с файлами на удаленных узлах через HTTP или FTP URL.
Функция ftell() принимает дескриптор файла и возвращает текущее смещение (в байтах) указателя позиции в данном файле. Например:
$fpi_offset = ftell($fp); rewind()
Функция работает подобно обратной перемотке ленты в магнитофоне ++++++ она при+ нимает дескриптор файла и переустанавливает указатель позиции в начало этого файла. Вызов
rewind($fp);
функционально аналогичен вызову
fseek($fp, 0);
Как было показано выше, функция fpassthru() выводит данные файла, начиная с текущей позиции. Если данные из файла уже считывались, но желательно вывести все содержимое файла, то сначала необходимо вызвать функцию rewind().
С помощью функции rewind() можно модернизировать сценарий счетчика так, чтобы он открывал файл только однажды и для чтения и для записи:

308 Глава 7
<?php //hit_counter09.php
$counter_file = "./count.dat";
if(!($fp = fopen($counter_file, "r+"))) die ("Невозможно открыть файл $counter_file.");
$counter = (int) fread($fp, 20); $counter++;
echo "Вы – посетитель № $counter."; rewind($fp);
fwrite($fp, $counter); fclose($fp);
?>
Очевидно, что в данном случае файл открывается только один раз в режиме чте+ ния+записи. После считывания из файла последнего числа посещений и отображения этого числа вызывается функция rewind(), которая возвращает указатель позиции в начало файла.
Практика Навигация по файлу
В следующем примере используются описанные функции fseek(), ftell() и rewind():
<?php //nav_file.php
$name_field_len = 15; $country_code_field_len = 2; $country_field_len = 20; $email_field_len = 30;
if(!($fp = fopen("./address.dat", "r"))){
die ("Невозможно открыть файл с адресными данными.");
}
do{
$address = '';
$field = fread($fp, $name_field_len); $address .= $field;
$field = fread($fp, $country_code_field_len); $address .= $field;
$field = fread($fp, $country_field_len); $address .= $field;
$field = fread($fp, $email_field_len); $address .= $field;
echo "$address<BR>"; }while($field);
rewind($fp);
echo "<BR>";
fseek($fp, $name_field_len);
do{
$country_code = fread($fp, $country_code_field_len); fseek($fp, ftell($fp) + $country_field_len +
$email_field_len +
$name_field_len + 1);
//Примечание: на Win32-платформах '+1' необходимо заменить на '+2'

Файлы и каталоги 309
echo "$country_code<BR>"; }while($country_code);
fclose($fp); ?>
Предполагается, что в текущем каталоге существует файл адресной книги, который называется address.dat и выглядит следующим образом (необходимо очень внима+ тельно ввести данный текст и даже соблюдать количество пробелов в каждом поле):
Wankyu Choi |
KRRepublic of Korea |
wankyu@whatyoumaycallit.com |
James Hetfield |
USUnited States |
james@headbangers.com |
Nomura Sensei |
JPJapan |
nomura@nosuchsite.com |
Результат тестового запуска сценария:
Wankyu Choi KRRepublic of Korea wankyu@whatyoumaycallit.com
James Hetfield USUnited States james@headbangers.com
Nomura Sensei JPJapan nomura@nosuchsite.com
KR
US
JP
Записи в данном файле отделяются друг от друга символами новой строки. (Как уже говорилось, символом новой строки в Windows+платформах является последова+ тельность CRLF, в Macintosh ++++++ CR, а в Linux ++++++ LF.) Каждое поле имеет фиксирован+ ную длину: 15 символов для имени, 2 символа для кода страны и т.д.
Как это работает
Сценарий начинает свою работу с открытия в текущем каталоге файла address.dat в режиме для чтения. Сначала отображаются все записи в том виде, как они есть в файле. Когда функция fread() достигает конца файла, переменная $field получает значе+ ние False и первый цикл прекращается:
if(!($fp = fopen("./address.dat", "r"))){
die ("Невозможно открыть файл с адресными данными.");
}
do{
$address = '';
$field = fread($fp, $name_field_len); $address .= $field;
$field = fread($fp, $country_code_field_len); $address .= $field;
$field = fread($fp, $country_field_len); $address .= $field;
$field = fread($fp, $email_field_len); $address .= $field;
echo "$address<BR>"; }while($field);
После этого указатель позиции возвращается в начало файла, а затем в конец пер+ вого поля с именем. Теперь сценарий готов считывать и отображать значения кодов страны:
rewind($fp);
fseek($fp, $name_field_len);
Начинается цикл do...while. В каждой его итерации сначала с помощью функ+ ции fread() переменной $country_code присваивается код страны. Затем указатель

310 Глава 7
позиции перемещается в начало следующего поля с кодом страны, и, наконец, полу+ ченный код выводится на экран.
do{
$country_code = fread($fp, $country_code_field_len);
Точная позиция следующего поля с кодом страны определяется таким образом:
с помощью вызова ftell($fp) определяется текущая позиция указателя;
к данному числу прибавляется общая длина всех оставшихся полей и завер+ шающий символ новой строки.
Используя полученный результат как второй аргумент для функции fseek(), можно установить указатель позиции в соответствующую точку файла:
fseek($fp, ftell($fp) + $country_field_len + $email_field_len + $name_field_len + 1);
Для Windows+платформ длина комбинации CR LF равна 2, а не 1 (как в Linux или Macintosh). Записанное значение выводится на экран и цикл, который длится, пока переменная $country_code содержит значение отличное от False, закрывается:
echo "$country_code<BR>"; }while($country_code);
Наконец, закрывается файл данных:
fclose($fp);
Получение информации о файлах
В данной главе уже было показано, какие проблемы могут возникнуть, если сцена+ рий попытается открыть несуществующий файл счетчика. Кроме этого, вы уже озна+ комились с некоторыми базовыми процедурами проверки ошибок при открытии файла. Такой подход очень прост ++++++ если что+либо происходит неправильно, то поль+ зователю необязательно знать, в чем заключается проблема.
PHP предоставляет несколько базовых функций, которые позволяют получить дос+ туп к весьма полезной информации. Например, вместо того, чтобы выдавать стандарт+ ное сообщение об ошибке при неудачном открытии файла, можно использовать функ+ цию file_exists(), позволяющую проверить существование файла. Если заданного файла не существует, то можно сделать вывод, что данный пользователь является пер+ вым посетителем этой страницы, и создать необходимый файл данных. Например:
file_exists("/home/chris/count.dat")
Данная функция возвращает True, если файл count.dat существует и находится в каталоге /home/chris/, и False ++++++ в противном случае. Проверка ошибок теперь может иметь следующую форму:
<?php //hit_counter_10.php
$counter_file = "./count.dat"; if(file_exists($counter_file)) {
if(!($fp = fopen($counter_file, "r+")))
die("Невозможно открыть файл $counter_file"); $counter = (int) fread($fp, filesize($counter_file)); $counter++;
rewind($fp);
}

Файлы и каталоги 311
else {
if(!($fp = fopen($counter_file, "w")))
die("Невозможно открыть файл $counter_file"); $counter = 1;
}
echo "Вы – посетитель № $counter.";
fwrite($fp, $counter); fclose($fp);
?>
Это только одна из множества функций, которые возвращают полезную информа+ цию о заданном файле. Подобным образом можно использовать функцию filesize(), определяющую точное количество байтов, которые необходимо считать из файла данных. Как и предыдущая функция, filesize() вместо дескриптора файла прини+ мает непосредственно его имя:
filesize("/home/chris/count.dat")
Врезультате такого вызова возвращается размер заданного файла в байтах или False
вслучае ошибки. Функцию filesize() можно использовать для того, чтобы опреде+ лить, сколько байтов необходимо считать из файла счетчика:
<?php //hit_counter11.php
$counter_file = "./count.dat"; if(file_exists($counter_file)) {
if(!($fp = fopen($counter_file, "r+")))
die("Невозможно открыть файл $counter_file"); $counter = (int) fread($fp, filesize($counter_file)); $counter++;
rewind($fp);
}
...
Временные свойства файлов
Кроме содержимого, существуют также другие свойства файлов, которые могут со+ общать полезную информацию об этих файлах. Эти свойства главным образом зависят от операционной системы, на которой создаются и модифицируются файлы. На Unix+ платформах, например, таких как Linux, данные свойства включают в себя дату созда+ ния, дату изменения, дату последнего доступа и права пользователей. Добавив немного кода, можно заставить сценарий счетчика отображать время последнего доступа к стра+ нице. Функция fileatime() возвращает время последнего доступа к файлу в формате временных меток Unix, а также дату изменения файла в Windows+формате.
Временная метка Unix представляет собой длинное целое число, которое можно интерпретировать как количество секунд между началом так называемой Unix*эпохи (1 января 1970 года) и указанной датой и временем.
PHP предоставляет две другие связанные с временными свойствами файлов функции.
filectime() возвращает время последнего изменения файла в формате вре+ менной метки Unix. Изменение файла фиксируется при создании файла, запи+ си данных в файл или изменении прав доступа к файлу.

312 Глава 7
filemtime() возвращает время последней модификации файла в формате временной метки Unix. Файл считается модифицированным, если он был соз+ дан или его содержимое изменилось.
При работе с временными метками очень полезной является функция getdate(). Она возвращает ассоциативный массив, содержащий информацию о представленной в виде временной метки дате. Возвращаемый массив содержит такие значения, как год, месяц, день месяца и др. Можно записать возвращаемое функцией getdate() значение в переменную (например, $my_date), а затем получить месяц, обратившись к элементу $my_date[‘month’].
Практика Отображение времени последнего доступа к файлу данных счетчика
В данном примере для получения значений даты для файла, имя которого записа+ но в переменную $counter_file, используются функции fileatime() и getdate().
<?php //last_counter_access.php $counter_file = "./count.dat"; if(file_exists($counter_file)){
$date_str = getdate(fileatime($counter_file)); $year = $date_str["year"];
$mon = $date_str["mon"]; $mday = $date_str["mday"]; $hours = $date_str["hours"];
$minutes = $date_str["minutes"]; $seconds = $date_str["seconds"];
$date_str = "$hours:$minutes:$seconds $mday/$mon/$year";
if(!($fp = fopen($counter_file, "r+"))){ die("Невозможно открыть файл $counter_file");
}
$counter = (int) fread($fp, filesize($counter_file)); $counter++;
echo "Вы – посетитель № $counter.";
echo "Последнее посещение страницы состоялось $date_str"; rewind($fp);
}else{
if(!($fp = fopen($counter_file, "w"))){ die("Невозможно открыть файл $counter_file");
}
$counter = 1;
echo "Вы – посетитель № $counter.";
}
fwrite($fp, $counter); fclose($fp);
?>
На рис. 7.4 показано, как должна выглядеть эта страница (следует отметить, что на Windows+системах может отображаться время создания файла, а не время последнего доступа к нему).