From 117deab9d8896e44e39cbfd566597bf9d3f22fb1 Mon Sep 17 00:00:00 2001 From: babaev-an Date: Sat, 12 Jul 2025 16:27:53 +0300 Subject: [PATCH] 20250712 1.1.1 Beta 4 --- .gitignore | 3 +- sources/extensions/TypeExtension.md | 179 ++++++++ sources/extensions/TypeExtension.php | 30 +- tests/data/D.php | 56 +++ tests/data/MenuCssClassModel.php | 39 -- tests/data/MenuItemModel.php | 100 ----- tests/data/MenuItems.php | 555 ------------------------- tests/data/MenuModel.php | 139 ------- tests/extensions/TypeExtensionTest.php | 112 +++-- 9 files changed, 326 insertions(+), 887 deletions(-) create mode 100644 sources/extensions/TypeExtension.md create mode 100644 tests/data/D.php delete mode 100644 tests/data/MenuCssClassModel.php delete mode 100644 tests/data/MenuItemModel.php delete mode 100644 tests/data/MenuItems.php delete mode 100644 tests/data/MenuModel.php 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']); } }