diff --git a/.gitignore b/.gitignore
index b13c1aa..0bb7d32 100644
--- a/.gitignore
+++ b/.gitignore
@@ -78,4 +78,5 @@ fabric.properties
.idea/caches/build_file_checksums.ser
.idea/
-vendor/
\ No newline at end of file
+vendor/
+/tests/extensions/class.txt
diff --git a/sources/extensions/TypeExtension.md b/sources/extensions/TypeExtension.md
new file mode 100644
index 0000000..1152ae9
--- /dev/null
+++ b/sources/extensions/TypeExtension.md
@@ -0,0 +1,179 @@
+# Описание класса TypeExtension
+
+## Информация о версии
+
+Версия класса: 1.0
+
+Впервые введено в пакет с версии: 1.1.1
+
+Описание класса: Расширение для любого типа.
+
+## Публичные свойства и константы класса
+
+К публичным свойствам можно отнести следующие статичные функции: `DEFAULT_TO_ARRAY_ON_CLASS` и
+`DEFAULT_FROM_ARRAY_ON_CLASS`. Они описывают методы обработки классов по умолчанию для методов `ToArray` и `FromArray`
+соответственно.
+
+### Метод `DEFAULT_TO_ARRAY_ON_CLASS`
+
+Этот метод возвращает `Closure`, который можно использовать (и он используется при значении параметра `null`) во втором
+параметре метода `ToArray`.
+
+### Метод `DEFAULT_FROM_ARRAY_ON_CLASS`
+
+Этот метод возвращает `Closure`, который можно использовать (и он используется при значении параметра `null`) во втором
+параметре метода `FromArray`.
+
+## Методы и функции
+
+### Метод `ToArray`
+
+Этот _статический_ метод переводит класс в массив. Он содержит **1 обязательный параметр**:
+
+* `object $class` - класс, который нужно перевести в массив
+
+и **1 необязательный параметр**:
+
+* `?callable $onClass = null` - метод обработки классов. Если передаётся `null`, то используется
+ `DEFAULT_TO_ARRAY_ON_CLASS`. По умолчанию, `null`.
+
+В качестве метода обработки классов `onClass` подойдет любое `Closure` вида
+
+ fn (object $class): array => <ДЕЙСТВИЕ>;
+
+Метод возвращает `array` - массив публичных свойств.
+
+Синтаксис:
+
+ public static function ToArray (object $class, ?callable $onClass = null): array
+
+Пример,
+
+ // Создаём тестовый класс (см. TypeExtensionTest)
+ $class = new D (
+ 'test_string',
+ 12345,
+ true,
+ new A("test_string_A", 6789, false),
+ new B("test_string_B", 9876, "false"),
+ new C("test_string_C", 54321, true)
+ );
+
+ // Создаём метод обработки классов
+ // - воспользуемся тем, что класс C реализует интерфейс ISerialize
+ $c_closure = fn (C $class): array =>
+ [
+ "type_class" => C::class,
+ "type_value" => $class->Serialize()
+ ];
+
+ // - зададим метод обработки
+ $closure = fn (object $class): array => $class instanceof C
+ ? $c_closure($class)
+ : TypeExtension::ToArray($class);
+
+ // Преобразуем в массив
+ try {
+ $array = TypeExtension::ToArray($class, $closure);
+ }
+ catch (TypeException $e) {
+ // - если ошибка, то выводим её и завершаем работу
+ die($e->getMessage());
+ }
+
+ // Вывожу в файл
+ file_put_contents(__DIR__ . "/class.txt", json_encode($array, JSON_PRETTY_PRINT));
+
+В результате содержимое файла:
+
+ {
+ "type_class": "goodboyalex\\php_components_pack\\tests\\data\\D",
+ "stringD": "test_string",
+ "intD": 12345,
+ "boolD": true,
+ "a": {
+ "type_class": "goodboyalex\\php_components_pack\\tests\\data\\A",
+ "a": "test_string_A",
+ "b": 6789,
+ "c": false
+ },
+ "b": {
+ "type_class": "goodboyalex\\php_components_pack\\tests\\data\\B",
+ "a": "test_string_B",
+ "b": 9876,
+ "d": "false"
+ },
+ "c": {
+ "type_class": "goodboyalex\\php_components_pack\\tests\\data\\C",
+ "type_value": "{\"string\":\"test_string_C\",\"int\":54321,\"bool\":true}"
+ }
+ }
+
+#### Выбрасываемые исключения
+
+Этот метод выбрасывает исключение типа `TypeException` в случае, если объект `$class` (обязательный параметр метода) не является классом.
+
+### Метод `FromArray`
+
+Этот _статический_ метод переводит массив в класс. Он содержит **1 обязательный параметр**:
+
+* `array $array` - массив публичных свойств
+
+и **1 необязательный параметр**:
+
+* `?callable $onClass = null` - метод обработки классов. Если передаётся `null`, то используется
+ `DEFAULT_FROM_ARRAY_ON_CLASS`. По умолчанию, `null`.
+
+В качестве метода обработки классов `onClass` подойдет любое `Closure` вида
+
+ fn (array $classArray): object => <ДЕЙСТВИЕ>;
+
+Метод возвращает `object` - экземпляр класса.
+
+Синтаксис:
+
+ public static function FromArray (array $array, ?callable $onClass = null): object
+
+Пример,
+
+ // Считываем данные из файла и преобразуем в массив
+ $array = json_decode(file_get_contents(__DIR__ . "/class.txt"), true);
+
+ // Создаём метод обработки классов
+ // - воспользуемся тем, что класс C реализует интерфейс ISerialize
+ $c_closure = function (string $serializedC): C
+ {
+ $classC = new C();
+ $classC->UnSerialize($serializedC);
+ return $classC;
+ };
+
+ // - зададим метод обработки
+ $closure = fn (array $classArray): object =>
+ $classArray['type_class'] == C::class
+ ? $c_closure($classArray["type_value"])
+ : TypeExtension::FromArray($classArray);
+
+ // Преобразуем в объект
+ try {
+ $class = TypeExtension::FromArray($array, D::CLOSURE_FROM_ARRAY());
+ } catch (TypeException $e) {
+ // - если ошибка, то выводим её и завершаем работу
+ die($e->getMessage());
+ }
+
+ // Вывожу тестовую строку
+ echo $class->c->stringC;
+
+В результате на экране отобразится:
+
+ test_string_C
+
+#### Выбрасываемые исключения
+
+Этот метод выбрасывает исключение типа `TypeException` в случаях, если:
+
+* массив `$array` не создан через метод `ToArray`;
+* при попытке создать объект, он не создаётся;
+* дата передана не корректного формата;
+* пытается передать `null` не `nullable` типу.
\ No newline at end of file
diff --git a/sources/extensions/TypeExtension.php b/sources/extensions/TypeExtension.php
index 72156cf..ce37bf0 100644
--- a/sources/extensions/TypeExtension.php
+++ b/sources/extensions/TypeExtension.php
@@ -28,20 +28,24 @@ final class TypeExtension
*/
public static function DEFAULT_TO_ARRAY_ON_CLASS (): Closure
{
- return fn (object $class) => self::ToArray($class, self::DEFAULT_TO_ARRAY_ON_CLASS());
+ return fn (object $class): array => self::ToArray($class);
}
/**
* Переводит объект в массив.
*
* @param object $class Объект.
- * @param callable $onClass Метод обработки классов.
+ * @param callable|null $onClass Метод обработки классов. Если передаётся null, то используется
+ * DEFAULT_TO_ARRAY_ON_CLASS
. По умолчанию, null.
*
* @return array Массив свойств типа.
* @throws TypeException Исключение, если объект не является классом.
*/
- public static function ToArray (object $class, callable $onClass): array
+ public static function ToArray (object $class, ?callable $onClass = null): array
{
+ // Проверяю, что метод обработки классов не null и если null, то присваиваю дефолтный метод
+ $onClass ??= self::DEFAULT_TO_ARRAY_ON_CLASS();
+
// Создаю массив результата
$result = [];
@@ -102,16 +106,28 @@ final class TypeExtension
return $result;
}
+ /**
+ * Дефолтный метод обработки классов для FromArray.
+ *
+ * @return Closure Метод обработки классов для FromArray.
+ */
+ public static function DEFAULT_FROM_ARRAY_ON_CLASS (): Closure
+ {
+ return fn (array $classArray): object => self::FromArray($classArray);
+ }
+
/**
* Переводит массив в объект.
*
* @param array $array Массив свойств.
+ * @param callable|null $onClass Метод обработки классов. Если передаётся null, то используется
+ * DEFAULT_FROM_ARRAY_ON_CLASS
. По умолчанию, null.
*
- * @return mixed Объект типа $className.
+ * @return mixed Восстановленный объект.
* @throws TypeException Исключение, если массив не создан через ToArray, объект не создан, дата не корректного
* формата или пытается передать null не nullable типу.
*/
- public function FromArray (array $array): object
+ public static function FromArray (array $array, ?callable $onClass = null): object
{
// Проверяю, что массив является массивом, созданным через ToArray, то есть содержит ключ type_class
if (!array_key_exists("type_class", $array))
@@ -186,7 +202,7 @@ final class TypeExtension
}
// - если значение является NULL
- if ($value = "null") {
+ if (is_string($value) && $value == "null") {
// -- присваиваю NULL
try {
$instance->$key = null;
@@ -203,7 +219,7 @@ final class TypeExtension
// - если значение является классом
if (is_object($instance->$key) && is_array($array[$key]) && array_key_exists("type_class", $array[$key])) {
// -- добавляю в массив через рекурсию
- $instance->$key = self::FromArray($array[$key]);
+ $instance->$key = $onClass($array[$key]);
// -- следующий элемент
continue;
diff --git a/tests/data/D.php b/tests/data/D.php
new file mode 100644
index 0000000..6047a5e
--- /dev/null
+++ b/tests/data/D.php
@@ -0,0 +1,56 @@
+stringD = $string;
+ $this->intD = $int;
+ $this->boolD = $bool;
+
+ $this->a = $a ?? new A();
+ $this->b = $b ?? new B();
+ $this->c = $c ?? new C();
+ }
+
+ public static function CLOSURE_FROM_ARRAY (): Closure
+ {
+ return fn (array $classArray): object
+ => $classArray['type_class'] == C::class
+ ? self::CLOSURE_FROM_FOR_C($classArray["type_value"]) : TypeExtension::FromArray($classArray);
+ }
+
+ private static function CLOSURE_FROM_FOR_C (string $serializedC): object
+ {
+ $classC = new C();
+ $classC->UnSerialize($serializedC);
+ return $classC;
+ }
+
+ public static function CLOSURE_TO_ARRAY (): Closure
+ {
+ return fn (object $class): array => $class instanceof C ? self::CLOSURE_TO_FOR_C($class)
+ : TypeExtension::ToArray($class);
+ }
+
+ private static function CLOSURE_TO_FOR_C (C $class): array
+ {
+ return [
+ "type_class" => C::class,
+ "type_value" => $class->Serialize()
+ ];
+ }
+}
\ No newline at end of file
diff --git a/tests/data/MenuCssClassModel.php b/tests/data/MenuCssClassModel.php
deleted file mode 100644
index 850b9cc..0000000
--- a/tests/data/MenuCssClassModel.php
+++ /dev/null
@@ -1,39 +0,0 @@
-Id ?? GUIDExtension::GUIDEmpty;
- }
- set {
- // Проверка идентификатора
- if (!GUIDExtension::Validate($value))
- // - исключение
- throw new Exception("Неверный идентификатор (GUID)");
-
- // Установка идентификатора
- $this->Id = $value;
- }
- }
-
- /**
- * @var string $Name Имя элемента.
- */
- public string $Name;
-
- /**
- * @var string|null $Description Описание элемента.
- */
- public ?string $Description;
-
- /**
- * @var string $URL Адрес URL элемента.
- */
- public string $URL;
-
- /**
- * @var bool $OpenInNewWindow Открывать ли в новом окне (добавлять к ссылке target="_blank").
- */
- public bool $OpenInNewWindow;
-
- /**
- * @var string|null $IconClass Класс иконки.
- */
- public ?string $IconClass;
-
- /**
- * @var string $ParentId Идентификатор родительского элемента.
- */
- public string $ParentId {
- get {
- return $this->ParentId ?? GUIDExtension::GUIDEmpty;
- }
- set {
- // Проверка идентификатора
- if (!GUIDExtension::Validate($value))
- // - исключение
- throw new Exception("Неверный идентификатор (GUID)");
-
- // Установка идентификатора
- $this->ParentId = $value;
- }
- }
-
- /**
- * @var int $Order Порядок.
- */
- public int $Order;
-
- /**
- * @inheritdoc
- */
- public function Duplicate (): MenuItemModel
- {
- // Создание дубликата модели
- $model = new MenuItemModel();
-
- // Копирование свойств
- ClassMapper::MapClass($this, $model);
-
- // Возврат дубликата
- return $model;
- }
-}
\ No newline at end of file
diff --git a/tests/data/MenuItems.php b/tests/data/MenuItems.php
deleted file mode 100644
index 9309650..0000000
--- a/tests/data/MenuItems.php
+++ /dev/null
@@ -1,555 +0,0 @@
-_items = is_array($items) ? new ObjectArray($items) : $items;
- }
-
- /**
- * Добавляет список элементов.
- *
- * @param ObjectArray|array $items Список элементов меню.
- *
- * @return void
- */
- public function AddItems (ObjectArray|array $items): void
- {
- // Получаю список
- $itemsToAdd = is_array($items) ? new ObjectArray($items) : $items;
-
- /**
- * @var MenuItemModel $item Элемент меню
- */
- foreach ($itemsToAdd as $item)
- // - добавляю элемент
- $this->AddItem($item);
- }
-
- /**
- * Добавляет элемент меню в список.
- *
- * @param MenuItemModel $item Элемент меню.
- *
- * @return void
- */
- public function AddItem (MenuItemModel $item): void
- {
- // Добавляю
- $this->_items[] = $item;
- }
-
- /**
- * Содержится ли элемент в списке.
- *
- * @param string $id Идентификатор элемента меню.
- *
- * @return bool Содержится ли элемент в списке.
- */
- public function ContainsItem (string $id): bool
- {
- // Проверяю, что идентификатор не пустой
- if (GUIDExtension::IsNotValidOrEmpty($id))
- // - возвращаю отрицательный результат
- return false;
-
- // Получаю, что элемент с таким идентификатором существует
- return $this->_items->IsExist(fn (MenuItemModel $item) => $item->Id == $id);
- }
-
- /**
- * Вычисляет количество элементов меню.
- *
- * @param callable|null $predicate Выражение, уточняющее детали.
- *
- * @return int Количество элементов меню.
- */
- public function Count (?callable $predicate = null): int
- {
- return $this->_items->Count($predicate);
- }
-
- /**
- * Очищает список элементов.
- *
- * @return void
- */
- public function Clear (): void
- {
- // Очищаю
- $this->_items->Clear();
- }
-
- /**
- * Перемещает элемент меню вверх.
- *
- * @param string $id Идентификатор элемента меню.
- *
- * @return ActionState Результат выполнения.
- */
- public function MoveUp (string $id): ActionState
- {
- // Создаю результат
- $result = new ActionState();
-
- // Проверяю, что идентификатор не пустой
- if (GUIDExtension::IsNotValidOrEmpty($id)) {
- // - то выдаю ошибку
- $result->AddError("Неверный идентификатор (GUID)");
-
- // - и прерываю
- return $result;
- }
-
- // Загружаю элемент
- $item = $this->GetItem($id);
-
- // Если элемент не загружен
- if ($item == null) {
- // - то выдаю ошибку
- $result->AddError("Пункт меню с идентификатором 0%s не найден!");
-
- // - и прерываю
- return $result;
- }
-
- // Получаю его текущий порядковый номер
- $oldOrder = $item->Order;
-
- // Если порядковый номер не больше 1
- if ($oldOrder <= 1) {
- // - то выдаю предупреждение
- $result->AddWarning("Пункт меню находится на первом месте. Перемещение не требуется!");
-
- // - и прерываю
- return $result;
- }
-
- // Получаю новый порядковый номер
- $newOrder = $oldOrder - 1;
-
- // Получаю элемент, находящийся на новом порядковом номере (или GuidExEmpty, если нет элемента)
- $oldPlaceItem = $this->_items->GetRow(fn (MenuItemModel $item) => $item->Order == $newOrder);
-
- // Присваиваю новый порядковый номер изменяемого элемента
- $item->Order = $newOrder;
-
- // Обновляю список элементов
- $this->UpdateItem($item);
-
- // И если на новом порядковом номере существовал элемент
- if ($oldPlaceItem !== false) {
- // - то присваиваю ему старый порядковый номер
- $oldPlaceItem->Order = $oldOrder;
-
- // - и обновляю список
- $this->UpdateItem($oldPlaceItem);
- }
-
- // Возвращаю результат
- return $result;
- }
-
- /**
- * Получает элемент меню.
- *
- * @param string $id Идентификатор элемента меню.
- *
- * @return MenuItemModel|null Элемент меню или null, если элемент не найден.
- */
- public function GetItem (string $id): ?MenuItemModel
- {
- // Проверяю, что идентификатор не пустой
- if (GUIDExtension::IsNotValidOrEmpty($id))
- // - то возвращаю null
- return null;
-
- // Получаю элемент
- $item = $this->_items->GetRow(fn (MenuItemModel $item) => $item->Id == $id);
-
- // Если элемент не найден, то возвращаю null, иначе возвращаю найденный элемент.
- return ($item === false) ? null : $item;
- }
-
- /**
- * Обновляет элемент меню.
- *
- * @param MenuItemModel $item Обновлённый элемент меню.
- *
- * @return void
- */
- public function UpdateItem (MenuItemModel $item): void
- {
- // Если элемент существует
- if ($this->_items->IsExist(fn (MenuItemModel $itemM) => $itemM->Id == $item->Id))
- // - то удаляю его
- $this->RemoveItem($item->Id);
-
- // Добавляю новый
- $this->AddItem($item);
- }
-
- /**
- * Удаление элемента меню.
- *
- * @param string $id Идентификатор элемента меню.
- *
- * @return bool Статус удаления.
- */
- public function RemoveItem (string $id): bool
- {
- // Проверяю, что идентификатор не пустой
- if (GUIDExtension::IsNotValidOrEmpty($id))
- // - возвращаю отрицательный результат
- return false;
-
- return $this->_items->Delete(fn (MenuItemModel $item) => $item->Id == $id);
- }
-
- /**
- * Перемещает элемент меню вниз.
- *
- * @param string $id Идентификатор элемента меню.
- *
- * @return ActionState Результат выполнения.
- */
- public function MoveDown (string $id): ActionState
- {
- // Создаю результат
- $result = new ActionState();
-
- // Проверяю, что идентификатор не пустой
- if (GUIDExtension::IsNotValidOrEmpty($id)) {
- // - то выдаю ошибку
- $result->AddError("Неверный идентификатор (GUID)");
-
- // - и прерываю
- return $result;
- }
-
- // Загружаю элемент
- $item = $this->GetItem($id);
-
- // Если элемент не загружен
- if ($item == null) {
- // - то выдаю ошибку
- $result->AddError("Пункт меню с идентификатором 0%s не найден!");
-
- // - и прерываю
- return $result;
- }
-
- // Получаю его текущий порядковый номер
- $oldOrder = $item->Order;
-
- // Если порядковый номер не больше 1
- if ($oldOrder >= $this->GetLastItemOrder($item->ParentId)) {
- // - то выдаю предупреждение
- $result->AddWarning("Пункт меню находится на последнем месте. Перемещение не требуется!");
-
- // - и прерываю
- return $result;
- }
-
- // Получаю новый порядковый номер
- $newOrder = $oldOrder + 1;
-
- // Получаю элемент, находящийся на новом порядковом номере (или GuidExEmpty, если нет элемента)
- $oldPlaceItem = $this->_items->GetRow(fn (MenuItemModel $item) => $item->Order == $newOrder);
-
- // Присваиваю новый порядковый номер изменяемого элемента
- $item->Order = $newOrder;
-
- // Обновляю список элементов
- $this->UpdateItem($item);
-
- // И если на новом порядковом номере существовал элемент
- if ($oldPlaceItem !== false) {
- // - то присваиваю ему старый порядковый номер
- $oldPlaceItem->Order = $oldOrder;
-
- // - и обновляю список
- $this->UpdateItem($oldPlaceItem);
- }
-
- // Возвращаю результат
- return $result;
- }
-
- /**
- * Получает порядковый номер последнего элемента в списке.
- *
- * @param string $parentItemId Идентификатор родителя.
- *
- * @return int Порядковый номер последнего элемента в списке.
- */
- public function GetLastItemOrder (string $parentItemId): int
- {
- // Проверяю, что идентификатор не пустой
- if (GUIDExtension::IsNotValidOrEmpty($parentItemId) && $parentItemId !== GUIDExtension::GUIDEmpty)
- // - возвращаю отрицательный результат
- return 0;
-
- // Получаю последний элемент
- $item = $this->GetSubItems($parentItemId)->Last(false);
-
- // Если элемент не найден
- if ($item === false)
- // - возвращаю нулевой результат
- return 0;
-
- // Возвращаю порядок последнего элемента
- return $item->Order;
- }
-
- /**
- * Получает список вложенных элементов.
- *
- * @param string $parentItemId Идентификатор родителя.
- * @param bool $sortByOrder Нужно ли сортировать по порядку элементов.
- *
- * @return ObjectArray Список вложенных элементов.
- */
- public function GetSubItems (string $parentItemId, bool $sortByOrder = true): ObjectArray
- {
- // Проверяю, что идентификатор корректен
- if (GUIDExtension::IsNotValidOrEmpty($parentItemId) && $parentItemId !== GUIDExtension::GUIDEmpty)
- // - возвращаю отрицательный результат
- return new ObjectArray([]);
-
- // Получаю список элементов
- $result = $this->_items->GetRows(fn (MenuItemModel $item) => $item->ParentId == $parentItemId);
-
- // Если нужно сортировать
- if ($sortByOrder)
- // - сортирую
- $result->Sort("Order");
-
- // Возвращаю результат
- return $result;
- }
-
- /**
- * Изменяет родителя у элемента.
- *
- * @param string $id Идентификатор элемента.
- * @param string $newParentId Идентификатор нового родителя.
- *
- * @return ActionState Результат выполнения.
- */
- public function ChangeParent (string $id, string $newParentId): ActionState
- {
- // Создаю результат
- $result = new ActionState();
-
- // Проверяю, что идентификатор не пустой
- if (GUIDExtension::IsNotValidOrEmpty($id) || GUIDExtension::IsNotValidOrEmpty($newParentId)) {
- // - то выдаю ошибку
- $result->AddError("Неверный идентификатор (GUID)");
-
- // - и прерываю
- return $result;
- }
-
- // Если имеет дочерние
- if ($this->HasSubItems($id)) {
- // - то выдаю ошибку
- $result->AddError(
- "Пункт меню с идентификатором 0%s имеет дочерние пункты (подпункты). Пожалуйста, сперва переместите их, а потом будет возможно сменить родителя у данного элемента!");
-
- // - и прерываю
- return $result;
- }
-
- // Загружаю элемент
- $item = $this->GetItem($id);
-
- // Если элемент не загружен
- if ($item == null) {
- // - то выдаю ошибку
- $result->AddError("Пункт меню с идентификатором 0%s не найден!");
-
- // - и прерываю
- return $result;
- }
-
- // Получаю список свободных порядковых номеров
- $freeOrdersList = $this->GetFreeOrders($newParentId);
-
- // Получаю первый свободный порядковый номер
- $newOrder = count($freeOrdersList) > 0 ? min($freeOrdersList) : $this->GetLastItemOrder($newParentId) + 1;
-
- // Присваиваю элементу идентификатор нового родителя
- $item->ParentId = $newParentId;
-
- // Устанавливаю его порядок
- $item->Order = $newOrder;
-
- // Обновляю список элементов
- $this->UpdateItem($item);
-
- // Возвращаю результат
- return $result;
- }
-
- /**
- * Проверяет, имеет ли элемент дочерние.
- *
- * @param string $id Идентификатор элемента меню.
- *
- * @return bool Имеет ли элемент дочерние.
- */
- public function HasSubItems (string $id): bool
- {
- // Проверяю, что идентификатор не пустой
- if (GUIDExtension::IsNotValidOrEmpty($id))
- // - возвращаю отрицательный результат
- return false;
-
- // Проверяю, существует ли элемент, у которого идентификатор родителя совпадает
- // с идентификатором указанного элемента
- return $this->_items->IsExist(fn (MenuItemModel $item) => $item->ParentId == $id);
- }
-
- /**
- * Получает порядковый номер отсутствующего элемента в списке между первым и последним.
- *
- * @param string $parentItemId Идентификатор родителя.
- *
- * @return array Список порядковых номеров, которые не используются.
- */
- public function GetFreeOrders (string $parentItemId): array
- {
- // Проверяю, что идентификатор не пустой
- if (GUIDExtension::IsNotValidOrEmpty($parentItemId) && $parentItemId !== GUIDExtension::GUIDEmpty)
- // - возвращаю отрицательный результат
- return [];
-
- // Получаю список элементов
- $items = $this->GetSubItems($parentItemId);
-
- // Создаю список-результат
- $result = [];
-
- // Получаю минимальный порядок элементов
- $min = $this->GetFirstItemOrder($parentItemId);
-
- // Получаю максимальный порядок элементов
- $max = $this->GetLastItemOrder($parentItemId);
-
- // Если произошёл уникальный случай, когда нет элементов
- if ($min == 0 || $max == 0)
- return $result;
-
- // Заношу в список все до минимума
- if ($min > 0)
- for ($ind = 1; $ind < $min; $ind++)
- $result[] = $ind;
-
- // Прохожу "занятые" элементы
- for ($ind = $min; $ind <= $max; $ind++)
- // - и проверяю их на свободные
- if (!$items->IsExist(fn (MenuItemModel $item) => $item->Order == $ind))
- // -- заношу свободные в список
- $result[] = $ind;
-
- // Сортирую список
- sort($result, SORT_NUMERIC);
-
- // Возвращаю результат
- return $result;
- }
-
- /**
- * Получает порядковый номер первого элемента в списке.
- *
- * @param string $parentItemId Идентификатор родителя.
- *
- * @return int Порядковый номер первого элемента в списке.
- */
- public function GetFirstItemOrder (string $parentItemId): int
- {
- // Проверяю, что идентификатор не пустой
- if (GUIDExtension::IsNotValidOrEmpty($parentItemId) && $parentItemId !== GUIDExtension::GUIDEmpty)
- // - возвращаю отрицательный результат
- return 0;
-
- // Получаю первый элемент
- $item = $this->GetSubItems($parentItemId)->First(false);
-
- // Если элемент не найден
- if ($item === false)
- // - возвращаю нулевой результат
- return 0;
-
- // Возвращаю порядок первого элемента
- return $item->Order;
- }
-
- /**
- * @inheritdoc
- */
- public function getIterator (): Traversable
- {
- return $this->_items->getIterator();
- }
-
- /**
- * @inheritdoc
- */
- public function Duplicate (): MenuItems
- {
- return new MenuItems($this->_items);
- }
-
- /**
- * @inheritdoc
- */
- public
- function Serialize (): string
- {
- return $this->_items->Serialize();
- }
-
- /**
- * @inheritdoc
- */
- public
- function UnSerialize (string $serialized): void
- {
- // Создаю новый класс списка элементов
- $this->_items = new ObjectArray([]);
-
- // Восстанавливаю данные
- $this->_items->UnSerialize($serialized);
- }
-}
\ No newline at end of file
diff --git a/tests/data/MenuModel.php b/tests/data/MenuModel.php
deleted file mode 100644
index 9db8c91..0000000
--- a/tests/data/MenuModel.php
+++ /dev/null
@@ -1,139 +0,0 @@
-Id ?? GUIDExtension::GUIDEmpty;
- }
- set {
- // Проверка идентификатора
- if (!GUIDExtension::Validate($value))
- // - исключение
- throw new Exception("Неверный идентификатор (GUID)");
-
- // Установка идентификатора
- $this->Id = $value;
- }
- }
-
- /**
- * @var string $Name Имя меню.
- */
- public string $Name = "";
-
- /**
- * @var string $Description Описание меню.
- */
- public string $Description = "";
-
- /**
- * @var MenuItems Элементы меню.
- */
- public MenuItems $Items;
-
- /**
- * @var bool $AllowSubMenu Допустимо ли вложенное меню.
- */
- public bool $AllowSubMenu = true;
-
- /**
- * @var MenuCssClassModel $Css Модель стилей оформления меню.
- */
- public MenuCssClassModel $Css;
-
- /**
- * Конструктор.
- */
- public function __construct ()
- {
- $this->Items = new MenuItems([]);
- $this->Css = new MenuCssClassModel();
- }
-
- /**
- * @inheritdoc
- */
- public function Duplicate (): MenuModel
- {
- // Создаю новый класс
- $model = new MenuModel();
-
- // Копирую свойства класса
- ClassMapper::MapClass($this, $model);
-
- // Возвращаю класс
- return $model;
- }
-
- /**
- * @inheritDoc
- */
- public function ToSQL (bool $withId = true): array
- {
- // Создаем результат
- $result = [];
-
- // Добавляем значения
- $result['Name'] = $this->Name;
- $result['Description'] = $this->Description;
- $result['Items'] = $this->Items->Serialize();
- $result['AllowSubMenu'] = $this->AllowSubMenu ? 1 : 0;
- $result['Css'] = serialize($this->Css);
-
- // Если требуется идентификатор
- if ($withId)
- // - то добавляем его
- $result['Id'] = $this->Id;
-
- // Возвращаем результат
- return $result;
- }
-
- /**
- * @inheritDoc
- */
- public function FromSQL (array $sqlData): MenuModel
- {
- // Создаем результат
- $result = new MenuModel();
-
- // Устанавливаем значения
- if (isset($sqlData['Id']))
- $result->Id = $sqlData['Id'];
- if (isset($sqlData['Name']))
- $result->Name = $sqlData['Name'];
- if (isset($sqlData['Description']))
- $result->Description = $sqlData['Description'];
- $this->Items = new MenuItems([]);
- if (isset($sqlData['Items']))
- $result->Items->UnSerialize($sqlData['Items']);
- if (isset($sqlData['AllowSubMenu']))
- $result->AllowSubMenu = $sqlData['AllowSubMenu'] == 1;
- $this->Css = new MenuCssClassModel();
- if (isset($sqlData['Css']))
- $result->Css = unserialize($sqlData['Css']);
-
- // Возвращаем массив
- return $result;
- }
-}
\ No newline at end of file
diff --git a/tests/extensions/TypeExtensionTest.php b/tests/extensions/TypeExtensionTest.php
index 4708b10..3ea4d0e 100644
--- a/tests/extensions/TypeExtensionTest.php
+++ b/tests/extensions/TypeExtensionTest.php
@@ -2,70 +2,90 @@
namespace goodboyalex\php_components_pack\tests\extensions;
-use goodboyalex\php_components_pack\extensions\GUIDExtension;
+use goodboyalex\php_components_pack\exceptions\TypeException;
use goodboyalex\php_components_pack\extensions\TypeExtension;
-use goodboyalex\php_components_pack\tests\data\MenuCssClassModel;
-use goodboyalex\php_components_pack\tests\data\MenuItemModel;
-use goodboyalex\php_components_pack\tests\data\MenuItems;
-use goodboyalex\php_components_pack\tests\data\MenuModel;
+use goodboyalex\php_components_pack\tests\data\A;
+use goodboyalex\php_components_pack\tests\data\B;
+use goodboyalex\php_components_pack\tests\data\C;
+use goodboyalex\php_components_pack\tests\data\D;
use PHPUnit\Framework\TestCase;
class TypeExtensionTest extends TestCase
{
public function testFromArray ()
{
-
- }
-
- public function testToArray ()
- {
+ // Подготавливаем данные к тестированию
$this->PrepareForTest();
- $menu = new MenuModel();
- $menu->Id = GUIDExtension::Generate();
- $menu->Name = 'Menu';
- $menu->Description = 'Description';
- $menu->AllowSubMenu = true;
- $menu->Css = new MenuCssClassModel();
- $menu->Css->MenuClass = "menuClass";
- $menu->Css->ItemClass = "itemClass";
- $menu->Css->SubItemClass = "subItemClass";
- $menu->Css->ItemSubMenuClass = "itemSubMenuClass";
+ // Зададим имя файла
+ $fileName = __DIR__ . "/class.txt";
- $menuItem1 = new MenuItemModel();
- $menuItem1->Id = GUIDExtension::Generate();
- $menuItem1->Name = 'MenuItem1';
- $menuItem1->Description = 'Description';
- $menuItem1->Order = 1;
- $menuItem1->ParentId = GUIDExtension::GUIDEmpty;
- $menuItem1->URL = 'https://www.google.com';
- $menu->Items->AddItem($menuItem1);
- $menuItem2 = new MenuItemModel();
- $menuItem2->Id = GUIDExtension::Generate();
- $menuItem2->Name = 'MenuItem2';
- $menuItem2->Description = 'Description';
- $menuItem2->ParentId = $menuItem1->Id;
- $menuItem2->Order = 1;
- $menuItem2->URL = 'https://www.google.ru';
- $menu->Items->AddItem($menuItem2);
+ // Если файл не найден
+ if (!file_exists($fileName))
+ // - то завершаем работу
+ die("The test data file could not be found or has not yet passed the ToArray test! / Файл с данными для тестирования не найден или не пройдено ещё тестирование ToArray!");
- $closure = fn (object $class) => $class::class == MenuItems::class ? $class->Serialize()
- : TypeExtension::ToArray($class, TypeExtension::DEFAULT_TO_ARRAY_ON_CLASS());
+ // Считываем данные из файла и преобразуем в массив
+ $array = json_decode(file_get_contents($fileName), true);
- $array = TypeExtension::ToArray($menu, $closure);
+ // Преобразуем в объект
+ try {
+ $class = TypeExtension::FromArray($array, D::CLOSURE_FROM_ARRAY());
+ }
+ catch (TypeException $e) {
+ // - если ошибка, то выводим её и завершаем работу
+ die($e->getMessage());
+ }
- file_put_contents(__DIR__ . "/class.txt", json_encode($array, JSON_PRETTY_PRINT));
- var_dump($array);
- die();
+ // Проверяем
+ $this->assertEquals(D::class, $class::class);
+ $this->assertEquals(A::class, $class->a::class);
+ $this->assertEquals(B::class, $class->b::class);
+ $this->assertEquals(9876, $class->b->b);
+ $this->assertEquals(C::class, $class->c::class);
+ $this->assertEquals(54321, $class->c->intC);
+ $this->assertEquals('test_string', $class->stringD);
}
private function PrepareForTest (): void
{
require_once __DIR__ . '/../../sources/exceptions/TypeException.php';
require_once __DIR__ . '/../../sources/extensions/TypeExtension.php';
- require_once __DIR__ . '/../data/MenuItemModel.php';
- require_once __DIR__ . '/../data/MenuCssClassModel.php';
- require_once __DIR__ . '/../data/MenuItems.php';
- require_once __DIR__ . '/../data/MenuModel.php';
+ require_once __DIR__ . '/../data/A.php';
+ require_once __DIR__ . '/../data/B.php';
+ require_once __DIR__ . '/../data/C.php';
+ require_once __DIR__ . '/../data/D.php';
+ }
+
+ public function testToArray ()
+ {
+ // Подготавливаем данные к тестированию
+ $this->PrepareForTest();
+
+ // Создаём тестовый класс
+ $class = new D ('test_string', 12345, true, new A("test_string_A", 6789, false),
+ new B("test_string_B", 9876, "false"), new C("test_string_C", 54321, true));
+
+ // Преобразуем в массив
+ try {
+ $array = TypeExtension::ToArray($class, D::CLOSURE_TO_ARRAY());
+ }
+ catch (TypeException $e) {
+ // - если ошибка, то выводим её и завершаем работу
+ die($e->getMessage());
+ }
+
+ // Сохраняем массив в файл (для тестирования FromArray)
+ file_put_contents(__DIR__ . "/class.txt", json_encode($array, JSON_PRETTY_PRINT));
+
+ // Проверяем
+ $this->assertEquals(D::class, $array['type_class']);
+ $this->assertEquals(A::class, $array['a']['type_class']);
+ $this->assertEquals(B::class, $array['b']['type_class']);
+ $this->assertEquals(9876, $array['b']['b']);
+ $this->assertEquals(C::class, $array['c']['type_class']);
+ $this->assertEquals('test_string', $array['stringD']);
+ $this->assertEquals(12345, $array['intD']);
+ $this->assertTrue($array['boolD']);
}
}