Menu

PHP fopen(): открыть, прочитать и записать файл, режимы

fopen('data.txt', 'r') открывает файл и возвращает дескриптор или false при сбое. Читайте из дескриптора через fgets() или fread(), пишите через fwrite(), закрывайте через fclose(). Режим ('r', 'w', 'a', 'x', 'c' и их формы с +) определяет, что можно делать.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

fopen('data.txt', 'r') открывает файл и возвращает дескриптор, ресурс потока, который остальные функции для работы с файлами принимают первым аргументом. Читайте через fgets() (одна строка) или fread() (заданное число байтов), пишите через fwrite() и закрывайте через fclose(). Второй аргумент это режим: 'r' для чтения, 'w' для записи, 'a' для дописывания.

Вывод:

Got: apple
Got: banana
Got: cherry

fopen() возвращает false, когда не может открыть файл (его нет в режиме r, отсутствует папка, нет прав), а PHP печатает предупреждение вроде:

Warning: fopen(missing.txt): Failed to open stream: No such file or directory in /home/index.php on line 2

Поэтому проверяйте на false до использования дескриптора. Чтобы прочитать или записать небольшой файл целиком одним вызовом, короче file_get_contents() и file_put_contents(); fopen() нужен для файлов, которые читают по частям или держат открытыми.

Режимы fopen

РежимЧтениеЗаписьЕсли файл существуетЕсли файла нетНачало
rданетоткрываетошибканачало
r+дадаоткрывает, сохраняет содержимоеошибканачало
wнетдаочищаетсоздаётначало
w+дадаочищаетсоздаётначало
aнетдасохраняет содержимоесоздаётконец, каждая запись дописывается
a+дадасохраняет содержимоесоздаётконец, каждая запись дописывается
xнетдаошибкасоздаётначало
x+дадаошибкасоздаётначало
cнетдасохраняет содержимоесоздаётначало
c+дадасохраняет содержимоесоздаётначало

Две строки удивляют. r+ записывает поверх существующих байтов на месте, ничего не вставляя, а a+ всегда пишет в конец, даже после перемещения указателя через fseek() (перемещение влияет только на чтение):

Вывод:

Xbc
Xbc
Xbc
end

В Windows можно встретить 'rb' или 'wb': флаг b означает двоичный режим и не даёт Windows преобразовывать окончания строк. В Linux и macOS он ничего не делает, поэтому его добавление безвредно и переносимо.

Чтение файла построчно через fgets()

fgets() возвращает следующую строку вместе с её \n, а в конце файла false. Поскольку в памяти одновременно только одна строка, этот цикл справится с лог-файлом любого размера, а остановиться раньше можно через break:

Вывод:

2 errors in 5 lines
line 2: database timeout
line 4: disk full

Сравнивайте с false через !==, а не простым while ($line = fgets($fh)): строка, содержащая только "0" без переноса (такой может быть последняя строка файла), ложна и досрочно завершила бы цикл.

Запись и дописывание через fwrite()

fwrite() записывает строку в текущую позицию и возвращает число записанных байтов или false при сбое. fputs() это та же функция под другим именем. Откройте файл в режиме 'a', чтобы дописывать в конец, не трогая то, что там есть:

Вывод:

wrote 12 bytes
wrote 13 bytes
wrote 15 bytes
visit /home
visit /about
visit /contact
visit /home

PHP_EOL это окончание строки той системы, на которой работает PHP (\n в Linux и macOS, \r\n в Windows). Для файлов, которые читают другие программы, выбирайте окончание явно, обычно "\n".

Чтение и запись CSV через fgetcsv() и fputcsv()

fputcsv() записывает массив одной строкой CSV и берёт в кавычки поля, где есть запятые, кавычки или переносы строк. fgetcsv() читает одну строку обратно в массив. Явно передавайте разделитель, символ обрамления и пустой escape-символ: так получается стандартный CSV, а PHP 8.4 печатает уведомление об устаревании, если аргумент escape не указан.

Вывод:

name,city,note
Ada,London,"likes ""math"", a lot"
Linus,Helsinki,kernel

Ada lives in London: likes "math", a lot
Linus lives in Helsinki: kernel

Разбиение строк через explode(',', $line) ломается, как только в поле появляется запятая; fgetcsv() обрабатывает кавычки за вас. Для файла с разделителем точка с запятой (так часто выглядит экспорт из Excel в Европе и России) передайте ';' разделителем.

Перемещение по файлу: fseek(), ftell() и rewind()

У открытого файла есть позиция. Чтение и запись начинаются с неё и сдвигают её вперёд. ftell() сообщает, где она, fseek() перемещает её на смещение в байтах, а rewind() возвращает в начало. С w+ можно записать файл и прочитать его обратно через тот же дескриптор:

Вывод:

11
world
hello world

Блокировка файла через flock()

Два запроса, которые одновременно читают счётчик, прибавляют единицу и записывают обратно, могут оба прочитать 41 и оба записать 42. flock() с LOCK_EX заставляет второй ждать, пока первый не снимет блокировку. Здесь подходит режим c+: он создаёт файл при необходимости и, в отличие от w+, не очищает его до того, как вы взяли блокировку.

Вывод:

Visitor number 1
Visitor number 2
Visitor number 3

Блокировки рекомендательные: они останавливают только другой код, который тоже вызывает flock(). Для дописывания одной строки file_put_contents($path, $line, FILE_APPEND | LOCK_EX) делает то же одним вызовом.

Частая ошибка: while (!feof($fh))

Многие старые учебники крутят цикл через feof(). Она возвращает true только после того, как чтение уже упёрлось в конец, поэтому цикл выполняется на один раз больше, чем строк, и последний fgets() возвращает false:

Вывод:

string(2) "a
"
string(2) "b
"
string(2) "c
"
bool(false)
4 rounds for 3 lines

Этот лишний false превращается в пустую строку отчёта или пустой элемент массива. Стройте цикл на самом чтении, while (($line = fgets($fh)) !== false), как во всех остальных примерах на этой странице.

Часто задаваемые вопросы

Какие режимы fopen есть в PHP?

r читает, w пишет, предварительно очистив файл, a пишет в конец, x создаёт новый файл и завершается ошибкой, если он существует, а c пишет, не очищая файл. Добавление + (r+, w+, a+, x+, c+) разрешает и чтение, и запись. Все режимы, кроме r и r+, создают файл, если его нет.

Чем режим w отличается от a в fopen?

w очищает файл сразу при открытии и пишет с начала. a сохраняет содержимое, и каждый fwrite() идёт в конец файла, даже после fseek(), поэтому это режим для лог-файлов.

Как прочитать CSV-файл в PHP?

Откройте его через fopen($path, 'r') и вызывайте fgetcsv() в цикле: while (($row = fgetcsv($fh, null, ',', '"', '')) !== false) { ... }. Каждый вызов возвращает одну строку как массив полей, а кавычки и запятые внутри полей обрабатываются за вас.

Нужно ли вызывать fclose в PHP?

PHP закрывает все открытые файлы, когда скрипт завершается, поэтому короткий скрипт работает и без него. Всё равно вызывайте fclose(), как только закончили: он сбрасывает буферизованные записи, снимает блокировки, взятые через flock(), и не даёт долго работающему скрипту исчерпать дескрипторы файлов.

Иллюстрация языков программирования Coddy

Учитесь программировать с Coddy

НАЧАТЬ