Файловые операции в PHP: как избежать и обработать ошибки ввода вывода
Ошибки файлового ввода вывода в PHP: обзор и решения
Файловые операции часто становятся источником неожиданных сбоев. Причинами могут быть отсутствие файла, недостаток прав, исчерпание дискового пространства или сетевые таймауты. Рассмотрим основные способы обработки таких ошибок.
Основной подход: проверка доступности и исключения
Наиболее эффективное решение включает проверку существования и прав доступа перед обращением к файлу, а также перехват ошибок через try catch. Это даёт полный контроль над процессом и позволяет корректно завершить операцию.
$filename = '/path/to/file.txt';
if (!file_exists($filename)) {
throw new \RuntimeException('Файл не найден: ' . $filename);
}
if (!is_readable($filename)) {
throw new \RuntimeException('Файл недоступен для чтения: ' . $filename);
}
try {
$handle = fopen($filename, 'r');
if ($handle === false) {
throw new \RuntimeException('Не удалось открыть файл: ' . $filename);
}
// работа с файлом
$content = fread($handle, filesize($filename));
} catch (\Throwable $e) {
// логируем и обрабатываем
error_log($e->getMessage());
throw $e; // или возвращаем false
} finally {
if (isset($handle) && is_resource($handle)) {
fclose($handle);
}
}Php not found (ошибка 404 в php)
Такой код гарантирует, что ресурс будет закрыт даже при возникновении исключения. Для записи аналогично проверяется is_writable.
Вариант 1: file_get_contents с контролем ошибок
Как прочитать файл с минимальным кодом, но с контролем ошибок?
Функция file_get_contents возвращает false при неудаче. Однако она также генерирует предупреждения, которые можно перехватить через error_get_last.
$filename = 'data.txt';
$content = @file_get_contents($filename); // @ подавляет warning
if ($content === false) {
$error = error_get_last();
throw new \RuntimeException('Ошибка чтения: ' . $error['message']);
}Php try (обработка исключений try-catch в php)
Использование @ нежелательно, лучше применить временный обработчик ошибок или set_error_handler.
Вариант 2: fopen и fread с пошаговым контролем
Как организовать процесс чтения большого файла с пошаговым контролем?
Подходит для потоковой обработки. После открытия проверяется ресурс, затем в цикле читаются блоки данных.
$handle = fopen('large.txt', 'r');
if ($handle === false) {
throw new \RuntimeException('Не удалось открыть файл');
}
try {
while (!feof($handle)) {
$chunk = fread($handle, 1024);
if ($chunk === false) {
throw new \RuntimeException('Ошибка чтения блока');
}
// обработка chunk
}
} finally {
fclose($handle);
}
Php код ответа (код ответа http в php)
Вариант 3: SplFileObject - объектно ориентированный подход
Какие преимущества даёт объектно ориентированный подход к файлам?
SplFileObject бросает исключения Runtime Exception при ошибках, что упрощает обработку.
try {
$file = new SplFileObject('missing.txt', 'r');
} catch (\RuntimeException $e) {
echo 'Ошибка: ' . $e->getMessage();
exit;
}
foreach ($file as $line) {
// обработка строки
}Post 500 php (ошибка 500 при post-запросе в php)
Вариант 4: проверка прав доступа перед операцией
Как избежать ошибки Permission denied?
Перед записью проверяется is_writable, но из за состояния гонки (race condition) между проверкой и операцией может возникнуть ошибка. Поэтому проверка - только подсказка.
if (is_writable($filename)) {
file_put_contents($filename, $data);
} else {
throw new \RuntimeException('Нет прав на запись');
}Php страница не найдена (страница 404 в php)
Вариант 5: логирование ошибок и продолжение
Как записать ошибку в лог и не прерывать выполнение?
Перехватив исключение, можно занести сообщение в журнал и продолжить работу.
try {
$content = file_get_contents('config.php');
} catch (\Throwable $e) {
error_log('config error: ' . $e->getMessage());
$content = getDefaultConfig();
}Access denied php (ошибка доступа в php)
Вариант 6: использование контекстных опций и временных обработчиков
Как подавить warning и обработать ошибку самостоятельно?
Можно временно подменить обработчик ошибок, чтобы превратить warning в исключение. Это чище, чем @.
set_error_handler(function ($severity, $message, $file, $line) {
throw new \ErrorException($message, 0, $severity, $file, $line);
});
try {
$content = file_get_contents('unreliable.txt');
} catch (\ErrorException $e) {
echo 'Ошибка файла: ' . $e->getMessage();
} finally {
restore_error_handler();
}- No such file or directory - проверять путь, использовать реальный путь или __DIR__;
- Permission denied - проверять права на файл и директорию, владельца процесса;
- Disk quota exceeded - мониторить место на диске;
- File not readable/writable - проверять is_readable/is_writable;
- Maximum execution time - для больших файлов увеличить лимит или читать частями.
Расширенные примеры работы с файлами и обработка ошибок
Пример 1: Рекурсивное чтение директории с обработкой ошибок доступа
function listFilesRecursive(string $dir, int $depth = 0): array {
$result = [];
try {
$iterator = new DirectoryIterator($dir);
} catch (\RuntimeException $e) {
error_log('Нет доступа к ' . $dir . ': ' . $e->getMessage());
return [];
}
foreach ($iterator as $item) {
if ($item->isDot()) continue;
if ($item->isDir()) {
$sub = listFilesRecursive($item->getPathname(), $depth + 1);
$result = array_merge($result, $sub);
} else {
$result[] = $item->getPathname();
}
}
return $result;
}
$files = listFilesRecursive('/var/log');
print_r($files);Array
(
[0] => /var/log/syslog
[1] => /var/log/auth.log
// ... пропущены директории без прав
)Пример 2: Безопасная запись с блокировкой и обработкой ошибок блокировки
$filename = 'counter.txt';
$handle = fopen($filename, 'c+');
if (!$handle) {
throw new \RuntimeException('Не удалось открыть файл');
}
try {
if (!flock($handle, LOCK_EX)) {
throw new \RuntimeException('Не удалось получить эксклюзивную блокировку');
}
$counter = (int) fread($handle, 1024);
$counter++;
rewind($handle);
if (ftruncate($handle, 0) === false) {
throw new \RuntimeException('Ошибка усечения файла');
}
if (fwrite($handle, (string)$counter) === false) {
throw new \RuntimeException('Ошибка записи');
}
flock($handle, LOCK_UN);
} catch (\Throwable $e) {
// логируем и, возможно, повторяем
error_log('Counter error: ' . $e->getMessage());
throw $e;
} finally {
fclose($handle);
}(пустой вывод, если успешно; в лог запишется сообщение при ошибке)
Пример 3: Чтение удалённого файла с обработкой таймаута
$context = stream_context_create([
'http' => [
'timeout' => 5, // секунд
'method' => 'GET',
'ignore_errors' => true
]
]);
$url = 'https://example.com/data.json';
$result = @file_get_contents($url, false, $context);
if ($result === false) {
$error = error_get_last();
throw new \RuntimeException('Не удалось загрузить ' . $url . ': ' . $error['message']);
}
$data = json_decode($result, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \RuntimeException('Ошибка парсинга JSON: ' . json_last_error_msg());
}При успешном выполнении $data содержит массив; при ошибке таймаута - исключение с сообщением о таймауте.
Пример 4: Работа с CSV с проверкой целостности строк
$handle = fopen('data.csv', 'r');
if ($handle === false) {
throw new \RuntimeException('Не удалось открыть CSV');
}
try {
$headers = fgetcsv($handle);
if ($headers === false || count($headers) < 2) {
throw new \RuntimeException('Некорректный заголовок CSV');
}
$lineNumber = 1;
while (($row = fgetcsv($handle)) !== false) {
$lineNumber++;
if (count($row) !== count($headers)) {
error_log("Строка $lineNumber: неверное количество полей, пропуск");
continue;
}
// обработка строки
}
} finally {
fclose($handle);
}(логирование пропущенных строк, остальные обрабатываются)
Пример 5: Создание временного файла и гарантированное удаление
$tmpFile = tempnam(sys_get_temp_dir(), 'php_');
if ($tmpFile === false) {
throw new \RuntimeException('Не удалось создать временный файл');
}
try {
file_put_contents($tmpFile, $data);
// использование файла...
} finally {
if (file_exists($tmpFile) && !unlink($tmpFile)) {
error_log('Не удалось удалить временный файл: ' . $tmpFile);
}
}(временный файл существует в системе до удаления)
Пример 6: Использование stream wrapper для кастомной обработки ошибок
class ErrorLogStream {
private $handle;
public function stream_open($path, $mode, $options, &$opened_path) {
$this->handle = fopen('/dev/null', 'w'); // заглушка
return true;
}
public function stream_write($data) {
// вместо записи пишем в лог
error_log('Ошибка записи в поток: ' . $data);
return strlen($data);
}
}
stream_wrapper_register('errorlog', 'ErrorLogStream');
$stream = fopen('errorlog://dummy', 'w');
if ($stream) {
fwrite($stream, 'Это сообщение будет залогировано');
fclose($stream);
}(в логе появится строка 'Ошибка записи в поток: Это сообщение будет залогировано')