А это клавиатура, с помощью которой я творю...
http://kurepin.ru/php/zametki/add_str/
Rambler's Top100
PHP. Заметки. Как записать данные в начало файла

Довольно популярный вопрос среди начинающих программистов: "А как записать данные не в конец, а в начало файла?".

Вопрос хороший. Подобная задача рано или поздно встает перед каждым web-программистом. Обычно — раньше.

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

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

Суть ошибки заключается в том, что программист использует для временного сохранения данных оперативную память: читает файл в память, дописывает к нему новые данные, благо оперативка позволяет дописывать данные куда угодно, после чего сохранят данные в старый файл. "Чем же плох такой метод?", -- спросит любой из вас. А вот чем. Попробуйте мысленно растянуть во времени всю эту процедуру: вот данные прочтены в оперативную память... вот вы закрыли файл для чтения... вновь открыли его, но уже для перезаписи... [fatal error] ...вот тут у вас по какой-то причине оборвалось выполнение скрипта. Сбой в питании, перезагрузка, не хватило квоты на диске или случилась любая другая ошибка. Экземпляр скрипта уничтожается вместе с прочтенными в оперативку данными, а открытый для перезаписи файл автоматически закрывается девственно чистым или с некоторой частью данных, которые успели записаться. Все, данные теперь можно вытащить только из backup. Хорошо, если он у вас окажется актуальным.

Что же произошло, неужели ошибка в алгоритме? Нет, никакой ошибки в алгоритме нет. Но давайте все по порядку...

Предположим, что аварийного завершения работы скрипта у вас никогда не произойдет, хотя я в это и не верю, но пройдет какое-то время и файл с данными (предположим, что это гостевая книга) раздуется до такого размера, что просто перестанет влезать в отведенную для данного скрипта оперативную память. Вы полагаете, что можете распоряжаетесь всей свободной оперативной памятью сервера? Ничего подобного! Каждому скрипту обычно выделяется какой-то фиксированный объем памяти, которым он может распоряжаться. Как правило, это несколько мегабайт. Надеюсь, дальше эту проблему можно не описывать.

"Ну хорошо", -- скажете вы, -- "а если я обеспечу своевременное дробление файла на куски, чтобы не вылезать, скажем, за пределы 1 мегабайта в каждом куске?". Что ж, это ход, но я бы его тоже назвал полумерой. А если к скрипту обратится сразу много запросов, что тогда произойдет? А произойдет вполне очевидное — запустится такое количество экземпляров скрипта, какое может разместиться в оперативной памяти. И если каждый экземпляр скрипта будет читать в память хотя бы по мегабайту, то не трудно посчитать, сколько человек одновременно смогут одновременно прочесть вашу гостевую.

Все дело в том, что описанные проблемы возникают далеко не сразу. Они могут вообще не возникнуть, но где гарантия, что это будет именно так, как вы хотите?

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

"Как же так", — продолжите удивляться вы, — "а зачем тогда в PHP существуют операторы, позволяющие прочесть файл в массив строк и т.п.? Зачем они тогда существуют?". А я вам с удовольствием отвечу на этот вопрос. Существуют они для работы с файлами более-менее фиксированного объема. Самым удобным примером тут может быть файл настроек вашей программы. Конфигурационные файлы обычно имеют более-менее фиксированный размер, и сроки в них представляют собой или директивы или комментарии. Такой файл очень удобно читать в строковый массив, производить над ним действия и записывать данные обратно в файл. Вот так.

Но давайте-ка, вернемся к нашим баранам. Как же все-таки записывать новые данные в начало файла? А вот как:

$file_gb="gb.txt"; // файл гостевой книги
$file_tmp="gb_tmp.txt"; // временный файл
$str="новая строка текста";

// проверяем, не было ли сбоя в предыдущем запуске скрипта
if(file_exists($file_tmp)) die("fatal error, call administrator!");

// копируем содержимое файла в tmp
if(copy($file_gb, $file_tmp))
{
 // удачно скопировался, можно перезаписывать основной файл
 if($w=fopen($file_gb,"w"))
 {
  flock($w,2); // локируем файл
  fwrite($w,$str."\n"); // записываем первую строку

  if(!$r=fopen($file_tmp,"r")) die("can't open file");
  flock($r,1);
  while($str=fgets($r,10240)) // читаем построчно
  {
   fputs($w,$str); // пишем построчно
  }
  flock($r,3);
  fclose($r);
  flock($w,3);
  fclose($w);
  unlink($file_tmp);
 }
}


Вот так. А теперь попробуйте представить себе ситуацию, когда данные могут быть испорчены работой этого скрипта. И не пытайтесь! На каком этапе работы не произошел бы сбой, всегда сохранится актуальная информация: или в основном файле, или во временном. Причем, если информация осталась во временном файле, то следующий экземпляр скрипта не станет работать, чтобы не затереть данные в tmp-файле.

Теперь вы можете быть спокойны за свою гостевую книгу, форум, сбор подписей, хранилище заказов и т.п. — в худшем случае программа просто перестанет принимать новые данные, надежно сохранив существующие.

Надеюсь, мне удалось доказать, что работать с файлами надо как с файлами, а не как с переменными, которые на то и переменные, чтобы часто рождаться и часто умирать.

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

Желаю удачи!

10.01.03

[шаг назад] [печатать] [в начало сайта]



copyright ©2000-2017 Ruslan Kurepin