Как записать массив в JSON-файл на PHP: encode, атомарная запись и ошибки

Нужно сохранить массив или конфиг в JSON-файл на PHP — и потом прочитать обратно без сюрпризов с кодировкой, правами и частично записанным файлом. Ниже рабочий минимум: json_encode / json_decode, атомарная запись, флаги и типичные ошибки. Для Битрикс рядом полезны Loader / ModuleManager (если JSON лежит в модуле) и кеш через TaggedCache, когда файл читают на каждом хите.

Базовая запись массива в JSON

$cars = ['lada', 'ford', 'bmw'];

$json = json_encode(
    $cars,
    JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT
);

if ($json === false) {
    throw new RuntimeException('json_encode failed: ' . json_last_error_msg());
}

file_put_contents(__DIR__ . '/config.json', $json, LOCK_EX);

JSON_UNESCAPED_UNICODE оставляет кириллицу читаемой. LOCK_EX снижает риск гонки при параллельной записи. Без проверки json_encode === false легко затереть файл пустотой при невалидных данных (например, NAN или рекурсии).

Чтение и обновление

$path = __DIR__ . '/config.json';
$raw = file_get_contents($path);
if ($raw === false) {
    throw new RuntimeException("Cannot read $path");
}

$data = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
$data[] = 'lada kalina';

$tmp = $path . '.tmp';
file_put_contents(
    $tmp,
    json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
    LOCK_EX
);
rename($tmp, $path); // атомарная замена на том же разделе

Второй аргумент true у json_decode даёт ассоциативный массив. JSON_THROW_ON_ERROR (PHP 7.3+) бросает JsonException вместо тишины. Схема «писать во временный файл → rename» не оставляет читателям обрезанный JSON при обрыве процесса.

Ассоциативный конфиг

$config = [
    'env' => 'prod',
    'features' => ['new_checkout' => true, 'cache' => true],
    'limits' => ['rpm' => 120],
];

file_put_contents(
    __DIR__ . '/app.json',
    json_encode($config, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
);

Для продакшена чаще хранят секреты вне репозитория (env / vault), а в JSON — только несекретные флаги. В Битрикс настройки модуля лучше класть в Config\Option, а не в произвольный файл в upload/.

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

  • Кириллица как \uXXXX — добавьте JSON_UNESCAPED_UNICODE.
  • Пустой файл после записи — не проверили json_encode / права на каталог.
  • Предупреждение «Invalid UTF-8» — входные строки не в UTF-8; нормализуйте до encode.
  • Гонка двух воркеров — LOCK_EX + запись через tmp + rename.
  • json_decode вернул null — отличите валидный JSON null от ошибки через json_last_error() или JSON_THROW_ON_ERROR.

Когда не нужен JSON-файл

Частые хиты лучше отдают в Redis/кеш ядра, а не в file_get_contents на каждый запрос. Большие наборы данных — в БД (ORM). JSON на диске уместен для редких конфигов, экспорта и отладки.

Итого: encode с флагами → проверка ошибки → атомарная запись. Так массив в JSON-файле переживает и кириллицу, и параллельные PHP-FPM воркеры.