Сохранение содержимого средствами PHP: от основ до продвинутых техник
Основные способы записи данных в PHP
Как быстро и просто записать строку в файл?
Наиболее эффективное решение - использование функции file_put_contents(). Она открывает файл, записывает данные и закрывает его за один вызов. Не требуется явно управлять указателем или ресурсом.
<?php
$filename = 'log.txt';
$data = 'Новая запись в лог';
file_put_contents($filename, $data);
?>
По умолчанию файл перезаписывается. Для дописывания используется флаг FILE_APPEND:
<?php
file_put_contents($filename, $data, FILE_APPEND);
?>
Функция возвращает количество записанных байт или false при ошибке. Полезна для протоколирования, кэширования и создания конфигураций.
- Отсутствие прав на запись в директорию (Warning: failed to open stream). Решение: проверить права через
is_writable()или настроить chmod. - Файл уже открыт другим процессом (блокировка не используется). Для одновременной записи используйте ручную блокировку через
flock()или атомарную замену (см. примеры). - Некорректная обработка многобайтовых строк. При записи
file_put_contentsработает с байтами, поэтому для Unicode убедитесь, что исходная строка имеет нужную кодировку (например, UTF-8).
Как записать данные с сохранением предыдущего содержимого и возможностью блокировки?
Используйте fopen() + flock() + fwrite() + fclose(). Это даёт полный контроль над процессом.
<?php
$filename = 'data.txt';
$handle = fopen($filename, 'a'); // 'a' для дописывания, 'w' для перезаписи
if (flock($handle, LOCK_EX)) {
fwrite($handle, "Дополнительная строка\n");
flock($handle, LOCK_UN);
}
fclose($handle);
?>
Режим 'a' устанавливает указатель в конец файла. Флаг LOCK_EX предотвращает одновременную запись из разных процессов. Подходит для высоконагруженных логов и очередей.
- Забытая блокировка может привести к взаимоблокировке (deadlock). Всегда снимайте блокировку.
- На NFS блокировка может не работать. Альтернатива - атомарная запись через временный файл.
Как записать данные в формате JSON или CSV с гарантией атомарности?
Для атомарной записи (другие процессы увидят только полностью записанный файл) используйте временный файл и rename().
<?php
$tmp = tempnam('/tmp', 'prefix_');
file_put_contents($tmp, json_encode($array, JSON_PRETTY_PRINT));
rename($tmp, 'data.json');
?>
Этот подход особенно полезен для конфигурационных файлов и кэша - исключает чтение частично записанных данных.
Как записать данные в нестандартные потоки, например, напрямую в буфер вывода или в удалённый файл по FTP?
PHP поддерживает обертки потоков (wrappers). Пример записи в stdout (браузер):
<?php
file_put_contents('php://output', "Строка для вывода\n");
?>
Запись по FTP:
<?php
$remote = 'ftp://user:pass@host/remote.txt';
file_put_contents($remote, 'Содержимое файла');
?>
Этот способ удобен для публикации файлов на удаленном сервере без сторонних библиотек.
Как записать данные с фильтрацией (например, сжатие или шифрование)?
Используйте потоковые фильтры через stream_filter_append().
<?php
$handle = fopen('output.txt', 'w');
stream_filter_append($handle, 'zlib.deflate', STREAM_FILTER_WRITE);
fwrite($handle, 'Текст для сжатия');
fclose($handle);
?>
Позволяет сжимать или шифровать данные на лету без записи промежуточного файла.
Расширенные примеры записи данных
1. Запись многобайтового текста с явной установкой кодировки
<?php
$text = "Привет, мир! © 2025";
// Принудительно кодируем в UTF-8 (если исходная строка в Windows-1251)
$text = mb_convert_encoding($text, 'UTF-8', 'Windows-1251');
file_put_contents('example_utf8.txt', $text);
?>
Файл example_utf8.txt содержит корректный UTF-8 текст. Без преобразования символы могли отображаться как вопросительные знаки.
2. Атомарная запись с возвратом предыдущего значения
<?php
function atomicWrite($file, $data) {
$tmp = tempnam(dirname($file), '.tmp');
if (file_put_contents($tmp, $data) === false) {
unlink($tmp);
return false;
}
// Rename - атомарная операция на одной файловой системе
if (rename($tmp, $file)) {
return true;
}
unlink($tmp);
return false;
}
$old = @file_get_contents('counter.txt') ?: 0;
$new = $old + 1;
if (atomicWrite('counter.txt', $new)) {
echo "Счётчик увеличен: $new";
}
?>
Счётчик увеличен: 42
3. Запись CSV с управлением разделителями и экранированием
<?php
$data = [
['Имя', 'Возраст', 'Город'],
['Анна', 25, 'Москва'],
['Пётр', 30, '"Санкт-Петербург"'],
];
$handle = fopen('users.csv', 'w');
foreach ($data as $row) {
fputcsv($handle, $row, ';', '"');
}
fclose($handle);
?>
Содержимое users.csv: Имя;Возраст;Город Анна;25;Москва Пётр;30;""Санкт-Петербург""
4. Запись в буфер вывода с последующим захватом
<?php
ob_start();
echo "Строка 1\n";
echo "Строка 2\n";
$content = ob_get_clean();
file_put_contents('output.log', $content, FILE_APPEND);
?>
В файл output.log добавлено: Строка 1 Строка 2
5. Запись через поток с пользовательским фильтром (преобразование регистра)
<?php
class UpperCaseFilter extends php_user_filter {
public function filter($in, $out, &$consumed, $closing) {
while ($bucket = stream_bucket_make_writeable($in)) {
$bucket->data = strtoupper($bucket->data);
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}
stream_filter_register('upper', UpperCaseFilter::class);
$handle = fopen('uppercase.txt', 'w');
stream_filter_append($handle, 'upper', STREAM_FILTER_WRITE);
fwrite($handle, 'hello world');
fclose($handle);
?>
Содержимое uppercase.txt: HELLO WORLD