Php не пишет в файл

Обновлено: 07.07.2024

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

Чтение и запись файлов

Открытие и закрытие файлов

Для открытия файлов в PHP определена функция fopen() . Она имеет следующее определение: resource fopen(string $filename, string $mode) . Первый параметр $filename представляет путь к файлу, а второй - режим открытия. В зависимости от цели открытия и типа файла данный параметр может принимать следующие значения:

'r' : файл открывается только для чтения. Если файла не существует, возвращает false

'r+' : файл открывается только для чтения с возможностью записи. Если файла не существует, возвращает false

'w' : файл открывается для записи. Если такой файл уже существует, то он перезаписывается, если нет - то он создается

'w+' : файл открывается для записи с возможностью чтения. Если такой файл уже существует, то он перезаписывается, если нет - то он создается

'a' : файл открывается для записи. Если такой файл уже существует, то данные записываются в конец файла, а старые данные остаются. Если файл не существует, то он создается

'a+' : файл открывается для чтения и записи. Если файл уже существует, то данные дозаписываются в конец файла. Если файла нет, то он создается

Результатом функции fopen будет дескриптор файла. Этот дескриптор используется для операций с файлом и для его закрытия.

После окончания работы файл надо закрыть с помощью функции fclose() , которая принимает в качестве параметра дескриптор файла. Например, откроем и закроем файл:

Чтение файла

Для чтения файла можно использовать несколько функций. Для построчного чтения используется функция fgets() , которая получает дескриптор файла и возвращает одну считанную строку. Пройдем построчно по всему файлу:

При каждом вызове fgets() PHP будет помещать указатель в конец считанной строки. Чтобы проследить окончание файла, используется функция feof() , которая возвращает true при завершении файла. И пока не будет достигнут конец файла, мы можем применять функцию fgets().

Чтение файла полностью

Если нам надо прочитать файл полностью, то мы можем облегчить себе жизнь, применив функцию file_get_contents() :

При этом нам не надо открывать явно файл, получать дескриптор, а затем закрывать файл.

Поблочное считывание

Также можно провести поблочное считывание, то есть считывать определенное количество байт из файла с помощью функции fread() :

Функция fread() принимает два параметра: дескриптор считываемого файла и количество считываемых байтов. При считывании блока указатель в файле становится в конец этого блока. И также с помощью функции feof() можно отследить завершение файла.

Запись файла

Для записи файла применяется функция fwrite() , которая записывает в файл строку:

Аналогично работает другая функция fputs() :

Работа с указателем файла

При открытии файла для чтения или записи в режиме 'w', указатель в файле помещается в начало. При считывании данных PHP перемещает указатель в файле в конец блока считанных данных. Однако мы также вручную можем управлять указателем в файле и устанавливать его в произвольное место. Для этого надо использовать функцию fseek, которая имеет следующее формальное определение:

Параметр $handle представляет дескриптор файла. Параметр $offset - смещение в байтах относительно начала файла, с которого начнется считывание/запись. Третий необязательный параметр задает способ установки смещения. Он может принимать три значения:

SEEK_SET : значение по умолчанию, устанавливает смещение в offset байт относительно начала файла

SEEK_CUR : устанавливает смещение в offset байт относительно начала текущей позиции в файле

SEEK_END : устанавливает смещение в offset байт от конца файла

В случае удачной установки указателя функция fseek() возвращает 0, а при неудачной установке возвращает -1.

Если в кратце, то суть такая: file_put_contents() не атомарен и не гарантирует записи данных файл, а может вообще очистить его содержимое так и не записав новое содержимое.

Синтаксический сахар это хорошо. Судя по описанию, функция file_put_contents() идентична последовательным успешным вызовам функций fopen(), fwrite() и fclose(), что влечёт за собой некоторые особенности, связанные с этими функциями. Сразу скажу, что пробовал воспроизводить это только внутри Docker контейнеров и утилизацией диска близкой к 100%, возможно в другом окружении такое поведение воспроизводиться не будет. Для начала давайте с умным видом посмотрим исходник PHP 7.1.12.

Собственно, какие шаги можно увидеть?

  • открывается файл (php_stream_open_wrapper_ex) по-умолчанию в режиме w, что производит очистку файла! Флаг FILE_APPEND открывает файл в режиме a, но этот кейс не рассматриваем.
  • если передан флаг LOCK_EX, то файл открывается в режиме c и дополнительно очищается (php_stream_truncate_set_size)
  • что-то пишется в файл (php_stream_copy_to_stream_ex || php_stream_write)
  • файл закрывается (php_stream_close)

А что, если скрипт прервётся (например, kill, Control + C, reset) после открытия файл в в режиме w, либо в режиме c после php_stream_truncate_set_size, но не дойдя до php_stream_copy_to_stream_ex или php_stream_write? Правильно, файл останется абсолютно пустым! Т.е. вызывая функцию file_put_contents() существует вероятность получить абсолютно пустой файл, вместо его предшествующего содержимого.

Такую же ситуацию можно получить, если вызывать поочерёдно fopen(), fwrite() и fclose(). На вскидку приходит несколько способов защиты от такого побочного эффекта.

Во-первых, не пользоваться для сохранения значимых данных файлами. Гораздо надёжнее использовать полноценные СУБД с их атомарностью в рамках транзакций, что защитит даже от внезапной остановки сервера, при условии использования журналируемой файловой системы, либо InnoDB Doublewrite Buffer в MySQL и его аналогов в других СУБД. Как вариант, можно рассмотреть NoSQL и Key-Value решения, в случаях остановки PHP скрипта они защитят от потери данных. Но, для случаев внезапной остановки сервера нужно изучить их работу.

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

В-третьих, можно воспользоваться таким кодом:

Обратите внимание на открытие файла в режиме c.

  • файл содержит данные: 1234567
  • открывает файл на запись и помещаем указатель на первый символ
  • пишем в файл новые данные: 890 + $
  • в файле оказывается содержание: 890$567
  • обрезаем файл до длины полезных данных, т.е. до 3-х символов
  • в файле оказывается содержимое 890
  • закрываем файл

Такой подход защищён от резкого останова скрипта. Даже если он будет остановлен между 4 и 5 шагами, можно будет в момент чтения отсечь лишние данные начиная с $ символа. При резком отключении всего сервера возникает 2 ситуации, в зависимости от журналируемости файловой системы. Если она не поддерживается или отключена, то в файле может оказаться неконсистентное состояние. Но если журналирование включено, то в файле останется последнее консистентное состояние.

В одной из библиотек на github нашёл такой вариант решения проблемы:

Очень крутая задача, в которой не всё так просто, как кажется на первый взгляд. Попробуйте найти в ней 2 логические…

В некоторых docker-контейнерах при запуске консольных приложений, вместо программы может отобразиться ошибка:

Иногда при обработке с помощью PHP больших и не очень данных, можно словить досадную ошибку посреди выполнения скрипта: PHP Fatal…

Читайте также: