Nickolay.info. PHP. Статьи. Пример на работу с текстовым файлом в PHP

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

Пусть наш пример будет поддерживать простейшую текстовую "базу данных", в которой одна строка файла является одной записью. Запись будет состоять из 2 величин - имя (т.е., строка) и некое число, отделённое пробелом. Скрипт должен проверять существование файла (и при необходимости создавать новый), не позволять добавлять одинаковые записи, сортировать поддерживаемый список по алфавиту, уметь показать существующий список и форму для ввода новой записи.

Обычно подобные действия и требуются в реальных приложениях.

Сначала определим имя файла для наших данных, папка предполагается текущей:

$filename = 'data.txt';

Возможно, мы будем выходить из скрипта в нескольких местах кода (например, из-за ошибок доступа к файлу), так что сразу напишем функцию myexit, "закрывающую" документ HTML и делающую выход. А вот "вход" в скрипт будет точно один, так что печать соответствующего заголовка документа HTML поставим первым действием.

function myexit($errorcode) {
  echo '</body></html>';
  exit ($errorcode);
 }

 echo '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
 <html>
 <head>
  <meta content="text/html; charset=Windows-1251" http-equiv="content-type">
  <title>Пример на работу с текстовым файлом</title>
 </head>
 <body>';

Проверим, что файл существует и доступен для записи, если нет - попытаемся создать пустой файл и проверим, что получилось. При неудаче - выводим сообщение об ошибке и выходим с кодом завершения 1.

if (!is_writable($filename)) {
  if (!file_exists($filename)) {
   $f=@fopen($filename,"w");
   if (!$f) {
    echo 'Не удалось открыть на запись новый файл данных '.$filename.', проверьте права!'; 
    myexit(1);
   }
   fclose ($f);
  }
 }

Получим содержимое файла, разделим его на отдельные строки методом explode (каждая запись будет в элементе массива $a), элементы массива $a, в свою очередь, разделим по пробелу на имена (после второго вызова explode они будут содержаться в $fields[0]) и числа ($fields[1]). Но нам нужны пока только имена - чтобы сформировать из них массив всех имён $fio. Ведь мы потом будем проверять имя на повторное добавление.

$data=file_get_contents ($filename); //можно сразу $a=file($filename); вместо этих 2 строк -
$a=explode("\n",$data);                    //но нам потом понадобится $data как одна строка
$fio=array();
foreach ($a as $item) {
 $fields = explode(" ",trim($item));
 if (!empty($fields[0])) {
  array_push ($fio,$fields[0]);
 }
}

Проверим и получим 2 внешних параметра - имя $name и число $number. Из имени мы просто удалили теги функцией htmlspecialchars, а число преобразовали методом intval, чтобы удалить нежелательные символы.

$name = $number = '';
 if (isset($_GET['name'])) $name = htmlspecialchars(trim($_GET['name']));
 if (isset($_GET['number'])) $number = intval(trim($_GET['number']));

Если обе переданных величины непусты, попытаемся добавить их в файл - но сначала проверим, что такого имени ещё нет (array_search). Перед добавлением сортируем массив данных функцией sort и объединяем его в строку функцией implode, отдельные записи снова разделяем переводом строки. Конечно, такой подход нежелателен для больших объёмов данных, в этом случае лучше применять базы данных.

if (!empty($name) and !empty($number)) {
  if (array_search($name,$fio)===false) {
   array_push ($a,"$name $number");
   sort ($a);
   $data=implode ("\n",$a);
   file_put_contents ($filename,$data);
  }
 }

Выводим форму для внесения новых данных, её поля, естественно, называются name и number, если соответствующие переменные были переданы - они выводятся как начальное содержимое полей. Конечно, неплохо было бы, если бы при совпадении поля имени и несовпадении числа скрипт умел ещё обновлять данные в файле - попробуйте "прикрутить" соответствующий код.

echo '<form action="index.php" method="get">
  Имя: <input type="text" name="name" value="'.$name.'" size="30" maxlength="30">
  <br>Число: <input type="text" name="number" value="'.$number.'" size="6" maxlength="6">
  <br><input type="submit" value="Отправить">
 </form>';

Остаётся вывести строку $data и завершить скрипт. Если бы нам понадобилась печать с разделением данных - мы бы просканировали изменённый массив $a циклом foreach.

echo "$data";
 myexit(0);

Ниже прикреплён архив ZIP с файлом этого примера. Предполагается, что файл скрипта называется index.php, то есть, является "файлом по умолчанию" для своей папки.

 Простейший пример на работу с текстовой "базой данных" средствами PHP (1 Кб)

Вот версия этого проекта на одном-единственном файле .php, предполагается, что файл будет записан в кодировке Юникода UTF-8. Проверено на PHP 7.X в XAMPP.

<!DOCTYPE html>
 <html>
 <head>
  <meta charset="utf-8">
  <title>Мини-БД (PHP 5.5 и выше)</title>
 </head>
 <body>
<?php
 //1. Чтение и обработка формата имя \t число
 $filename = 'data.txt';
 if (!is_writable($filename) or !file_exists($filename)) {
  $f=@fopen($filename,"w");
  if (!$f) exit('Не удалось открыть файл на запись!');
 }
 $data = file_get_contents ($filename); //получить данные
 $arr = explode ("\n", $data); //разбить на строки
 $fio = array(); //массив записей ( 0 => имя, 1 => число )
 foreach ($arr as $item) {
  $fields = explode ("\t",trim($item)); //разделитель = TAB
  if (count($fields) > 1) //Только 2 элемента правильно
   array_push ($fio,array($fields[0],$fields[1]));
 }
 //2. Добавление или редактирование данных
 $name = ''; $number = 0;
 if (isset($_GET['name'])) //Переданы ли новые данные?
  $name = htmlspecialchars(trim($_GET['name']));
 if (isset($_GET['number'])) 
  $number = intval(trim($_GET['number']));
 if (!empty($name)) { //Если передано имя
  $key = array_search ($name,array_column($fio,0));
  if ($key === false) //Если имя не найдено - добавить
   array_push ($fio, array($name,$number));
  else $fio[$key] = array ($name,$number); //Иначе заменить число
  usort ($fio, //Сортировать по алфавиту
   function ($a,$b) { return strcmp($a[0],$b[0]); } );
  $data = ''; 
  $n = count($fio);
  for ($i = 0; $i < $n; $i++) //Слить $fio в строку $data
   $data .= $fio[$i][0]."\t".$fio[$i][1].
            ($i < $n-1 ? "\n" : '');
  @file_put_contents ($filename,$data); //Вернуть в файл
 }
 //3. Показать форму и текущие данные из файла
 echo '<form method="get">
  Имя: <input type="text" name="name" value="'.
   $name.'" size="30" maxlength="30">
  Число: <input type="text" name="number" value="'.
   $number.'" size="6" maxlength="6">
  <input type="submit" value="Отправить"></form>';
 echo "<pre>$data</pre>";
?> 
</body></html>

P.S. На практике не следует забывать о теоретической возможности потери данных при работе с плоскими файлами, особенно при записи в них. Например, если два клиента пытаются записать файл одновременно, изменения, сделанные как минимум одним из них, могут пропасть... а в худшем случае пропадает весь файл!

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

$fp = fopen("/tmp/lock.txt", "w+");
if (flock($fp, LOCK_EX)) { //Если удалось "запереть" файл
 fwrite($fp, "Что-нибудь пишем\n");
 flock($fp, LOCK_UN); // "Отпираем" файл
} 
else {
 echo "Ошибка блокировки файла на запись!";
}
fclose($fp);

Однако, проблемы может создать и предшествующее записи чтение - представьте, что скрипт открывает файл, читает оттуда информацию в массив (точка 1), что-то добавляет в этот массив и пишет всё обратно в файл (точка 2). Блокируется только запись. Клиент 2 находится в точке 2 и собирается записать информацию. Клиент 1 из-за блокировки не смог получить содержимое файла, поэтому прочитал пустой массив. Но потом-то он тоже дойдёт до точки 2! Результат - содержимое файла потеряно.

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

function read_file($path){ 
 if (! is_file ($path)) {return false; } 
 elseif (! filesize ($path)) {return array (); } 
 elseif ($array= file ($path)) {return $array; } 
 else { while (!$array= file ($path)){ sleep (1);} return $array; } 
}

Суть этого кода - пока файл блокирован на чтение, пользователь, который хочет прочитать из него данные, находится в цикле с функцией sleep, то есть, как бы "стоит на месте", ожидая, когда файл разблокируется на чтение.

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

Рейтинг@Mail.ru

вверх гостевая; E-mail