Файловые базы данных на PHP: JSON, CSV, и другие форматы

Раздел: Разработка на PHP -> Работа с файловой системой

Хранение данных в файлах в PHP: плоские базы данных

Плоские базы данных (flat-file databases) представляют собой способ хранения структурированной информации в обычных текстовых файлах без использования специализированных СУБД. В PHP такой подход часто применяется для небольших проектов, конфигураций, кэширования или прототипов, когда не требуется высокая производительность транзакций и сложные запросы. Рассмотрим основные варианты реализации, их сильные стороны и типичные проблемы.

Наиболее эффективное решение: формат JSON

Вопрос: Как организовать компактное и читаемое хранение разнородных данных с возможностью простого преобразования в массивы PHP?

JSON (JavaScript Object Notation) обеспечивает лёгкость чтения как человеком, так и машиной, поддерживает вложенные структуры и встроенные функции PHP json_encode() и json_decode(). Этот метод подходит для конфигураций, небольших справочников, пользовательских сессий или кэша.

<?php
// Запись массива в JSON файл
$data = [
    'users' => [
        ['id' => 1, 'name' => 'Иван', 'email' => 'ivan@example.com'],
        ['id' => 2, 'name' => 'Мария', 'email' => 'maria@example.com']
    ]
];
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
file_put_contents('data.json', $json);

// Чтение и преобразование обратно
$content = file_get_contents('data.json');
$decoded = json_decode($content, true);
echo $decoded['users'][0]['name']; // Иван
?>

Php get extension (получение информации о файлах в php)

Возможные проблемы:

  • Конкурентный доступ без блокировки может привести к повреждению файла. Решение: использование flock() или атомарной записи через временный файл.
  • Большие файлы JSON медленно читаются целиком. При размерах свыше 10–20 МБ стоит рассмотреть другие подходы.
  • Ошибки кодировки: при отсутствии JSON_UNESCAPED_UNICODE русские символы превращаются в \uXXXX. Проблема решается указанием соответствующего флага.

Альтернативный вариант: формат CSV

Вопрос: Как хранить табличные данные с разделителями, совместимыми с электронными таблицами?

CSV удобен для экспорта/импорта и ручного редактирования. Используются функции fputcsv() и fgetcsv().

<?php
$users = [
    ['id', 'name', 'email'],
    [1, 'Иван', 'ivan@example.com'],
    [2, 'Мария', 'maria@example.com']
];

$fp = fopen('users.csv', 'w');
foreach ($users as $row) {
    fputcsv($fp, $row, ';', '"');
}
fclose($fp);

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

Index php file 1 (работа с файлами и папками на php)

Характерные ошибки:

  • Игнорирование экранирования: если данные содержат разделитель или кавычки, они должны корректно экранироваться; fputcsv делает это автоматически.
  • Различие в кодировках: CSV часто ожидается в UTF-8 с BOM, иначе Excel может отображать иероглифы.
  • Отсутствие строгой типизации: все значения попадают как строки.

Вариант: сериализованные объекты PHP

Вопрос: Как сохранить сложную структуру данных (включая объекты) с возможностью полного восстановления состояния?

Функции serialize() / unserialize() сохраняют любые типы PHP, включая ресурсы (кроме потоков). Подходит для кэширования объектов и быстрого обмена данными между скриптами.

<?php
class User {
    public $id;
    public $name;
    public function __construct($id, $name) {
        $this->id = $id;
        $this->name = $name;
    }
}

$user = new User(1, 'Иван');
$serialized = serialize($user);
file_put_contents('user.ser', $serialized);

// Восстановление
$restored = unserialize(file_get_contents('user.ser'));
echo $restored->name; // Иван
?>

Php запись в файл (запись в файл php)

Риски:

  • Внутренний формат не предназначен для чтения человеком и сильно привязан к версии PHP.
  • Опасность при использовании unserialize() на недоверенных данных: возможно выполнение произвольного кода (уязвимость). Никогда не передавайте эти данные от пользователя.

Вариант: собственный текстовый формат (ключ=значение)

Вопрос: Как создать простейшую базу с однотипными записями и быстрым поиском по ключу?

Подходит для конфигурационных файлов (аналог .ini) или простых словарей. Данные организуются как набор строк ключ:значение.

<?php
$conf = [
    'db_host' => 'localhost',
    'db_name' => 'test',
    'secret' => 'myKey'
];

$lines = [];
foreach ($conf as $key => $val) {
    $lines[] = $key . '=' . $val;
}
file_put_contents('config.txt', implode("\n", $lines));

// Парсинг
$pairs = file('config.txt', FILE_IGNORE_NEW_LINES);
$parsed = [];
foreach ($pairs as $line) {
    list($key, $val) = explode('=', $line, 2);
    $parsed[$key] = $val;
}
echo $parsed['db_host']; // localhost
?>

Php изменить файл (изменение файла в php)

Подводные камни:

  • Символ = в значении: нужно экранировать или использовать другой разделитель.
  • Невозможность хранения сложных вложенных данных без дополнительных соглашений.

Вариант: XML

Вопрос: Как обеспечить строгую валидацию и поддержку универсального обмена данными?

XML позволяет описывать иерархические структуры с атрибутами, имеет встроенную поддержку кодировок и широко используется в веб-сервисах. В PHP применяют SimpleXML или DOMDocument.

<?php
$xml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><users></users>');
$user1 = $xml->addChild('user');
$user1->addChild('id', 1);
$user1->addChild('name', 'Иван');
$xml->asXML('users.xml');

// Чтение
$loaded = simplexml_load_file('users.xml');
foreach ($loaded->user as $u) {
    echo $u->name;
}
?>

Недостатки:

  • Избыточность разметки приводит к большому объёму файла.
  • Парсинг больших документов потребляет много памяти.
  • Необходимость строгой обработки пространств имён и схем.

Выбор конкретного формата зависит от требований к читаемости, скорости доступа, необходимости конкурентной записи и типов данных. JSON является сбалансированным решением для большинства типовых задач. Для высоконагруженных систем рекомендуется всё же использовать специализированные СУБД.

- проверка файлов php (проверка файлов php)
- Php папка файла (папка файла php)
- Php размер файла (размер файла php)

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

Приведённые ниже примеры демонстрируют нестандартные ситуации и технические приёмы, которые редко встречаются в базовых учебниках, но полезны на практике.

Атомарная запись с помощью временного файла и rename

Гарантирует, что файл никогда не останется в повреждённом состоянии при обрыве записи. Это особенно важно при одновременном доступе нескольких скриптов.

Пример
<?php
$data = ['version' => '2.1', 'timestamp' => time()];
$tmpFile = tempnam(sys_get_temp_dir(), 'phpdb_');
file_put_contents($tmpFile, json_encode($data, JSON_UNESCAPED_UNICODE));
rename($tmpFile, 'config.json'); // атомарно на одной файловой системе
?>
Результат: файл config.json создаётся только после полной записи. Если процесс падает на записи, временный файл остаётся, а целевой - нет.

Конкурентное чтение с блокировкой flock

Используется для предотвращения чтения частично записанных данных. Рекомендуется комбинировать с атомарной записью.

Пример
<?php
$fp = fopen('data.json', 'r');
if (flock($fp, LOCK_SH)) {
    $content = fread($fp, filesize('data.json'));
    flock($fp, LOCK_UN);
}
fclose($fp);
$data = json_decode($content, true);
?>
Результат: после снятия блокировки данные гарантированно корректны (при условии атомарной записи).

Построчное чтение большого CSV файла с генератором

Позволяет обрабатывать файлы, размер которых превышает доступную память. Используется yield для создания итератора.

Пример
<?php
function csvGenerator($filename, $delimiter = ',') {
    $fp = fopen($filename, 'r');
    while (($line = fgetcsv($fp, 0, $delimiter)) !== FALSE) {
        yield $line;
    }
    fclose($fp);
}

foreach (csvGenerator('large.csv') as $index => $row) {
    if ($index > 10000) break; // обрабатываем первые 10000 строк
    echo $row[0] . "\n";
}
?>
Результат: скрипт использует минимальный объём памяти независимо от размера файла.

Создание индексного файла для быстрого поиска по ключу

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

Пример
<?php
// Запись данных с созданием индекса
$dataFile = fopen('records.txt', 'a');
$indexFile = fopen('index.txt', 'a');
$record = ['id' => 'user42', 'name' => 'Анна'];
$position = ftell($dataFile);
fwrite($dataFile, serialize($record) . PHP_EOL);
fwrite($indexFile, $record['id'] . ':' . $position . PHP_EOL);
fclose($dataFile);
fclose($indexFile);

// Поиск по индексу
$key = 'user42';
$idx = file('index.txt', FILE_IGNORE_NEW_LINES);
$pos = null;
foreach ($idx as $line) {
    list($k, $p) = explode(':', $line);
    if ($k === $key) { $pos = (int)$p; break; }
}
if ($pos !== null) {
    $fh = fopen('records.txt', 'r');
    fseek($fh, $pos);
    $data = unserialize(fgets($fh));
    fclose($fh);
    echo $data['name']; // Анна
}
?>
Результат: поиск по ключу выполняется быстрее, чем чтение всего файла record.txt, за счёт предварительного сканирования индекса (но при большом индексе всё равно потребуется прочитать его целиком; для ускорения можно хранить индекс в отсортированном виде).

Хранение нескольких таблиц в одном файле с разделителями разделов

Позволяет организовать «базу данных» в одном файле, где разные таблицы разделяются маркерами. Парсинг выполняется с учётом текущей секции.

Пример
<?php
$content = <<<END
[users]
id;name
1;Пётр
2;Елена
[orders]
id;product;amount
1;Ноутбук;1
2;Мышь;2
END;
file_put_contents('database.txt', $content);

// Парсинг
$sections = explode('[', $content);
array_shift($sections); // убираем пустой первый элемент
$db = [];
foreach ($sections as $section) {
    $lines = explode("\n", trim($section));
    $name = trim(array_shift($lines), ']');
    $headers = explode(';', array_shift($lines));
    $db[$name] = [];
    foreach ($lines as $line) {
        if (trim($line) === '') continue;
        $values = explode(';', $line);
        $db[$name][] = array_combine($headers, $values);
    }
}
print_r($db);
?>
Результат: ассоциативный массив, содержащий две таблицы. Такой подход удобен для небольших конфигурационных файлов с несколькими разделами.

Транзакции с помощью копирования и переименования

Имитация ACID для файлового хранилища: все изменения выполняются на копии, после успеха копия становится основной.

Пример
<?php
$original = 'data.json';
$backup = 'data_backup.json';
$temp = tempnam(sys_get_temp_dir(), 'tx_');

// 1. Копируем оригинал во временный файл (или начинаем изменения с нуля)
copy($original, $temp);

// 2. Выполняем операции (чтение, модификация, запись)
$current = json_decode(file_get_contents($temp), true);
$current['counter']++;
file_put_contents($temp, json_encode($current, JSON_UNESCAPED_UNICODE));

// 3. Если всё успешно – делаем копию на случай отката, затем переименовываем
rename($original, $backup); // сохраняем старую версию
rename($temp, $original);   // атомарно заменяем
?>
Результат: при любом сбое остаётся либо исходный файл, либо его резервная копия. Ни один шаг не приводит к потере всех данных.

Хранение данных в файлах в PHP (flat-file database) - comments

En
Php база в файле (php)