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…
Читайте также: