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(), и не даёт долго работающему скрипту исчерпать дескрипторы файлов.