Переменные и файлы в PHP: полное руководство

Раздел: Программирование на PHP -> Программирование на PHP

Переменные для работы с файлами в PHP: обзор методов

В PHP переменные часто используются для хранения путей к файлам, их содержимого или управляющих флагов. Выбор подходящего способа чтения и записи влияет на производительность, безопасность и читаемость кода. Ниже приведены основные подходы с примерами и типичными ошибками.

Как прочитать весь файл в переменную и записать переменную в файл?

Наиболее простое и эффективное решение для небольших файлов - использование функций file_get_contents() и file_put_contents(). Они работают с целым файлом за один вызов, поддерживают блокировки и флаги.


<?php
$filename = 'config.json';
$data = file_get_contents($filename); // чтение всего файла в строку
if ($data === false) {
    // обработка ошибки
    die('Невозможно прочитать файл');
}

$newData = json_decode($data, true);
$newData['version'] = '2.0';
$json = json_encode($newData, JSON_PRETTY_PRINT);

$result = file_put_contents($filename, $json, LOCK_EX);
if ($result === false) {
    // ошибка записи
    die('Ошибка записи в файл');
}
?>

Пояснение: file_get_contents возвращает содержимое в виде строки или false при ошибке. file_put_contents записывает строку в файл, флаг LOCK_EX обеспечивает эксклюзивную блокировку. Возвращает количество записанных байт или false.

Типичные ошибки:

  • Проблема: Файл не существует или нет прав на чтение. Решение: Проверять существование через file_exists() и права is_readable(). Либо использовать исключения (если включено их выбрасывание).
  • Проблема: Файл занят другим процессом. Решение: Использовать LOCK_EX при записи и предусмотреть повтор при блокировке.
  • Проблема: Неверная кодировка при работе с JSON. Решение: Указывать опции JSON_UNESCAPED_UNICODE.

Цели использования: конфигурационные файлы, логи, кэш, текстовые данные небольшого размера. Подходит когда файл можно целиком загрузить в память.

Как записать данные в файл частями или в цикле?

Когда нужно записывать данные постепенно (например, логирование), удобно использовать пару fopen() + fwrite() + fclose().


<?php
$logFile = 'app.log';
$handle = fopen($logFile, 'a'); // режим 'a' - дописывание в конец
if ($handle === false) {
    die('Не удалось открыть файл');
}

foreach ($logs as $entry) {
    fwrite($handle, date('Y-m-d H:i:s') . ' ' . $entry . PHP_EOL);
}

fclose($handle);
?>

Пояснение: fopen открывает файл и возвращает ресурс. Режимы: 'w' (удалить и записать), 'a' (дописать), 'r' (чтение). Важно закрыть ресурс fclose() для освобождения системы.

Типичные ошибки:

  • Проблема: Забыли закрыть файл. Решение: Использовать конструкцию try...finally или обертку, гарантирующую закрытие.
  • Проблема: Отсутствие прав на запись. Решение: Проверять с помощью is_writable().
  • Проблема: Файл открыт в неверном режиме (например, 'r' вместо 'a'). Решение: Явно указывать режим в соответствии с задачей.

Цели использования: ведение журналов, скачивание больших файлов, создание отчётов построчно.

Как работать с файлом в объектно-ориентированном стиле?

Класс SplFileObject предоставляет ООП-интерфейс для работы с файлами, включая итерацию по строкам, запись и произвольный доступ.


<?php
$file = new SplFileObject('data.csv', 'r+');
$file->setFlags(SplFileObject::READ_CSV); // режим парсинга CSV

foreach ($file as $row) {
    // $row - массив полей строки
    if ($row[0] == '1') {
        $row[1] = 'Изменено';
        $file->fputcsv($row); // записываем изменённую строку
        break;
    }
}
?>

Пояснение: SplFileObject наследует Iterator, позволяя использовать в foreach. Флаги настройки упрощают парсинг CSV, JSON не требует дополнительных функций.

Типичные ошибки:

  • Проблема: Невозможность получить доступ к файлу при открытии. Решение: Обернуть создание объекта в try-catch, так как выбрасывается RuntimeException.
  • Проблема: Путаница с позицией указателя при чтении и записи одновременно. Решение: Использовать fseek() или ftell().

Цели использования: обработка CSV-файлов, сложные манипуляции с позицией, итеративный разбор больших файлов.

Как сохранить массив данных в формате CSV и прочитать его?

Функции fputcsv() и fgetcsv() предназначены для корректной записи и чтения CSV-строк с учётом экранирования.


<?php
$data = [
    ['name', 'email', 'age'],
    ['Иван Петров', 'ivan@example.com', 30],
    ['Анна Сидорова', 'anna@example.com', 25]
];

$handle = fopen('users.csv', 'w');
foreach ($data as $row) {
    fputcsv($handle, $row, ';', '"'); // разделитель ';'
}
fclose($handle);

// Чтение
$handle = fopen('users.csv', 'r');
while (($row = fgetcsv($handle, 0, ';', '"')) !== false) {
    print_r($row);
}
fclose($handle);
?>

Пояснение: fputcsv автоматически экранирует поля, содержащие разделитель или кавычки. Параметр 0 означает без ограничения длины строки.

Типичные ошибки:

  • Проблема: Несоответствие разделителя. Решение: Всегда указывать один и тот же разделитель при записи и чтении.
  • Проблема: Неверная кодировка (Windows-1251 вместо UTF-8). Решение: Приводить данные к utf-8 через mb_convert_encoding().

Цели использования: экспорт/импорт табличных данных, обмен с Excel.

Как сериализовать данные в JSON и сохранить в файл?

Для хранения структурированных данных в современном формате используется JSON в связке с file_put_contents() и file_get_contents().


<?php
$config = [
    'db' => ['host' => 'localhost', 'port' => 3306],
    'debug' => false
];

$json = json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
$written = file_put_contents('config.json', $json, LOCK_EX);
if ($written === false) {
    error_log('Failed to write config');
}

// Чтение
$raw = file_get_contents('config.json');
$restored = json_decode($raw, true);
print_r($restored);
?>

Пояснение: json_decode с параметром true возвращает ассоциативный массив, без него - объект stdClass. Флаги JSON_THROW_ON_ERROR помогают отлавливать ошибки декодирования.

Типичные ошибки:

  • Проблема: json_decode возвращает null при ошибке (в версиях до 7.3). Решение: Использовать json_last_error() или включить JSON_THROW_ON_ERROR.
  • Проблема: Большие файлы занимают всю память. Решение: Использовать построчную запись/чтение или SplFileObject.

Цели использования: конфигурации, API-ответы, сериализация состояния.

Как загрузить содержимое удаленного файла через URL?

При включенной директиве allow_url_fopen можно читать файлы по протоколам HTTP, FTP и другим с помощью file_get_contents().


<?php
$url = 'https://api.example.com/data.json';
$context = stream_context_create([
    'http' => [
        'method' => 'GET',
        'header' => "User-Agent: MyApp/1.0\r\n"
    ]
]);

$response = file_get_contents($url, false, $context);
if ($response === false) {
    $error = error_get_last();
    die('Ошибка загрузки: ' . $error['message']);
}

$data = json_decode($response, true);
?>

Пояснение: stream_context_create позволяет задать HTTP-заголовки, таймауты, метод запроса. Без контекста можно прочитать простые URL.

Типичные ошибки:

  • Проблема: allow_url_fopen отключен в php.ini для безопасности. Решение: Использовать cURL в качестве альтернативы.
  • Проблема: Медленная загрузка крупных файлов. Решение: Установить таймаут через контекст: 'timeout' => 10.

Цели использования: загрузка внешних данных, веб-скрапинг, вызов REST API.

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

Пример 1. Безопасная запись с блокировкой и обновлением по транзакции

Пример

<?php
$file = 'counter.txt';

$fp = fopen($file, 'c+'); // 'c+' - открыть файл, создать если нет, не усекать
if (flock($fp, LOCK_EX)) { // эксклюзивная блокировка
    $content = fread($fp, filesize($file) ?: 1);
    $count = (int)trim($content) + 1;
    ftruncate($fp, 0); // очищаем файл
    rewind($fp);
    fwrite($fp, $count);
    fflush($fp); // принудительная запись
    flock($fp, LOCK_UN); // снятие блокировки
}
fclose($fp);
echo "Текущее значение счетчика: $count";
?>
Текущее значение счетчика: 42

Пояснение: Режим c+ позволяет читать и писать без удаления содержимого при открытии. Блокировка flock предотвращает состояние гонки. ftruncate и rewind гарантируют перезапись с начала.

Пример 2. Чтение большого лог-файла построчно с фильтрацией

Пример

<?php
$largeFile = 'server.log';
$handle = fopen($largeFile, 'r');
if (!$handle) {
    die('Не удалось открыть файл');
}

$errors = [];
while (($line = fgets($handle)) !== false) {
    if (stripos($line, 'ERROR') !== false) {
        $errors[] = $line;
    }
    // Можно ограничить количество строк для экономии памяти
    if (count($errors) > 100) break;
}
fclose($handle);

file_put_contents('errors_sample.log', implode('', $errors), LOCK_EX);
echo 'Собрано ' . count($errors) . ' строк ошибок.';
?>
Собрано 100 строк ошибок.

Пояснение: fgets() читает одну строку, не загружая весь файл в память. Подходит для логов размером в гигабайты. Фильтрация выполняется на лету.

Пример 3. Рекурсивный обход директории и запись списка файлов в переменную

Пример

<?php
$directory = '/path/to/project';
$iterator = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS)
);

$fileList = [];
foreach ($iterator as $file) {
    if ($file->isFile()) {
        $fileList[] = $file->getPathname();
    }
}

file_put_contents('file_list.txt', implode("\n", $fileList));
echo 'Сохранили ' . count($fileList) . ' файлов.';
?>
Сохранили 2147 файлов.

Пояснение: RecursiveDirectoryIterator без флага SKIP_DOTS включает записи '.' и '..'. Полученный массив можно обработать или записать в файл.

Пример 4. Потоковая загрузка удаленного файла с записью на диск без загрузки в память

Пример

<?php
$url = 'https://example.com/large.zip';
$local = 'download.zip';

$src = fopen($url, 'rb');
$dest = fopen($local, 'wb');

if ($src && $dest) {
    while (!feof($src)) {
        $chunk = fread($src, 8192);
        fwrite($dest, $chunk);
    }
    fclose($dest);
    fclose($src);
    echo 'Файл загружен.';
} else {
    echo 'Ошибка открытия.';
}
?>
Файл загружен.

Пояснение: Функции fopen для URL работают только при allow_url_fopen. Потоковая передача кусками по 8 КБ позволяет экономить оперативную память при загрузке больших файлов.

Пример 5. Синхронизация данных между файлами с помощью блокировок

Пример

<?php
$file1 = 'state1.txt';
$file2 = 'state2.txt';

$fp1 = fopen($file1, 'r+');
$fp2 = fopen($file2, 'r+');

// Захватываем блокировки в одинаковом порядке во избежание deadlock
flock($fp1, LOCK_EX);
flock($fp2, LOCK_EX);

$data1 = fread($fp1, filesize($file1));
$data2 = fread($fp2, filesize($file2));

// Обмен значениями
rewind($fp1);
ftruncate($fp1, 0);
fwrite($fp1, $data2);

rewind($fp2);
ftruncate($fp2, 0);
fwrite($fp2, $data1);

flock($fp1, LOCK_UN);
flock($fp2, LOCK_UN);
fclose($fp1);
fclose($fp2);
echo 'Данные обменяны.';
?>
Данные обменяны.

Пояснение: При работе с несколькими файлами важно блокировать их в одном порядке, чтобы избежать взаимоблокировок (deadlock). После операций все ресурсы закрываются.

Переменная и файл в PHP - comments

En
переменную файл php (php)