From 52de613b0ff983bcc763043fc24912fafe0c8910 Mon Sep 17 00:00:00 2001 From: babaev-an Date: Sun, 29 Jun 2025 20:29:52 +0300 Subject: [PATCH] 20250629 1.1 Stable --- sources/classes/JsonReWriter.md | 710 ++++++++++++++++++ sources/classes/JsonReWriter.php | 115 +-- sources/enums/JsonErrorCode.php | 2 +- .../JsonReWriter/JsonReWriterDeleteTrait.php | 69 ++ .../JsonReWriter/JsonReWriterKeyTrait.php | 129 ++++ .../JsonReWriterLoadSaveTrait.php | 56 ++ .../JsonReWriter/JsonReWriterReadTrait.php | 49 +- .../JsonReWriter/JsonReWriterWriteTrait.php | 21 + tests/classes/JsonReWriterTest.php | 296 +++++++- tests/data/C.php | 36 + 10 files changed, 1365 insertions(+), 118 deletions(-) create mode 100644 sources/classes/JsonReWriter.md create mode 100644 sources/traits/JsonReWriter/JsonReWriterDeleteTrait.php create mode 100644 sources/traits/JsonReWriter/JsonReWriterKeyTrait.php create mode 100644 sources/traits/JsonReWriter/JsonReWriterLoadSaveTrait.php create mode 100644 tests/data/C.php diff --git a/sources/classes/JsonReWriter.md b/sources/classes/JsonReWriter.md new file mode 100644 index 0000000..0263fa8 --- /dev/null +++ b/sources/classes/JsonReWriter.md @@ -0,0 +1,710 @@ +# Описание класса JsonReWriter + +## Информация о версии + +Версия класса: 1.0 + +Впервые введено в пакет с версии: 1.1.0 + +Описание класса: Класс для работы с JSON-файлами. + +## Публичные свойства и константы класса + +В классе определены следующие свойства: + +- `string $JsonString` - строка JSON (чтение/запись). + +## Быстрый старт + +### Правила формирования ключей. + +Ключ для чтения/записи данных класса формируется следующим образом: `ключ/подключ/подключ подключа` . + +Например, если дан json-файл: + + { + "test": { + "subtest": { + "AAA": "123", + "BBB": 1.23 + } + }, + "test1": { + "test": 123 + }, + "test2": { + "test": true + }, + "test3": { + "test": { + "res": "[1,2,3]" + } + } + } + +Пусть требуется получить значение `BBB`, тогда ключ должен быть `test/subtest/BBB`. + +### Чтение. + +1. Создайте элемент класса `$json = new JsonReWriter ();` +2. Если у вас требуется загрузить json из файла `$fileName`, то воспользуйтесь методом `LoadFromFile`: + `$json->LoadFromFile($fileName);`, если же json представлен строкой `$jsonStr`, то загрузите её в свойство + `$JsonString`: `$json->JsonString = $jsonStr;` +3. Далее можно пользоваться любым методом для чтения `Read*`, в том числе и общим `$json->Read(...)`. + +**Пример:** + + // Имя файла + $fileName = __DIR__ . "/test.json"; + // Создаю класс + $json = new JsonReWriter(); + // Загшружаю данные из файла + $json->LoadFromFile($fileName); + // Получаю число + $float = $json->ReadFloat("test/subtest/BBB", 0.2); + +В итоге в `$float` будет `1.23`. + +### Запись. + +1. Создайте элемент класса `$json = new JsonReWriter ();` +2. Далее можно пользоваться любым методом для записи `Write*`, в том числе и общим `$json->Write(...)`. +3. Если вам требуется создать json файл с именем `$fileName`, то воспользуйтесь методом `SaveToFile`: + `$json->SaveToFile($fileName);`, если же json должен быть представлен строкой `$jsonStr`, то загрузите её из свойства + `$JsonString`: `$jsonStr = $json->JsonString;` + +**Пример:** + + // Имя файла + $fileName = __DIR__ . "/test.json"; + // Создаю класс + $json = new JsonReWriter(); + // Загшружаю данные из файла + $json->LoadFromFile($fileName); + // Получаю число + try { + $json->Write("test/subtest/BBB", 0.2); + } + catch (JsonException $e) { + echo $e->getMessage(); + } + +В итоге в `test/subtest/BBB` вместо `1.23` будет `0.2`. + +## Коды ошибок JsonErrorCode + +Ниже представлена таблица основных кодов ошибки: + +| Ошибка | Описание | Введено с версии | +|:-------------------:|:---------------------------------------------------------------------------:|:----------------:| +| Unknown | Неизвестная ошибка | 1.0 | +| None | Ошибок нет | 1.0 | +| Depth | Достигнута максимальная глубина стека | 1.0 | +| StateMismatch | Неверный или некорректный JSON | 1.0 | +| CTRLChar | Ошибка управляющего символа, возможно, неверная кодировка | 1.0 | +| Syntax | Синтаксическая ошибка | 1.0 | +| UTF8 | Некорректные для кодировки UTF-8 символы, возможно, неверная кодировка | 1.0 | +| Recursion | Одна или несколько зацикленных ссылок в кодируемом значении | 1.0 | +| InfOrNan | Одно или несколько значений NAN или INF в кодируемом значении | 1.0 | +| UnsupportedType | Передали значение с неподдерживаемым типом | 1.0 | +| InvalidPropertyName | Имя свойства не может быть закодировано | 1.0 | +| UTF16 | Некорректный для кодировки UTF-16 символ, возможно, некорректно закодирован | 1.0 | +| KeyIsNotArray | Ключ не содержит вложений, хотя от него требуется обратное | 1.0 | +| NotISerializable | Класс не реализует интерфейс ISerializable | 1.0 | + +### Методы и функции перечисления JsonErrorCode + +Перечисление содержит **статический метод** `static function FromLastError (): JsonErrorCode`, который получает код +ошибки из последней JSON ошибки. + +Например, при успешной загрузке, можем проверить ошибки: + + $errors = JsonErrorCode::FromLastError (); + +После того, как мы выведем `$errors`, мы получим `JsonErrorCode::None`. + +## Исключение JsonException + +Исключение расширяет класс `Exception`, поэтому может выбрасываться через `throw`. + +### Свойства + +Исключение содержит следующие свойства: + +- `?string $JsonString` - строка JSON (или null); +- `JsonErrorCode $ErrorCode` - код ошибки JSON; +- `?string $ErrorMessage` - сообщение об ошибке JSON (в отличие от функции json_last_error_msg(), данная переменная при + отсутствии ошибок выводит null, а не "No error"). + +### Методы и функции + +#### Конструктор. + +Конструктор принимает **3 необязательных параметра**: + +* `?string $json` - строка JSON (по умолчанию, `null`); +* `JsonErrorCode $errorCode` - код ошибки (по умолчанию, `JsonErrorCode::Unknown`); +* `?string $errorMessage` - сообщение об ошибке (по умолчанию, `null`). + +В результате создаётся новое исключение `JsonException`. + +Пример: + + throw new JsonException("{}", JsonErrorCode::Depth, "Пример"); + +Создаст исключение `JsonException`. + +## Методы и функции + +### Конструктор и деструктор. + +Конструктор не принимает никаких параметров. + +В результате создаётся новый класс `JsonReWriter`. + +**Пример:** + + // Контсуктор + $json = new JsonReWriter (); + // Деструктор + unset($json); + +Создаст и уничтожит класс `JsonReWriter`. + +### Сохранение и загрузка из файла. + +За сохранение и загрузку отвечают 2 метода: `SaveToFile` и `LoadFromFile`. + +#### Сохранение в файл (метод `SaveToFile`) + +Этот метод сохраняет содержимое JSON в файл. Он содержит **1 обязательный параметр**: + +* `string $fileName` - имя файла на диске. + +Метод возвращает `bool` - сохранены ли данные в файл: `true` - да, `false` - нет. + +Синтаксис: + + public function SaveToFile (string $fileName): bool + +**Пример:** + + // Имя файла + $fileName = __DIR__ . "/test.json"; + // Создаём класс + $json = new JsonReWriter(); + // Заполним данными + try { + $json->Write("test/subtest/AAA", "123"); + $json->Write("test/subtest/BBB", 1.23); + $json->Write("test1/test", 123); + $json->Write("test2/test", true); + $json->WriteArray("test3/test/res", [1, 2, 3]); + } catch (JsonException $e) { + echo $e->getMessage(); + } + // Сохраняем созданный JSON файл + $json->SaveToFile($fileName); + +Содержимое файла `test.json` представлено далее: + + { + "test": { + "subtest": { + "AAA": "123", + "BBB": 1.23 + } + }, + "test1": { + "test": 123 + }, + "test2": { + "test": true + }, + "test3": { + "test": { + "res": "[1,2,3]" + } + } + } + +#### Загрузка файла (метод `LoadFromFile`) + +Этот метод загружает содержимое файла в класс. Он содержит **1 обязательный параметр**: + +* `string $fileName` - имя файла на диске. + +Метод возвращает `bool` - загружены ли данные из файла: `true` - да, `false` - нет. + +Синтаксис: + + public function LoadFromFile (string $fileName): bool + +**Пример:** +Пусть дан файл `test.json`, содержимое которого представлено далее: + + { + "test": { + "subtest": { + "AAA": "123", + "BBB": 1.23 + } + }, + "test1": { + "test": 123 + }, + "test2": { + "test": true + }, + "test3": { + "test": { + "res": "[1,2,3]" + } + } + } + +Следующий код загрузит это содержимое в класс: + + // Имя файла + $fileName = __DIR__ . "/test.json"; + // Создаём класс + $json = new JsonReWriter(); + // Загружаю данные + $json->LoadFromFile($fileName); + +### Чтение данных + +Для чтения данных используется один общий метод `Read` и 7 его производных метода: `ReadString`, `ReadInt`, `ReadFloat`, +`ReadBool`, `ReadArray`, `ReadObject` и `ReadSerializable`. + +#### Метод `Read` + +Это метод, который читает значение ключа JSON. Он имеет **1 обязательный параметр** `string $key` (ключ) и **1 +необязательный параметр** `mixed $default` (значение по умолчанию, задан по умолчанию в `null`). + +Этот метод возвращает `mixed`: значение ключа JSON или значение по умолчанию. + +Синтаксис: + + public function Read (string $key, mixed $default = null): mixed + +**Пример,** + + // Создаю класс + $json = new JsonReWriter(); + // Заполняю данными + try { + $json->Write("test/subtest/AAA", "123"); + $json->Write("test/subtest/BBB", 1.23); + $json->Write("test1/test", 123); + $json->Write("test2/test", true); + $json->Write("test3/test/res", json_encode([1, 2, 3])); + } catch (JsonException $e) { + echo $e->getMessage(); + } + + // Получаю значение + $float = (float)$json->Read("test/subtest/BBB")); + +В результате, переменная `$float` будет иметь значение `1.23`. + +#### Метод `ReadString` + +Это метод, который читает значение ключа JSON. Он имеет **1 обязательный параметр** `string $key` (ключ) и **1 +необязательный параметр** `string $default` (значение по умолчанию, задан по умолчанию в `""`). + +Этот метод возвращает `string`: значение ключа JSON или значение по умолчанию. + +Синтаксис: + + public function ReadString (string $key, string $default = ""): string + +#### Метод `ReadInt` + +Это метод, который читает значение ключа JSON. Он имеет **1 обязательный параметр** `string $key` (ключ) и **1 +необязательный параметр** `int $default` (значение по умолчанию, задан по умолчанию в `0`). + +Этот метод возвращает `int`: значение ключа JSON или значение по умолчанию. + +Синтаксис: + + public function ReadInt (string $key, int $default = 0): int + +#### Метод `ReadFloat` + +Это метод, который читает значение ключа JSON. Он имеет **1 обязательный параметр** `string $key` (ключ) и **1 +необязательный параметр** `float $default` (значение по умолчанию, задан по умолчанию в `0.0`). + +Этот метод возвращает `float`: значение ключа JSON или значение по умолчанию. + +Синтаксис: + + public function ReadFloat (string $key, float $default = 0.0): float + +#### Метод `ReadBool` + +Это метод, который читает значение ключа JSON. Он имеет **1 обязательный параметр** `string $key` (ключ) и **1 +необязательный параметр** `bool $default` (значение по умолчанию, задан по умолчанию в `false`). + +Этот метод возвращает `bool`: значение ключа JSON или значение по умолчанию. + +Синтаксис: + + public function ReadBool (string $key, bool $default = false): bool + +#### Метод `ReadArray` + +Это метод, который читает значение ключа JSON. Он имеет **1 обязательный параметр** `string $key` (ключ) и **1 +необязательный параметр** `array $default` (значение по умолчанию, задан по умолчанию в `[]`). + +Этот метод возвращает `array`: значение ключа JSON или значение по умолчанию. + +Синтаксис: + + public function ReadArray (string $key, array $default = []): array + +#### Метод `ReadObject` + +Это метод, который читает значение ключа JSON. Он имеет **2 обязательных параметра**: + +- `string $key` (ключ); +- `object $default` (значение по умолчанию). + +Этот метод возвращает `object`: значение ключа JSON или значение по умолчанию. + +Синтаксис: + + public function ReadObject (string $key, object $default): object + +#### Метод `ReadSerializable` + +Это метод, который читает значение ключа JSON. Он имеет **2 обязательных параметра**: + +- `string $key` (ключ); +- `string $serializableClassName` (имя класса, реализующего интерфейс ISerializable, с namespace). + +Этот метод возвращает класс, реализующий интерфейс `ISerializable`. + +**Важно!** Этот метод может выбросить исключение `JsonException`, если класс, заданный в `$serializableClassName` не +реализует интерфейс `ISerializable`. + +Синтаксис: + + public function ReadSerializable (string $key, string $serializableClassName): ISerializable + +**Пример,** + +Пусть для примера, класс `Demo` из namespace `iam\namespace` реализует интерфейс `ISerializable`. + + // Создаю класс + $json = new JsonReWriter(); + // ... Здесь где-то загрузка данных + // Получаю класс + try { + /** + * @var Demo $unSerializableClass + */ + $unSerializableClass = $json->ReadSerializable("test", "iam\\namespace\\Demo"); + } catch (JsonException $e) { + echo $e->getMessage(); + } + +В результате, переменная `$unSerializableClass` будет содержать данные класса `Demo`. + +### Запись данных + +Для чтения данных используется один общий метод `Write` и 3 его производных метода: `WriteArray`, `WriteObject` и +`WriteSerializable`. + +**Важно!** Лобой из вышеуказанных методов может выбросить исключение `JsonException`, если ключ не содержит вложений, +хотя от него требуется обратное. + +#### Метод `Write` + +Это метод, который записывает значение в ключ JSON. Он имеет **2 обязательных параметра**: + +- `string $key` (ключ); +- `mixed $value` (записываемое значение). + +Этот метод ничего не возвращает. + +Синтаксис: + + public function Write (string $key, mixed $value): void + +**Пример,** + + // Имя файла + $fileName = __DIR__ . "/test.json"; + // Создаём класс + $json = new JsonReWriter(); + // Заполним данными + try { + $json->Write("test/subtest/AAA", "123"); + $json->Write("test/subtest/BBB", 1.23); + $json->Write("test1/test", 123); + $json->Write("test2/test", true); + $json->WriteArray("test3/test/res", [1, 2, 3]); + } catch (JsonException $e) { + echo $e->getMessage(); + } + // Сохраняем созданный JSON файл + $json->SaveToFile($fileName); + +Содержимое файла `test.json` представлено далее: + + { + "test": { + "subtest": { + "AAA": "123", + "BBB": 1.23 + } + }, + "test1": { + "test": 123 + }, + "test2": { + "test": true + }, + "test3": { + "test": { + "res": "[1,2,3]" + } + } + } + +#### Метод `WriteArray` + +Это метод, который записывает значение в ключ JSON. Он имеет **2 обязательных параметра**: + +- `string $key` (ключ); +- `array $array` (записываемое значение). + +Этот метод ничего не возвращает. + +Синтаксис: + + public function WriteArray (string $key, array $array): void + +#### Метод `WriteObject` + +Это метод, который записывает значение в ключ JSON. Он имеет **2 обязательных параметра**: + +- `string $key` (ключ); +- `object $value` (записываемое значение). + +Этот метод ничего не возвращает. + +Синтаксис: + + public function WriteObject (string $key, object $value): void + +#### Метод `WriteSerializable` + +Это метод, который записывает значение в ключ JSON. Он имеет **2 обязательных параметра**: + +- `string $key` (ключ); +- `ISerializable $serializableValue` (записываемое значение). + +Этот метод ничего не возвращает. + +Синтаксис: + + public function WriteSerializable (string $key, ISerializable $serializableValue): void + +**Пример,** +Пусть для примера, класс `Demo` из namespace `iam\namespace` реализует интерфейс `ISerializable`. + + // Имя файла + $fileName = __DIR__ . "/test.json"; + // Создаём класс + $json = new JsonReWriter(); + // Создаём класс Demo + $serializableClass = new Demo(...); + // Заполним данными + try { + $json->WriteSerializable("test", $serializableClass); + } catch (JsonException $e) { + echo $e->getMessage(); + } + ... + +### Работа с ключами JSON + +Для работы с ключами JSON есть 2 метода: `IsKeyExists` и `GetKeys`. + +#### Метод `IsKeyExists` + +Это метод проверяем наличие ключа в JSON. Он имеет **1 обязательный параметр** - `string $key` (ключ). + +Этот метод возвращает `bool`: `true` если ключ найден и `false` в противном случае. + +Синтаксис: + + public function IsKeyExists (string $key): bool + +**Пример,** + + // Создаём класс + $json = new JsonReWriter(); + // Заполним данными + try { + $json->Write("test/subtest/AAA", "123"); + $json->Write("test/subtest/BBB", 1.23); + $json->Write("test1/test", 123); + $json->Write("test2/test", true); + $json->WriteArray("test3/test/res", [1, 2, 3]); + } catch (JsonException $e) { + echo $e->getMessage(); + } + // Проверяем ключи + $check1 = $json->IsKeyExists("test/subtest/AAA"); + $check2 = $json->IsKeyExists("test/subtest/ССС"); + +В результате, `$check1` будет `true`, а `$check2` - `false`. + +#### Метод `GetKeys` + +Это метод получает список ключей. Он имеет **2 необязательных параметра**: + +- `string $parentKey` (ключ родителя (или "" (установлено по умолчанию) для всех); +- `bool $includeChildren` (нужно ли включать дочерние ключи (по умолчанию, да)). + +Этот метод возвращает `array`: список ключей. + +Синтаксис: + + public function GetKeys (string $parentKey = "", bool $includeChildren = true): array + +**Пример,** + + // Создаём класс + $json = new JsonReWriter(); + // Заполним данными + try { + $json->Write("test/subtest/AAA", "123"); + $json->Write("test/subtest/BBB", 1.23); + $json->Write("test1/test", 123); + $json->Write("test2/test", true); + $json->WriteArray("test3/test/res", [1, 2, 3]); + } catch (JsonException $e) { + echo $e->getMessage(); + } + // Получаем ключи + $keys1 = $json->GetKeys("test"); + $keys2 = $json->GetKeys("test", false); + $keys3 = $json->GetKeys(); + +В результате, `$key1` будет следующим массивом: + + [ + "test/subtest", + "test/subtest/AAA", + "test/subtest/BBB" + ] + +`$key2` будет следующим массивом: + + [ + "subtest" + ] + +`$key3` будет следующим массивом: + + [ + "test", + "test/subtest", + "test/subtest/AAA", + "test/subtest/BBB", + "test1", + "test1/test", + "test2", + "test2/test", + "test3", + "test3/test", + "test3/test/res" + ] + +### Удаление ключей JSON + +Для удаления ключей JSON есть 2 метода: `DeleteKey`, который удаляет только определённый ключ и `Clear`, который удаляет +**все ключи** из json-файла. + +#### Метод `DeleteKey` + +Это метод удаляет только определённый ключ в JSON. Он имеет **1 обязательный параметр** - `string $key` (ключ). + +Этот метод возвращает `bool` - результат удаления ключа: `true` - удаление прошло успешно, `false` - произошла ошибка +при удалении. + +Синтаксис: + + public function DeleteKey (string $key): bool + +**Пример,** + + // Создаём класс + $json = new JsonReWriter(); + // Заполним данными + try { + $json->Write("test/subtest/AAA", "123"); + $json->Write("test/subtest/BBB", 1.23); + $json->Write("test1/test", 123); + $json->Write("test2/test", true); + $json->WriteArray("test3/test/res", [1, 2, 3]); + } catch (JsonException $e) { + echo $e->getMessage(); + } + // Получаемем ключи до удаления + $check1 = $json->GetKeys("test/subtest"); + // Удаляем ключ + $this->DeleteKey("test/subtest/BBB"); + // Получаемем ключи после удаления + $check2 = $json->GetKeys("test/subtest"); + +В результате, `$check1` будет + + [ + "test/subtest", + "test/subtest/AAA", + "test/subtest/BBB" + ] + +а `$check2`: + + [ + "test/subtest", + "test/subtest/AAA" + ] + +#### Метод `Clear` + +Это метод удаляет **все ключи** из json-файла. Он не имеет никаких параметров и ничего не возвращает. + +Синтаксис: + + public function Clear (): void + +**Пример,** + + // Создаём класс + $json = new JsonReWriter(); + // Заполним данными + try { + $json->Write("test/subtest/AAA", "123"); + $json->Write("test/subtest/BBB", 1.23); + $json->Write("test1/test", 123); + $json->Write("test2/test", true); + $json->WriteArray("test3/test/res", [1, 2, 3]); + } catch (JsonException $e) { + echo $e->getMessage(); + } + // Очищаем + $json->Clear(); + // Получаем ключи + $keys = $json->GetKeys(); + +В результате, `$key` будет следующим массивом: + + [ + ] \ No newline at end of file diff --git a/sources/classes/JsonReWriter.php b/sources/classes/JsonReWriter.php index 49e7011..50e1d53 100644 --- a/sources/classes/JsonReWriter.php +++ b/sources/classes/JsonReWriter.php @@ -4,6 +4,9 @@ namespace goodboyalex\php_components_pack\classes; use goodboyalex\php_components_pack\enums\JsonErrorCode; use goodboyalex\php_components_pack\exceptions\JsonException; +use goodboyalex\php_components_pack\traits\JsonReWriter\JsonReWriterDeleteTrait; +use goodboyalex\php_components_pack\traits\JsonReWriter\JsonReWriterKeyTrait; +use goodboyalex\php_components_pack\traits\JsonReWriter\JsonReWriterLoadSaveTrait; use goodboyalex\php_components_pack\traits\JsonReWriter\JsonReWriterReadTrait; use goodboyalex\php_components_pack\traits\JsonReWriter\JsonReWriterWriteTrait; @@ -22,6 +25,11 @@ final class JsonReWriter */ public string $JsonString { get { + // Проверка на пустоту + if (count($this->JsonData) === 0) + // - если массив пуст, возвращаем пустой JSON + return '{}'; + // Преобразую данные в JSON $json = json_encode($this->JsonData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); @@ -65,107 +73,18 @@ final class JsonReWriter unset($this->JsonData); } - /** - * Сохраняем JSON в файл. - * - * @param string $fileName Имя файла. - * - * @return bool Сохранены ли данные в файл: true - да, false - нет. - */ - public function SaveToFile (string $fileName): bool - { - // Запись данных в файл - return file_put_contents($fileName, $this->JsonString) !== false; - } - - /** - * Загрузка данных из JSON-файла. - * - * @param string $fileName Имя файла. - * - * @return bool Загрузились ли данные из файла: true - да, false - нет. - */ - public function LoadFromFile (string $fileName): bool - { - // Проверка существования файла - if (!is_file($fileName)) - // - если нет, возвращаем false - return false; - - // Чтение содержимого файла - $result = file_get_contents($fileName); - - // Проверка на ошибки - if ($result === false) - // - если есть ошибки, возвращаем false - return false; - - // Записываем результат - $this->JsonString = $result; - - // Возвращаем true, если все хорошо - return true; - } - - /** - * Проверяем наличие ключа в JSON. - * - * @param string $key Ключ. - * - * @return bool true если ключ найден, false если нет. - */ - public function IsKeyExists (string $key): bool - { - // Получаем массив ключей по вложенности - $keys = $this->ParseKey($key); - - // Получаем текущий массив данных - $current = $this->JsonData; - - // Для каждого ключа - foreach ($keys as $key) { - // - проверяем наличие ключа в текущем массиве - if (!array_key_exists($key, $current)) - // - нет? Возвращаем false - return false; - - // Переходим ко вложенному массиву - $current = $current[$key]; - } - - // Возвращаем true, если все ключи найдены - return true; - } - - /** - * Получение ключей по вложенности. Т.е., ключи вида "key1/key2/key3" => ["key1", "key2", "key3"]. - * - * @param string $key Ключ. - * - * @return array Ключи по вложенности. - */ - private function ParseKey (string $key): array - { - return explode('/', $key); - } - - /** - * Создание ключа в JSON. - * - * @param string $key Ключ. - * @param mixed $value Значение. - * - * @return void - * @throws JsonException Если ключ не является массивом. - */ - private function CreateKey (string $key, mixed $value): void - { - - } - // Подключаем методы чтения JSON use JsonReWriterReadTrait; // Подключаем методы записи JSON use JsonReWriterWriteTrait; + + // Подключаем методы сохранения и загрузки JSON + use JsonReWriterLoadSaveTrait; + + // Подключаем методы работы с ключами + use JsonReWriterKeyTrait; + + // Подключаем методы удаления данных из JSON + use JsonReWriterDeleteTrait; } \ No newline at end of file diff --git a/sources/enums/JsonErrorCode.php b/sources/enums/JsonErrorCode.php index 88f7ea9..2348c12 100644 --- a/sources/enums/JsonErrorCode.php +++ b/sources/enums/JsonErrorCode.php @@ -70,7 +70,7 @@ enum JsonErrorCode: int /** * Имя свойства не может быть закодировано. */ - case INVALID_PROPERTY_NAME = 9; + case InvalidPropertyName = 9; /** * Некорректный для кодировки UTF-16 символ, возможно, некорректно закодирован. diff --git a/sources/traits/JsonReWriter/JsonReWriterDeleteTrait.php b/sources/traits/JsonReWriter/JsonReWriterDeleteTrait.php new file mode 100644 index 0000000..b8c0e8b --- /dev/null +++ b/sources/traits/JsonReWriter/JsonReWriterDeleteTrait.php @@ -0,0 +1,69 @@ +JsonData); + + // Создание пустого массива + $this->JsonData = []; + } + + /** + * Удаление ключа JSON. + * + * @param string $key Требуемый ключ JSON для удаления. + * + * @return bool Результат удаления ключа: true - удаление прошло успешно, false - + * произошла ошибка при удалении. + */ + public function DeleteKey (string $key): bool + { + // Очищаем ключ + $preparedKey = $this->PrepareKey($key); + + // Проверка ключа + if (!$this->IsKeyExists($preparedKey)) + // - ключ не существует + return false; + + // Разбиваем ключ на части + $keys = $this->ParseKey($preparedKey); + + // Получаем текущий массив данных + $current = &$this->JsonData; + + // Если ключ не является корневым + if (count($keys) > 0) + // - переходим к вложенному массиву + for ($i = 0; $i < count($keys) - 1; $i++) + // -- и добавляем массив данных + $current = &$current[$keys[$i]]; + + // Получаем удаляемый ключ + $deleteKey = $keys[count($keys) - 1]; + + // Удаляем ключ + unset($current[$deleteKey]); + + // Проверяем удаление + return $this->IsKeyExists($key); + } +} \ No newline at end of file diff --git a/sources/traits/JsonReWriter/JsonReWriterKeyTrait.php b/sources/traits/JsonReWriter/JsonReWriterKeyTrait.php new file mode 100644 index 0000000..d3fd41c --- /dev/null +++ b/sources/traits/JsonReWriter/JsonReWriterKeyTrait.php @@ -0,0 +1,129 @@ +ParseKey($key); + + // Получаем текущий массив данных + $current = $this->JsonData; + + // Для каждого ключа + foreach ($keys as $key) { + // - проверяем наличие ключа в текущем массиве + if (!array_key_exists($key, $current)) + // - нет? Возвращаем false + return false; + + // Переходим ко вложенному массиву + $current = $current[$key]; + } + + // Возвращаем true, если все ключи найдены + return true; + } + + /** + * Получение ключей по вложенности. Т.е., ключи вида "key1/key2/key3" => ["key1", "key2", "key3"]. + * + * @param string $key Ключ. + * + * @return array Ключи по вложенности. + */ + private function ParseKey (string $key): array + { + // Очищаем ключ + $key = $this->PrepareKey($key); + + // Разбиваем ключ на части + return explode('/', $key); + } + + /** + * Подготавливает ключ к использованию внутри методов. + * + * @param string $key Неочищенный ключ. + * + * @return string Очищенный ключ. + */ + private function PrepareKey (string $key): string + { + return trim($key, "/ "); + } + + /** + * Получение списка ключей. + * + * @param string $parentKey Ключ родителя (или "" (установлено по умолчанию) для всех). + * @param bool $includeChildren Нужно ли включать дочерние ключи (по умолчанию, да). + * + * @return array Список ключей. + */ + public function GetKeys (string $parentKey = "", bool $includeChildren = true): array + { + // Очищаем ключ + $parentKey = $this->PrepareKey($parentKey); + + // Разбиваем ключ на части + $keys = StringExtension::IsNullOrWhitespace($parentKey) ? [] : $this->ParseKey($parentKey); + + // Получаем текущий массив данных + $current = $this->JsonData; + + // Если ключ не является корневым + if (count($keys) > 0) + // - переходим к вложенному массиву + for ($i = 0; $i < count($keys); $i++) + // -- и добавляем массив данных + $current = $current[$keys[$i]]; + + // Получаем список ключей родителя + $parentKeysList = array_keys($current); + + // Если не нужно включать дочерние ключи + if (!$includeChildren) + // - возвращаем список родительских ключей + return $parentKeysList; + + // Создаем результирующий массив + $result = []; + + // Для каждого ключа + foreach ($parentKeysList as $key) { + // - очищаем текущий ключ + $currentKey = $this->PrepareKey($parentKey . "/" . $key); + + // - добавляем его в результирующий массив + $result[] = $currentKey; + + // - если у текущего ключа есть дочерние ключи + if (is_array($current[$key])) + // -- добавляем их в результирующий массив + $result = array_merge($result, $this->GetKeys($currentKey)); + } + + // Возвращаем результирующий массив + return $result; + } +} \ No newline at end of file diff --git a/sources/traits/JsonReWriter/JsonReWriterLoadSaveTrait.php b/sources/traits/JsonReWriter/JsonReWriterLoadSaveTrait.php new file mode 100644 index 0000000..52e27e7 --- /dev/null +++ b/sources/traits/JsonReWriter/JsonReWriterLoadSaveTrait.php @@ -0,0 +1,56 @@ +true - да, false - нет. + */ + public function SaveToFile (string $fileName): bool + { + // Запись данных в файл + return file_put_contents($fileName, $this->JsonString) !== false; + } + + /** + * Загрузка данных из JSON-файла. + * + * @param string $fileName Имя файла. + * + * @return bool Загрузились ли данные из файла: true - да, false - нет. + */ + public function LoadFromFile (string $fileName): bool + { + // Проверка существования файла + if (!is_file($fileName)) + // - если нет, возвращаем false + return false; + + // Чтение содержимого файла + $result = file_get_contents($fileName); + + // Проверка на ошибки + if ($result === false) + // - если есть ошибки, возвращаем false + return false; + + // Записываем результат + $this->JsonString = $result; + + // Возвращаем true, если все хорошо + return true; + } +} \ No newline at end of file diff --git a/sources/traits/JsonReWriter/JsonReWriterReadTrait.php b/sources/traits/JsonReWriter/JsonReWriterReadTrait.php index 5590b5e..1cdc8cc 100644 --- a/sources/traits/JsonReWriter/JsonReWriterReadTrait.php +++ b/sources/traits/JsonReWriter/JsonReWriterReadTrait.php @@ -39,6 +39,9 @@ trait JsonReWriterReadTrait */ public function Read (string $key, mixed $default = null): mixed { + // Подготавливаем ключ + $key = $this->PrepareKey($key); + // Проверяем, существует ли ключ if (!$this->IsKeyExists($key)) // - если нет, то возвращаем значение по умолчанию @@ -53,7 +56,7 @@ trait JsonReWriterReadTrait // Для каждого ключа до предпоследнего for ($i = 0; $i < count($keys) - 1; $i++) // - переходим ко вложенному массиву - $current = &$current[$keys[$i]]; + $current = $current[$keys[$i]]; // Возвращаем значение последнего ключа и если его нет, то возвращаем значение по умолчанию return $current[$keys[count($keys) - 1]] ?? $default; @@ -95,7 +98,27 @@ trait JsonReWriterReadTrait */ public function ReadArray (string $key, array $default = []): array { - return (array)$this->Read($key, $default); + // Получаем значение ключа JSON + $serializedDef = json_encode($default); + + // Читаем значение ключа JSON + $value = $this->ReadString($key, $serializedDef); + + // Десериализуем значение ключа JSON + return json_decode($value, true); + } + + /** + * Читает значение ключа JSON как строку. + * + * @param string $key Ключ JSON. + * @param string $default Значение по умолчанию. + * + * @return string Значение ключа JSON или значение по умолчанию. + */ + public function ReadString (string $key, string $default = ""): string + { + return (string)$this->Read($key, $default); } /** @@ -108,7 +131,14 @@ trait JsonReWriterReadTrait */ public function ReadObject (string $key, object $default): object { - return (object)$this->Read($key, $default); + // Получаем значение ключа JSON + $serializedDef = json_encode($default); + + // Читаем значение ключа JSON + $value = $this->ReadString($key, $serializedDef); + + // Десериализуем значение ключа JSON + return json_decode($value); } /** @@ -140,17 +170,4 @@ trait JsonReWriterReadTrait // Возвращаем объект return $instance; } - - /** - * Читает значение ключа JSON как строку. - * - * @param string $key Ключ JSON. - * @param string $default Значение по умолчанию. - * - * @return string Значение ключа JSON или значение по умолчанию. - */ - public function ReadString (string $key, string $default = ""): string - { - return (string)$this->Read($key, $default); - } } \ No newline at end of file diff --git a/sources/traits/JsonReWriter/JsonReWriterWriteTrait.php b/sources/traits/JsonReWriter/JsonReWriterWriteTrait.php index 6d2f206..0ceaaf0 100644 --- a/sources/traits/JsonReWriter/JsonReWriterWriteTrait.php +++ b/sources/traits/JsonReWriter/JsonReWriterWriteTrait.php @@ -41,6 +41,9 @@ trait JsonReWriterWriteTrait */ public function Write (string $key, mixed $value): void { + // Подготавливаем ключ + $key = $this->PrepareKey($key); + // Получаем массив ключей по вложенности $keys = $this->ParseKey($key); @@ -85,4 +88,22 @@ trait JsonReWriterWriteTrait // Записываем в ключ $this->Write($key, $serialized); } + + /** + * Записывает массив в ключ JSON. + * + * @param string $key Ключ JSON. + * @param array $array Массив для записи. + * + * @return void + * @throws JsonException Ключ не содержит вложений, хотя от него требуется обратное. + */ + public function WriteArray (string $key, array $array): void + { + // Сериализуем массив + $serialized = json_encode($array); + + // Записываем в ключ + $this->Write($key, $serialized); + } } \ No newline at end of file diff --git a/tests/classes/JsonReWriterTest.php b/tests/classes/JsonReWriterTest.php index f415271..e067d99 100644 --- a/tests/classes/JsonReWriterTest.php +++ b/tests/classes/JsonReWriterTest.php @@ -2,28 +2,318 @@ namespace goodboyalex\php_components_pack\tests\classes; +use goodboyalex\php_components_pack\classes\File; use goodboyalex\php_components_pack\classes\JsonReWriter; +use goodboyalex\php_components_pack\exceptions\JsonException; +use goodboyalex\php_components_pack\tests\data\A; +use goodboyalex\php_components_pack\tests\data\C; use PHPUnit\Framework\TestCase; class JsonReWriterTest extends TestCase { - public function testSaveToFile () + public function testIsKeyExists () { $this->PrepareForTest(); $json = new JsonReWriter(); - $json->Write("test/subtest/AAA", "123"); - $json->SaveToFile(__DIR__ . '/test.json'); + try { + $json->Write("test/subtest/AAA", "123"); + } + catch (JsonException $e) { + echo $e->getMessage(); + } + + $this->assertTrue($json->IsKeyExists("test/subtest/AAA")); } private function PrepareForTest (): void { require_once __DIR__ . '/../../sources/exceptions/JsonException.php'; + require_once __DIR__ . '/../../sources/traits/JsonReWriter/JsonReWriterDeleteTrait.php'; + require_once __DIR__ . '/../../sources/traits/JsonReWriter/JsonReWriterKeyTrait.php'; + require_once __DIR__ . '/../../sources/traits/JsonReWriter/JsonReWriterLoadSaveTrait.php'; + require_once __DIR__ . '/../../sources/traits/JsonReWriter/JsonReWriterReadTrait.php'; + require_once __DIR__ . '/../../sources/traits/JsonReWriter/JsonReWriterWriteTrait.php'; require_once __DIR__ . '/../../sources/classes/JsonReWriter.php'; + require_once __DIR__ . '/../data/A.php'; + require_once __DIR__ . '/../data/C.php'; + } + + public function testReadWriteInt () + { + $this->PrepareForTest(); + + $json = new JsonReWriter(); + try { + $json->Write("test/test", 123); + } + catch (JsonException $e) { + echo $e->getMessage(); + } + + $this->assertEquals(123, $json->ReadInt("test/test")); + } + + public function testReadWriteBool () + { + $this->PrepareForTest(); + + $json = new JsonReWriter(); + try { + $json->Write("test/test1", false); + $json->Write("test/test2", true); + } + catch (JsonException $e) { + echo $e->getMessage(); + } + + $this->assertTrue($json->ReadBool("test/test2")); + $this->assertFalse($json->ReadBool("test/test1")); + } + + public function testReadWriteString () + { + $this->PrepareForTest(); + + $json = new JsonReWriter(); + try { + $json->Write("test/test", "test string"); + } + catch (JsonException $e) { + echo $e->getMessage(); + } + + $this->assertEquals("test string", $json->ReadString("test/test", "test this")); + } + + public function testSaveToFile () + { + $this->PrepareForTest(); + + $fileName = __DIR__ . "/test.json"; + + if (file_exists($fileName)) + unlink($fileName); + + $json = new JsonReWriter(); + + try { + $json->Write("test/subtest/AAA", "123"); + $json->Write("test/subtest/BBB", 1.23); + $json->Write("test1/test", 123); + $json->Write("test2/test", true); + $json->WriteArray("test3/test/res", [1, 2, 3]); + } + catch (JsonException $e) { + echo $e->getMessage(); + } + + $json->SaveToFile($fileName); + + $this->assertFileExists($fileName); + + $size = File::FileSize($fileName)->Value; + + $this->assertEquals(268, $size); + + unlink($fileName); + } + + public function testReadWriteArray () + { + $this->PrepareForTest(); + + $json = new JsonReWriter(); + + try { + $json->Write("test/subtest/AAA", "123"); + $json->Write("test/subtest/BBB", 1.23); + $json->Write("test1/test", 123); + $json->Write("test2/test", true); + $json->WriteArray("test3/test/res", [1, 2, 3]); + } + catch (JsonException $e) { + echo $e->getMessage(); + } + + $this->assertArrayIsEqualToArrayIgnoringListOfKeys([1, 2, 3], $json->ReadArray("test3/test/res"), []); + } + + public function testClear () + { + $this->PrepareForTest(); + + $json = new JsonReWriter(); + + try { + $json->Write("test/subtest/AAA", "123"); + $json->Write("test/subtest/BBB", 1.23); + $json->Write("test1/test", 123); + $json->Write("test2/test", true); + $json->WriteArray("test3/test/res", [1, 2, 3]); + } + catch (JsonException $e) { + echo $e->getMessage(); + } + + $json->Clear(); + + $this->assertCount(0, $json->GetKeys()); + } + + public function testReadWriteObject () + { + $this->PrepareForTest(); + + $json = new JsonReWriter(); + + $class = new A("test", 123, true); + + try { + $json->WriteObject("test", $class); + } + catch (JsonException $e) { + echo $e->getMessage(); + } + + $this->assertEquals("test", $json->ReadObject("test", new A())->a); } public function testLoadFromFile () { + $this->PrepareForTest(); + $fileName = __DIR__ . "/test.json"; + + if (file_exists($fileName)) + unlink($fileName); + + $json = new JsonReWriter(); + + try { + $json->Write("test/subtest/AAA", "123"); + $json->Write("test/subtest/BBB", 1.23); + $json->Write("test1/test", 123); + $json->Write("test2/test", true); + $json->WriteArray("test3/test/res", [1, 2, 3]); + } + catch (JsonException $e) { + echo $e->getMessage(); + } + + $json->SaveToFile($fileName); + + unset($json); + + $json = new JsonReWriter(); + + $json->LoadFromFile($fileName); + + unlink($fileName); + + $this->assertEquals(123, $json->ReadInt("test1/test")); + } + + public function testDeleteKey () + { + $this->PrepareForTest(); + + $json = new JsonReWriter(); + + try { + $json->Write("test/subtest/AAA", "123"); + $json->Write("test/subtest/BBB", 1.23); + $json->Write("test1/test", 123); + $json->Write("test2/test", true); + $json->WriteArray("test3/test/res", [1, 2, 3]); + } + catch (JsonException $e) { + echo $e->getMessage(); + } + + $json->DeleteKey("test3/test/res"); + + $this->assertFalse($json->IsKeyExists("test3/test/res")); + } + + public function testReadWrite () + { + $this->PrepareForTest(); + + $json = new JsonReWriter(); + try { + $json->Write("test/subtest/AAA", "123"); + $json->Write("test/subtest/BBB", 1.23); + $json->Write("test1/test", 123); + $json->Write("test2/test", true); + $json->Write("test3/test/res", json_encode([1, 2, 3])); + } + catch (JsonException $e) { + echo $e->getMessage(); + } + + $this->assertTrue($json->IsKeyExists("test/subtest/AAA")); + $this->assertTrue($json->IsKeyExists("test1/test")); + $this->assertEquals(1.23, (float)$json->Read("test/subtest/BBB")); + } + + public function testGetKeys () + { + $this->PrepareForTest(); + + $json = new JsonReWriter(); + try { + $json->Write("test/subtest/AAA", "123"); + $json->Write("test/subtest/BBB", 1.23); + $json->Write("test1/test", 123); + $json->Write("test2/test", true); + $json->WriteArray("test3/test/res", [1, 2, 3]); + } + catch (JsonException $e) { + echo $e->getMessage(); + } + + $this->assertCount(11, $json->GetKeys()); + $this->assertCount(2, $json->GetKeys("test/subtest")); + } + + public function testReadWriteSerializable () + { + $this->PrepareForTest(); + + $serializableClass = new C("test", 123, true); + + $json = new JsonReWriter(); + try { + $json->WriteSerializable("test", $serializableClass); + + /** + * @var C $unSerializableClass Получаем объект из файла + */ + $unSerializableClass = $json->ReadSerializable("test", "goodboyalex\\php_components_pack\\tests\\data\\C"); + } + catch (JsonException $e) { + echo $e->getMessage(); + return; + } + + $this->assertEquals($serializableClass->stringC, $unSerializableClass->stringC); + $this->assertEquals($serializableClass->intC, $unSerializableClass->intC); + $this->assertEquals($serializableClass->boolC, $unSerializableClass->boolC); + } + + public function testReadWriteFloat () + { + $this->PrepareForTest(); + + $json = new JsonReWriter(); + try { + $json->Write("test", 1.23); + } + catch (JsonException $e) { + echo $e->getMessage(); + } + + $this->assertEquals(1.23, $json->ReadFloat("test", 0.2)); } } \ No newline at end of file diff --git a/tests/data/C.php b/tests/data/C.php new file mode 100644 index 0000000..790355e --- /dev/null +++ b/tests/data/C.php @@ -0,0 +1,36 @@ +stringC = $string; + $this->intC = $int; + $this->boolC = $bool; + } + + public function Serialize (): string + { + $array = []; + $array["string"] = $this->stringC; + $array["int"] = $this->intC; + $array["bool"] = $this->boolC; + return json_encode($array); + } + + public function UnSerialize (string $serialized): void + { + $array = json_decode($serialized, true); + $this->stringC = $array["string"]; + $this->intC = $array["int"]; + $this->boolC = $array["bool"]; + } +} \ No newline at end of file