Работа с файлами в веб-разработке: основные операции и продвинутые примеры
Основы обработки файлов в PHP
Наиболее контролируемый способ - использование функций fopen(), fwrite() (или fread()) и fclose(). Этот подход позволяет точно управлять режимом открытия, буферизацией и обработкой ошибок.
<?php
$filename = 'data.txt';
$handle = fopen($filename, 'w'); // 'w' - запись с перезаписью
if ($handle === false) {
exit('Не удалось открыть файл');
}
$written = fwrite($handle, 'Текст для записи');
if ($written === false) {
// обработка ошибки записи
}
fclose($handle);
?>
Пояснение: fopen возвращает дескриптор или false. После операции файл обязательно закрывается. Режим 'w' создаёт файл при отсутствии или перезаписывает существующий.
Типичные ошибки:
- Отсутствие прав на запись в директории - проверять через
is_writable(). - При одновременной записи нескольких процессов данные могут перемешиваться - рекомендуется блокировка
flock().
Как записать строку в файл без ручного управления дескриптором?
Функция file_put_contents() объединяет открытие, запись и закрытие в одну строку.
$bytes = file_put_contents('example.txt', 'Простое содержимое');
Пояснение: Возвращает количество записанных байт или false. По умолчанию файл перезаписывается.
Проблема: нет возможности проверить существование файла перед записью. Для добавления в конец используйте флаг FILE_APPEND.
Как прочитать весь файл в строку?
file_get_contents() - самый простой способ получить содержимое файла.
$content = file_get_contents('readme.txt');
if ($content === false) {
// ошибка чтения
}
Пояснение: Функция считывает файл целиком в память, что удобно для небольших файлов.
Ограничение: при больших объёмах данных (например, >100 МБ) может не хватить памяти. Для таких случаев лучше использовать построчное чтение.
Как читать файл построчно для обработки больших объёмов?
Комбинация fopen() + fgets() в цикле.
$handle = fopen('huge.log', 'r');
while (($line = fgets($handle)) !== false) {
echo $line;
}
fclose($handle);
Пояснение: Каждая итерация считывает одну строку (до символа новой строки). Память расходуется минимально.
Возможная проблема: если строки очень длинные, fgets() может прочитать только часть. В таких случаях нужно настроить размер буфера вторым параметром или использовать stream_get_line().
Как дописать данные в конец существующего файла?
Режим 'a' (append) у fopen() или флаг FILE_APPEND для file_put_contents().
file_put_contents('log.txt', 'Новая запись\n', FILE_APPEND);
// или
$handle = fopen('log.txt', 'a');
fwrite($handle, 'Новая запись\n');
fclose($handle);
Пояснение: Указатель устанавливается в конец файла, содержимое сохраняется.
Ошибка: если файл не существует, режим 'a' создаст его, но это может быть неожиданным. Сначала стоит проверить существование через file_exists().
Как проверить существование и доступность файла?
Функции file_exists(), is_readable(), is_writable().
if (file_exists('data.txt') && is_writable('data.txt')) {
// можно писать
}
Пояснение: file_exists() возвращает true для файлов и директорий. Дополнительные проверки уточняют права.
Нюанс: между проверкой и операцией файл может быть удалён или изменён (race condition). Для критичных случаев используйте блокировку.
Как удалить файл с контролем ошибок?
unlink() удаляет файл. Перед удалением стоит проверить его существование.
if (file_exists('old.txt')) {
unlink('old.txt');
} else {
// файл не найден
}
Пояснение: unlink() возвращает true при успехе или false при ошибке.
Типичная ошибка: удаление файла, который открыт в другом процессе, может привести к потере данных. В таких случаях сначала закрыть все дескрипторы.
Как обработать CSV файл с данными?
Специализированные функции fgetcsv() и fputcsv() упрощают парсинг.
$handle = fopen('users.csv', 'r');
while (($row = fgetcsv($handle, 0, ',', '"')) !== false) {
print_r($row);
}
fclose($handle);
Пояснение: Параметры: максимальная длина строки (0 - без ограничения), разделитель, ограничитель текста.
Проблема: CSV может содержать разные разделители (точка с запятой). Нужно указывать их явно. Также некорректно экранированные кавычки ломают парсинг.
Как загрузить файл на сервер из HTML формы?
Суперглобальный массив $_FILES и функция move_uploaded_file().
$uploadDir = 'uploads/';
$tmpName = $_FILES['userfile']['tmp_name'];
$destName = $uploadDir . basename($_FILES['userfile']['name']);
if (move_uploaded_file($tmpName, $destName)) {
echo 'Файл загружен';
}
Пояснение: Важно проверять MIME-тип и размер файла перед перемещением. move_uploaded_file() гарантирует, что файл был загружен через HTTP POST.
Ошибки безопасности: не следует доверять имени файла из формы - возможны path traversal атаки. Используйте basename() и проверяйте расширение.
Как работать с JSON данными в файлах?
Комбинация json_encode(), json_decode() и файловых функций.
$data = ['name' => 'John', 'age' => 30];
file_put_contents('user.json', json_encode($data, JSON_PRETTY_PRINT));
// чтение
$json = file_get_contents('user.json');
$array = json_decode($json, true);
Пояснение: json_decode со вторым параметром true возвращает ассоциативный массив.
Проблема: некорректный JSON вызывает json_last_error(). Всегда проверять результат декодирования.
Как записать бинарные данные (например, изображение)?
Для бинарных файлов используется режим 'wb' (binary write) или 'rb'.
$source = fopen('photo.jpg', 'rb');
$dest = fopen('copy.jpg', 'wb');
stream_copy_to_stream($source, $dest);
fclose($source);
fclose($dest);
Пояснение: Режим 'b' отключает преобразование концов строк, что критично для бинарных данных.
Ошибка: на Windows без 'b' бинарные файлы могут быть повреждены (добавление символов \r\n).
Как обрабатывать ошибки при работе с файлами?
Всегда проверять возвращаемые значения на false и использовать пользовательские обработчики ошибок.
$handle = @fopen('protected.txt', 'r'); // @ подавляет предупреждение
if ($handle === false) {
$error = error_get_last();
// логирование или исключение
}
Пояснение: Подавление ошибок не рекомендуется; лучше настроить исключения через set_error_handler() или проверять права заранее.
Типичная ошибка: игнорирование возвращаемого значения false приводит к неожиданным сбоям. Каждая операция с файлом требует проверки.
Чтение большого файла с помощью генератора
Позволяет обрабатывать файлы, не загружая их целиком в память.
function readLinesGenerator($filename) {
$handle = fopen($filename, 'r');
while (($line = fgets($handle)) !== false) {
yield rtrim($line, "\n\r");
}
fclose($handle);
}
$lines = readLinesGenerator('large.log');
foreach ($lines as $line) {
echo $line . PHP_EOL;
}
Вывод первых строк файла large.log (если файл существует). Пример вывода: [2025-03-15 10:00:01] INFO Старт приложения [2025-03-15 10:00:02] DEBUG Параметры загружены ...
Объектно-ориентированный подход с SplFileObject
Итератор для удобной работы с CSV или текстовыми файлами.
$file = new SplFileObject('data.csv');
$file->setFlags(SplFileObject::READ_CSV | SplFileObject::SKIP_EMPTY);
foreach ($file as $row) {
if ($row !== []) {
echo implode(' | ', $row) . "\n";
}
}
Результат: Имя | Возраст | Город Иван | 28 | Москва Мария | 32 | Санкт-Петербург
Потоки и контексты: чтение через HTTP с авторизацией
Использование stream_context_create() для удалённой загрузки файла.
$context = stream_context_create([
'http' => [
'header' => "Authorization: Basic " . base64_encode('user:pass'),
'timeout' => 30
]
]);
$content = file_get_contents('https://api.example.com/data.json', false, $context);
if ($content !== false) {
echo 'Загружено ' . strlen($content) . ' байт';
}
Вывод: Загружено 2048 байт (при успешном запросе).
Рекурсивный обход директории и поиск файлов по расширению
Обход папки и обработка всех файлов с заданным расширением.
function findFiles($dir, $extension) {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if (strtolower($file->getExtension()) === $extension) {
yield $file->getPathname();
}
}
}
foreach (findFiles('/var/www/logs', 'log') as $path) {
echo $path . "\n";
}
/var/www/logs/app.log /var/www/logs/error.log /var/www/logs/subdir/debug.log
Блокировка файла для предотвращения одновременной записи
Использование flock() для исключительной блокировки.
$handle = fopen('counter.txt', 'c+');
if (flock($handle, LOCK_EX)) {
$count = (int) fread($handle, 1024);
$count++;
rewind($handle);
fwrite($handle, (string)$count);
flock($handle, LOCK_UN);
} else {
echo 'Не удалось заблокировать файл';
}
fclose($handle);
Файл counter.txt будет содержать увеличенное значение (например, 5).
Загрузка файла на сервер с проверками
Полный пример с валидацией MIME-типа, размера и перемещением.
$allowedTypes = ['image/jpeg', 'image/png'];
$maxSize = 2 * 1024 * 1024; // 2MB
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['upload'])) {
$file = $_FILES['upload'];
if ($file['error'] !== UPLOAD_ERR_OK) {
die('Ошибка загрузки: ' . $file['error']);
}
if (!in_array($file['type'], $allowedTypes)) {
die('Недопустимый тип файла');
}
if ($file['size'] > $maxSize) {
die('Файл слишком большой');
}
$dest = 'uploads/' . uniqid() . '_' . basename($file['name']);
if (move_uploaded_file($file['tmp_name'], $dest)) {
echo 'Файл сохранён: ' . $dest;
} else {
echo 'Ошибка перемещения';
}
}
При успешной загрузке: Файл сохранён: uploads/63f1a2b3c4d5_photo.jpg
Чтение и запись бинарного файла (изображения) с проверкой
Копирование изображения с использованием fread и fwrite.
$src = fopen('source.png', 'rb');
$dst = fopen('dest.png', 'wb');
if ($src && $dst) {
while (!feof($src)) {
$chunk = fread($src, 8192);
if ($chunk !== false) {
fwrite($dst, $chunk);
}
}
fclose($src);
fclose($dst);
echo 'Копирование успешно';
} else {
echo 'Ошибка открытия файлов';
}
Вывод: Копирование успешно (размер dest.png совпадает с source.png).
Временные файлы через tmpfile()
Создание временного файла, который удаляется автоматически после закрытия.
$handle = tmpfile();
fwrite($handle, 'Временные данные');
rewind($handle);
echo fread($handle, 1024);
fclose($handle); // файл удалён
Вывод: Временные данные
Атомарная запись с LOCK_EX через file_put_contents
Флаг LOCK_EX обеспечивает эксклюзивную блокировку.
$data = 'Важные данные' . PHP_EOL;
$result = file_put_contents('lockfile.txt', $data, LOCK_EX);
if ($result !== false) {
echo 'Записано ' . $result . ' байт';
}
Вывод: Записано 14 байт