validateLevel($dto); $position = $this->nextPosition( $dto->level, $dto->parentElement, ); $model = ElementModel::create([ 'set_level_id' => $dto->level->getId(), 'name' => $dto->name, 'parent_element_id' => $dto->parentElement?->getId(), 'position' => $position, ]); return new Element( id: $model->id, name: $model->name, level: $dto->level, parentElement: $dto->parentElement, position: $model->position, ); } public function find(int $id): ?Element { $model = ElementModel::find($id); return $model === null ? null : $this->toDomain($model); } public function findBySet(Set $set): array { $levelsById = []; foreach ($this->setLevelRepository->findBySet($set) as $level) { $levelsById[$level->getId()] = $level; } if ($levelsById === []) { return []; } $models = ElementModel::query() ->whereIn('set_level_id', array_keys($levelsById)) ->orderBy('id') ->get(); $elements = []; $elementsById = []; foreach ($models as $model) { $level = $levelsById[$model->set_level_id] ?? null; if ($level === null) { throw new RuntimeException('element level not found'); } $parentElement = null; if ($model->parent_element_id !== null) { $parentElement = $elementsById[$model->parent_element_id] ?? null; if ($parentElement === null) { throw new RuntimeException('element parent not found'); } } $element = $this->toDomainWithRelations( model: $model, level: $level, parentElement: $parentElement, ); $elements[] = $element; $elementsById[$element->getId()] = $element; } return $elements; } public function findTopLevelBySet(Set $set): array { $levels = $this->setLevelRepository->findBySet($set); if ($levels === []) { return []; } $topLevel = $levels[0]; $models = ElementModel::query() ->where('set_level_id', $topLevel->getId()) ->whereNull('parent_element_id') ->orderBy('position') ->orderBy('id') ->get(); $elements = []; foreach ($models as $model) { $elements[] = $this->toDomainWithRelations( model: $model, level: $topLevel, parentElement: null, ); } return $elements; } public function findByParentElement(Element $parentElement): array { $models = ElementModel::query() ->where('parent_element_id', $parentElement->getId()) ->orderBy('position') ->orderBy('id') ->get(); $elements = []; foreach ($models as $model) { $level = $this->findLevel($model->set_level_id); $elements[] = $this->toDomainWithRelations( model: $model, level: $level, parentElement: $parentElement, ); } return $elements; } /** * @throws DomainException */ private function validateLevel(CreateElementDto $dto): void { $parentElement = $dto->parentElement; if ($parentElement === null) { if ($dto->level->getDepth() !== 0) { throw new DomainException( 'root element must use the first set level', ); } return; } if ( $parentElement->getSet()->getId() !== $dto->level->getSet()->getId() ) { throw new DomainException( 'parent element must belong to the same set', ); } if ( $dto->level->getDepth() !== $parentElement->getLevel()->getDepth() + 1 ) { throw new DomainException( 'child element must use the next set level', ); } } private function nextPosition( SetLevel $level, ?Element $parentElement, ): int { $query = ElementModel::query() ->where('set_level_id', $level->getId()); if ($parentElement === null) { $query->whereNull('parent_element_id'); } else { $query->where('parent_element_id', $parentElement->getId()); } $currentMaximum = $query->max('position'); if ($currentMaximum === null) { return 1; } return (int) $currentMaximum + 1; } private function toDomain(ElementModel $model): Element { $level = $this->findLevel($model->set_level_id); $parentElement = null; if ($model->parent_element_id !== null) { $parentElement = $this->find($model->parent_element_id); if ($parentElement === null) { throw new RuntimeException('element parent not found'); } } return $this->toDomainWithRelations( model: $model, level: $level, parentElement: $parentElement, ); } private function toDomainWithRelations( ElementModel $model, SetLevel $level, ?Element $parentElement, ): Element { return new Element( id: $model->id, name: $model->name, level: $level, parentElement: $parentElement, position: $model->position, ); } private function findLevel(int $id): SetLevel { $level = $this->setLevelRepository->find($id); if ($level !== null) { return $level; } throw new RuntimeException('element level not found'); } }