diff --git a/src/Services/ImportExportSystem/EntityImporter.php b/src/Services/ImportExportSystem/EntityImporter.php index 9396bb91..5f73ae92 100644 --- a/src/Services/ImportExportSystem/EntityImporter.php +++ b/src/Services/ImportExportSystem/EntityImporter.php @@ -26,6 +26,7 @@ use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\Parts\Category; use App\Entity\Parts\Part; +use App\Repository\StructuralDBElementRepository; use App\Serializer\APIPlatform\SkippableItemNormalizer; use Symfony\Component\Validator\ConstraintViolationList; use function count; @@ -50,12 +51,15 @@ class EntityImporter * Creates many entries at once, based on a (text) list of name. * The created entities are not persisted to database yet, so you have to do it yourself. * + * @template T of AbstractNamedDBElement * @param string $lines The list of names seperated by \n * @param string $class_name The name of the class for which the entities should be created + * @phpstan-param class-string $class_name * @param AbstractStructuralDBElement|null $parent the element which will be used as parent element for new elements * @param array $errors an associative array containing all validation errors * - * @return AbstractStructuralDBElement[] An array containing all valid imported entities (with the type $class_name) + * @return AbstractNamedDBElement[] An array containing all valid imported entities (with the type $class_name) + * @return T[] */ public function massCreation(string $lines, string $class_name, ?AbstractStructuralDBElement $parent = null, array &$errors = []): array { @@ -69,6 +73,13 @@ class EntityImporter throw new InvalidArgumentException('$parent must have the same type as specified in $class_name!'); } + //Ensure that parent is already persisted. Otherwise the getNewEntityFromPath function will not work. + if ($parent !== null && $parent->getID() === null) { + throw new InvalidArgumentException('The parent must persisted to database!'); + } + + $repo = $this->em->getRepository($class_name); + $errors = []; $valid_entities = []; @@ -78,10 +89,10 @@ class EntityImporter $indentations = [0]; foreach ($names as $name) { - //Count intendation level (whitespace characters at the beginning of the line) + //Count indentation level (whitespace characters at the beginning of the line) $identSize = strlen($name)-strlen(ltrim($name)); - //If the line is intendet more than the last line, we have a new parent element + //If the line is intended more than the last line, we have a new parent element if ($identSize > end($indentations)) { $current_parent = $last_element; //Add the new indentation level to the stack @@ -98,14 +109,25 @@ class EntityImporter //Skip empty lines (StrucuralDBElements must have a name) continue; } + /** @var AbstractStructuralDBElement $entity */ - //Create new element with given name - $entity = new $class_name(); - $entity->setName($name); - //Only set the parent if the entity is a StructuralDBElement - if ($entity instanceof AbstractStructuralDBElement) { - $entity->setParent($current_parent); + //Create new element with given name. Using the function from the repository, to correctly reuse existing elements + + if ($current_parent instanceof AbstractStructuralDBElement) { + $new_path = $current_parent->getFullPath("->") . '->' . $name; + } else { + $new_path = $name; } + //We can only use the getNewEntityFromPath function, if the repository is a StructuralDBElementRepository + if ($repo instanceof StructuralDBElementRepository) { + $entities = $repo->getNewEntityFromPath($new_path); + $entity = end($entities); + } else { //Otherwise just create a new entity + $entity = new $class_name; + $entity->setName($name); + } + + //Validate entity $tmp = $this->validator->validate($entity); diff --git a/tests/Services/ImportExportSystem/EntityImporterTest.php b/tests/Services/ImportExportSystem/EntityImporterTest.php index f560240c..07ac0017 100644 --- a/tests/Services/ImportExportSystem/EntityImporterTest.php +++ b/tests/Services/ImportExportSystem/EntityImporterTest.php @@ -24,10 +24,12 @@ namespace App\Tests\Services\ImportExportSystem; use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Attachments\AttachmentType; +use App\Entity\LabelSystem\LabelProfile; use App\Entity\Parts\Category; use App\Entity\Parts\Part; use App\Entity\UserSystem\User; use App\Services\ImportExportSystem\EntityImporter; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\Validator\ConstraintViolation; @@ -68,10 +70,24 @@ class EntityImporterTest extends WebTestCase //Check parent $this->assertNull($results[0]->getMasterPictureAttachment()); - $parent = new AttachmentType(); + $em = self::getContainer()->get(EntityManagerInterface::class); + $parent = $em->find(AttachmentType::class, 1); $results = $this->service->massCreation($lines, AttachmentType::class, $parent, $errors); $this->assertCount(3, $results); $this->assertSame($parent, $results[0]->getParent()); + + //Test for addition of existing elements + $errors = []; + $lines = "Node 3\n Node 3.new"; + $results = $this->service->massCreation($lines, AttachmentType::class, null, $errors); + $this->assertCount(2, $results); + $this->assertCount(0, $errors); + $this->assertSame('Node 3', $results[0]->getName()); + //Node 3 must be an existing entity + $this->assertNotNull($results[0]->getId()); + $this->assertSame('Node 3.new', $results[1]->getName()); + //Parent must be Node 3 + $this->assertSame($results[0], $results[1]->getParent()); } public function testNonStructuralClass(): void @@ -81,13 +97,9 @@ Test1 Test1.1 Test2 EOT; - - //Define a new anonymous class, which is not structural. We can not use User here, because it does some validation - $anonymous_object = new class extends AttachmentContainingDBElement {}; - $anonymous_class = get_class($anonymous_object); - $errors = []; - $results = $this->service->massCreation($input, $anonymous_class, null, $errors); + + $results = $this->service->massCreation($input, LabelProfile::class, null, $errors); //Import must not fail, even with non-structural classes $this->assertCount(3, $results); @@ -112,7 +124,8 @@ Test 2 EOT; $errors = []; - $parent = new AttachmentType(); + $em = self::getContainer()->get(EntityManagerInterface::class); + $parent = $em->find(AttachmentType::class, 1); $results = $this->service->massCreation($input, AttachmentType::class, $parent, $errors); //We have 7 elements, and 0 errors @@ -148,13 +161,15 @@ EOT; public function testMassCreationErrors(): void { $errors = []; - //Node 1 and Node 2 are created in datafixtures, so their attemp to create them again must fail. - $lines = "Test 1\nNode 1\nNode 2"; + $longName = str_repeat('a', 256); + + //The last node is too long, this must trigger a validation error + $lines = "Test 1\nNode 1\n " . $longName; $results = $this->service->massCreation($lines, AttachmentType::class, null, $errors); - $this->assertCount(1, $results); + $this->assertCount(2, $results); $this->assertSame('Test 1', $results[0]->getName()); - $this->assertCount(2, $errors); - $this->assertSame('Node 1', $errors[0]['entity']->getName()); + $this->assertCount(1, $errors); + $this->assertSame($longName, $errors[0]['entity']->getName()); } public function formatDataProvider(): array