$uriVariables * @param array $context */ public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed { if (!$data instanceof Composant) { return $this->decorated->process($data, $operation, $uriVariables, $context); } $pendingStructure = $data->getPendingStructure(); // Persist the entity first $result = $this->decorated->process($data, $operation, $uriVariables, $context); // On creation, scaffold slots from the ModelType skeleton + apply selections if ($operation instanceof Post) { $this->initializeSlotsFromSkeleton($data, $pendingStructure); $data->clearPendingStructure(); } return $result; } private function initializeSlotsFromSkeleton(Composant $composant, ?array $structure): void { $modelType = $composant->getTypeComposant(); if (!$modelType) { return; } $modelType = $this->entityManager->getRepository(ModelType::class)->find($modelType->getId()); if (!$modelType) { return; } // Index selections from the frontend payload by typePieceId/typeProductId/typeComposantId $pieceSelections = $this->indexSelections($structure['pieces'] ?? [], 'typePieceId', 'selectedPieceId'); $productSelections = $this->indexSelections($structure['products'] ?? [], 'typeProductId', 'selectedProductId'); $subSelections = $this->indexSelections($structure['subcomponents'] ?? [], 'typeComposantId', 'selectedComponentId'); $hasNewSlots = false; // Piece slots foreach ($modelType->getSkeletonPieceRequirements() as $req) { $typePieceId = $req->getTypePiece()->getId(); $slot = new ComposantPieceSlot(); $slot->setComposant($composant); $slot->setTypePiece($req->getTypePiece()); $slot->setPosition($req->getPosition()); $selectedId = $this->shiftSelection($pieceSelections, $typePieceId); if ($selectedId) { $slot->setSelectedPiece($this->entityManager->getReference(Piece::class, $selectedId)); } $this->entityManager->persist($slot); $hasNewSlots = true; } // Product slots foreach ($modelType->getSkeletonProductRequirements() as $req) { $typeProductId = $req->getTypeProduct()->getId(); $slot = new ComposantProductSlot(); $slot->setComposant($composant); $slot->setTypeProduct($req->getTypeProduct()); $slot->setFamilyCode($req->getFamilyCode()); $slot->setPosition($req->getPosition()); $selectedId = $this->shiftSelection($productSelections, $typeProductId); if ($selectedId) { $slot->setSelectedProduct($this->entityManager->getReference(Product::class, $selectedId)); } $this->entityManager->persist($slot); $hasNewSlots = true; } // Subcomponent slots foreach ($modelType->getSkeletonSubcomponentRequirements() as $req) { $typeComposantId = $req->getTypeComposant()?->getId() ?? ''; $slot = new ComposantSubcomponentSlot(); $slot->setComposant($composant); $slot->setAlias($req->getAlias()); $slot->setFamilyCode($req->getFamilyCode()); $slot->setTypeComposant($req->getTypeComposant()); $slot->setPosition($req->getPosition()); $selectedId = $this->shiftSelection($subSelections, $typeComposantId); if ($selectedId) { $slot->setSelectedComposant($this->entityManager->getReference(Composant::class, $selectedId)); } $this->entityManager->persist($slot); $hasNewSlots = true; } if ($hasNewSlots) { $this->entityManager->flush(); } } /** * Build an indexed map of typeId → [selectedId, selectedId, ...] from the payload. * Supports both flat format and nested definition format from the frontend. * * @return array> */ private function indexSelections(array $items, string $typeKey, string $selectionKey): array { $map = []; foreach ($items as $item) { if (!is_array($item)) { continue; } // The typeId can be at top level or inside definition $typeId = $item['definition'][$typeKey] ?? $item[$typeKey] ?? null; $selectedId = $item[$selectionKey] ?? null; if (!is_string($typeId) || '' === $typeId) { continue; } if (is_string($selectedId) && '' !== $selectedId) { $map[$typeId][] = $selectedId; } } return $map; } /** * Pop the first selection for a given typeId (handles multiple slots of the same type). */ private function shiftSelection(array &$selections, string $typeId): ?string { if (empty($selections[$typeId])) { return null; } return array_shift($selections[$typeId]); } }