20250711 1.1.1 Beta 3
This commit is contained in:
		
							
								
								
									
										555
									
								
								tests/data/MenuItems.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										555
									
								
								tests/data/MenuItems.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,555 @@ | ||||
| <?php | ||||
|  | ||||
| namespace goodboyalex\php_components_pack\tests\data; | ||||
|  | ||||
| use goodboyalex\php_components_pack\classes\ActionState; | ||||
| use goodboyalex\php_components_pack\classes\ObjectArray; | ||||
| use goodboyalex\php_components_pack\extensions\GUIDExtension; | ||||
| use goodboyalex\php_components_pack\interfaces\IDuplicated; | ||||
| use goodboyalex\php_components_pack\interfaces\ISerializable; | ||||
| use IteratorAggregate; | ||||
| use Traversable; | ||||
|  | ||||
| /** | ||||
|  * Класс списка элементов меню. | ||||
|  * | ||||
|  * @author Александр Бабаев | ||||
|  * @package freecms | ||||
|  * @version 0.1 | ||||
|  * @since 0.1 | ||||
|  */ | ||||
| final class MenuItems implements ISerializable, IteratorAggregate, IDuplicated | ||||
| { | ||||
|     /** | ||||
|      * @var ObjectArray Переменная для хранения списка. | ||||
|      */ | ||||
|     private ObjectArray $_items; | ||||
|  | ||||
|     /** | ||||
|      * Конструктор. | ||||
|      * | ||||
|      * @param ObjectArray|array $items Список элементов меню. | ||||
|      */ | ||||
|     public function __construct (ObjectArray|array $items) | ||||
|     { | ||||
|         $this->_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); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user