diff --git a/backend/app/Element/CreateElementDto.php b/backend/app/Element/CreateElementDto.php index 6f7df77..d8a9fdc 100644 --- a/backend/app/Element/CreateElementDto.php +++ b/backend/app/Element/CreateElementDto.php @@ -2,14 +2,13 @@ namespace App\Element; -use App\Set\Set; +use App\Set\SetLevel; final readonly class CreateElementDto { public function __construct( - public Set $set, public string $name, - public string $kind, + public SetLevel $level, public ?Element $parentElement, ) {} } diff --git a/backend/app/Element/Element.php b/backend/app/Element/Element.php index 5e4c6bd..5eb6fe8 100644 --- a/backend/app/Element/Element.php +++ b/backend/app/Element/Element.php @@ -3,14 +3,14 @@ namespace App\Element; use App\Set\Set; +use App\Set\SetLevel; final readonly class Element { public function __construct( private int $id, private string $name, - private string $kind, - private Set $set, + private SetLevel $level, private ?Element $parentElement, private int $position, ) {} @@ -27,12 +27,17 @@ final readonly class Element public function getKind(): string { - return $this->kind; + return $this->level->getKind(); + } + + public function getLevel(): SetLevel + { + return $this->level; } public function getSet(): Set { - return $this->set; + return $this->level->getSet(); } public function getParentElement(): ?Element diff --git a/backend/app/Element/ElementModel.php b/backend/app/Element/ElementModel.php index 6eb345d..5a228d7 100644 --- a/backend/app/Element/ElementModel.php +++ b/backend/app/Element/ElementModel.php @@ -8,9 +8,8 @@ use Illuminate\Database\Eloquent\Model; /** * @property int $id - * @property int $set_id + * @property int $set_level_id * @property string $name - * @property string $kind * @property int|null $parent_element_id * @property int $position * @@ -21,9 +20,8 @@ use Illuminate\Database\Eloquent\Model; * @mixin \Eloquent */ #[Fillable([ - 'set_id', + 'set_level_id', 'name', - 'kind', 'parent_element_id', 'position', ])] @@ -39,7 +37,7 @@ class ElementModel extends Model protected function casts(): array { return [ - 'set_id' => 'integer', + 'set_level_id' => 'integer', 'parent_element_id' => 'integer', 'position' => 'integer', ]; diff --git a/backend/app/Element/EloquentElementRepository.php b/backend/app/Element/EloquentElementRepository.php index 1f30e24..fd12d58 100644 --- a/backend/app/Element/EloquentElementRepository.php +++ b/backend/app/Element/EloquentElementRepository.php @@ -3,27 +3,27 @@ namespace App\Element; use App\Set\Set; -use App\Set\SetRepository; +use App\Set\SetLevel; +use App\Set\SetLevelRepository; use DomainException; use RuntimeException; class EloquentElementRepository implements ElementRepository { public function __construct( - private SetRepository $setRepository, + private SetLevelRepository $setLevelRepository, ) {} public function create(CreateElementDto $dto): Element { - $this->validateParentSet($dto); + $this->validateLevel($dto); $position = $this->nextPosition( - $dto->set, + $dto->level, $dto->parentElement, ); $model = ElementModel::create([ - 'set_id' => $dto->set->getId(), + 'set_level_id' => $dto->level->getId(), 'name' => $dto->name, - 'kind' => $dto->kind, 'parent_element_id' => $dto->parentElement?->getId(), 'position' => $position, ]); @@ -31,8 +31,7 @@ class EloquentElementRepository implements ElementRepository return new Element( id: $model->id, name: $model->name, - kind: $model->kind, - set: $dto->set, + level: $dto->level, parentElement: $dto->parentElement, position: $model->position, ); @@ -47,14 +46,27 @@ class EloquentElementRepository implements ElementRepository public function findBySet(Set $set): array { + $levelsById = []; + foreach ($this->setLevelRepository->findBySet($set) as $level) { + $levelsById[$level->getId()] = $level; + } + if ($levelsById === []) { + return []; + } + $models = ElementModel::query() - ->where('set_id', $set->getId()) + ->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] @@ -66,7 +78,7 @@ class EloquentElementRepository implements ElementRepository $element = $this->toDomainWithRelations( model: $model, - set: $set, + level: $level, parentElement: $parentElement, ); $elements[] = $element; @@ -78,8 +90,14 @@ class EloquentElementRepository implements ElementRepository public function findTopLevelBySet(Set $set): array { + $levels = $this->setLevelRepository->findBySet($set); + if ($levels === []) { + return []; + } + + $topLevel = $levels[0]; $models = ElementModel::query() - ->where('set_id', $set->getId()) + ->where('set_level_id', $topLevel->getId()) ->whereNull('parent_element_id') ->orderBy('position') ->orderBy('id') @@ -89,7 +107,7 @@ class EloquentElementRepository implements ElementRepository foreach ($models as $model) { $elements[] = $this->toDomainWithRelations( model: $model, - set: $set, + level: $topLevel, parentElement: null, ); } @@ -100,7 +118,6 @@ class EloquentElementRepository implements ElementRepository public function findByParentElement(Element $parentElement): array { $models = ElementModel::query() - ->where('set_id', $parentElement->getSet()->getId()) ->where('parent_element_id', $parentElement->getId()) ->orderBy('position') ->orderBy('id') @@ -108,9 +125,10 @@ class EloquentElementRepository implements ElementRepository $elements = []; foreach ($models as $model) { + $level = $this->findLevel($model->set_level_id); $elements[] = $this->toDomainWithRelations( model: $model, - set: $parentElement->getSet(), + level: $level, parentElement: $parentElement, ); } @@ -121,26 +139,44 @@ class EloquentElementRepository implements ElementRepository /** * @throws DomainException */ - private function validateParentSet(CreateElementDto $dto): void + 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->set->getId()) { + 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( - Set $set, + SetLevel $level, ?Element $parentElement, ): int { $query = ElementModel::query() - ->where('set_id', $set->getId()); + ->where('set_level_id', $level->getId()); if ($parentElement === null) { $query->whereNull('parent_element_id'); } else { @@ -157,7 +193,7 @@ class EloquentElementRepository implements ElementRepository private function toDomain(ElementModel $model): Element { - $set = $this->findSet($model->set_id); + $level = $this->findLevel($model->set_level_id); $parentElement = null; if ($model->parent_element_id !== null) { @@ -169,33 +205,32 @@ class EloquentElementRepository implements ElementRepository return $this->toDomainWithRelations( model: $model, - set: $set, + level: $level, parentElement: $parentElement, ); } private function toDomainWithRelations( ElementModel $model, - Set $set, + SetLevel $level, ?Element $parentElement, ): Element { return new Element( id: $model->id, name: $model->name, - kind: $model->kind, - set: $set, + level: $level, parentElement: $parentElement, position: $model->position, ); } - private function findSet(int $id): Set + private function findLevel(int $id): SetLevel { - $set = $this->setRepository->find($id); - if ($set !== null) { - return $set; + $level = $this->setLevelRepository->find($id); + if ($level !== null) { + return $level; } - throw new RuntimeException('element set not found'); + throw new RuntimeException('element level not found'); } } diff --git a/backend/database/migrations/2026_08_08_000000_create_elements_table.php b/backend/database/migrations/2026_08_08_000000_create_elements_table.php index 196c2c5..04fe1d4 100644 --- a/backend/database/migrations/2026_08_08_000000_create_elements_table.php +++ b/backend/database/migrations/2026_08_08_000000_create_elements_table.php @@ -10,18 +10,17 @@ return new class extends Migration { Schema::create('elements', function (Blueprint $table): void { $table->id(); - $table->foreignId('set_id') - ->constrained('sets') + $table->foreignId('set_level_id') + ->constrained('set_levels') ->restrictOnDelete(); $table->string('name'); - $table->string('kind'); $table->foreignId('parent_element_id') ->nullable() ->constrained('elements') ->restrictOnDelete(); $table->unsignedInteger('position'); $table->index([ - 'set_id', + 'set_level_id', 'parent_element_id', 'position', ]);