Файловые операции в PHP: как избежать и обработать ошибки ввода вывода

Раздел: Разработка на 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.

Типичные проблемы: игнорирование проверки is_readable/is_writable, забывание закрыть файл в блоке finally, использование несуществующего пути.

Вариант 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.

Проблемы: подавление warning может скрыть другие ошибки; error_get_last не сработает, если уже была ошибка.

Вариант 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)

Риск: если не проверять fread, можно получить неполные данные без уведомления.

Вариант 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)

Не все методы SplFileObject генерируют исключения, например fgets может вернуть false. Требуется дополнительная проверка.

Вариант 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 - для больших файлов увеличить лимит или читать частями.
- Try files php (try при работе с файлами php)
- Php no such file or directory (php: ошибка 'no such file or directory')
- Warning php page (предупреждение php)

Расширенные примеры работы с файлами и обработка ошибок

Пример 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);
}
(в логе появится строка 'Ошибка записи в поток: Это сообщение будет залогировано')

Ошибки файлового ввода-вывода в PHP - comments

En
Php file error (php)