From c86694ab8f8250d88203689acca4454a2d4e125f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 21 Nov 2023 19:41:18 +0100 Subject: [PATCH] Merge the remaining fields of a Part --- .../Mergers/EntityMergerHelperTrait.php | 80 +++++- .../EntityMergers/Mergers/PartMerger.php | 31 ++- .../Mergers/EntityMergerHelperTraitTest.php | 231 ++++++++++++++++++ .../EntityMergers/Mergers/MergeTestClass.php | 46 ++++ 4 files changed, 384 insertions(+), 4 deletions(-) create mode 100644 tests/Services/EntityMergers/Mergers/EntityMergerHelperTraitTest.php create mode 100644 tests/Services/EntityMergers/Mergers/MergeTestClass.php diff --git a/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php b/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php index ae3c75b9..b2fbff24 100644 --- a/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php +++ b/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php @@ -25,6 +25,7 @@ namespace App\Services\EntityMergers\Mergers; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentContainingDBElement; +use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\Parameters\AbstractParameter; use App\Entity\Parts\Part; @@ -33,6 +34,8 @@ use Doctrine\Common\Collections\Collection; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\Contracts\Service\Attribute\Required; +use function Symfony\Component\String\u; + /** * This trait provides helper methods for entity mergers. * By default, it uses the value from the target entity, unless it not fullfills a condition. @@ -184,10 +187,10 @@ trait EntityMergerHelperTrait protected function mergeTags(object $target, object $other, string $field, string $separator = ','): object { return $this->useCallback( - function (string $t, string $o) use ($separator): string { + function (string|null $t, string|null $o) use ($separator): string { //Explode the strings into arrays - $t_array = explode($separator, $t); - $o_array = explode($separator, $o); + $t_array = explode($separator, $t ?? ''); + $o_array = explode($separator, $o ?? ''); //Merge the arrays and remove duplicates $tmp = array_unique(array_merge($t_array, $o_array)); @@ -281,4 +284,75 @@ trait EntityMergerHelperTrait && $t->getGroup() === $o->getGroup(); }); } + + /** + * Check if the two strings have equal content. + * This method is case-insensitive and ignores whitespace. + * @param string|\Stringable $t + * @param string|\Stringable $o + * @return bool + */ + protected function areStringsEqual(string|\Stringable $t, string|\Stringable $o): bool + { + $t_str = u($t)->trim()->folded(); + $o_str = u($o)->trim()->folded(); + + return $t_str->equalsTo($o_str); + } + + /** + * Merge the text from the target and the other entity for the given field by attaching the other text to the target text via the given separator. + * For example, if the target text is "Hello" and the other text is "World", the result is "Hello / World". + * If the text is the same in both entities, the target text is returned. + * @param object $target + * @param object $other + * @param string $field + * @param string $separator + * @return object + */ + protected function mergeTextWithSeparator(object $target, object $other, string $field, string $separator = ' / '): object + { + return $this->useCallback( + function (string $t, string $o) use ($separator): string { + //Check if the strings are equal + if ($this->areStringsEqual($t, $o)) { + return $t; + } + + return trim($t) . $separator . trim($o); + }, + $target, + $other, + $field + ); + } + + /** + * Merge two comments from the target and the other entity for the given field. + * The comments of the both entities get concated, while the second part get a headline with the name of the old part. + * @param AbstractNamedDBElement $target + * @param AbstractNamedDBElement $other + * @param string $field + * @return object + */ + protected function mergeComment(AbstractNamedDBElement $target, AbstractNamedDBElement $other, string $field = 'comment'): object + { + return $this->useCallback( + function (string $t, string $o) use ($other): string { + //Check if the strings are equal + if ($this->areStringsEqual($t, $o)) { + return $t; + } + + return sprintf("%s\n\n%s:\n%s", + trim($t), + $other->getName(), + trim($o) + ); + }, + $target, + $other, + $field + ); + } } \ No newline at end of file diff --git a/src/Services/EntityMergers/Mergers/PartMerger.php b/src/Services/EntityMergers/Mergers/PartMerger.php index a3962118..6c839414 100644 --- a/src/Services/EntityMergers/Mergers/PartMerger.php +++ b/src/Services/EntityMergers/Mergers/PartMerger.php @@ -23,6 +23,8 @@ declare(strict_types=1); namespace App\Services\EntityMergers\Mergers; +use App\Entity\Parts\InfoProviderReference; +use App\Entity\Parts\ManufacturingStatus; use App\Entity\Parts\Part; use App\Entity\Parts\PartAssociation; use App\Entity\Parts\PartLot; @@ -46,7 +48,16 @@ class PartMerger implements EntityMergerInterface throw new \InvalidArgumentException('The target and the other entity must be instances of Part'); } - //Merge basic infos + //Merge basic fields + $this->mergeTextWithSeparator($target, $other, 'name'); + $this->mergeTextWithSeparator($target, $other, 'description'); + $this->mergeComment($target, $other); + $this->useOtherValueIfNotEmtpy($target, $other, 'manufacturer_product_url'); + $this->useOtherValueIfNotEmtpy($target, $other, 'manufacturer_product_number'); + $this->useOtherValueIfNotEmtpy($target, $other, 'mass'); + $this->useOtherValueIfNotEmtpy($target, $other, 'ipn'); + + //Merge relations to other entities $this->useOtherValueIfNotNull($target, $other, 'manufacturer'); $this->useOtherValueIfNotNull($target, $other, 'footprint'); $this->useOtherValueIfNotNull($target, $other, 'category'); @@ -62,6 +73,24 @@ class PartMerger implements EntityMergerInterface //Merge the tags using the tag merger $this->mergeTags($target, $other, 'tags'); + //Merge manufacturing status + $this->useCallback(function (?ManufacturingStatus $t, ?ManufacturingStatus $o): ?ManufacturingStatus { + //Use the other value, if the target value is not set + if ($t === ManufacturingStatus::NOT_SET || $t === null) { + return $o ?? ManufacturingStatus::NOT_SET; + } + + return $t; + }, $target, $other, 'manufacturing_status'); + + //Merge provider reference + $this->useCallback(function (InfoProviderReference $t, InfoProviderReference $o): InfoProviderReference { + if (!$t->isProviderCreated() && $o->isProviderCreated()) { + return $o; + } + return $t; + }, $target, $other, 'providerReference'); + return $target; } diff --git a/tests/Services/EntityMergers/Mergers/EntityMergerHelperTraitTest.php b/tests/Services/EntityMergers/Mergers/EntityMergerHelperTraitTest.php new file mode 100644 index 00000000..c8366019 --- /dev/null +++ b/tests/Services/EntityMergers/Mergers/EntityMergerHelperTraitTest.php @@ -0,0 +1,231 @@ +. + */ + +namespace App\Tests\Services\EntityMergers\Mergers; + +use App\Entity\Parts\Part; +use App\Services\EntityMergers\Mergers\EntityMergerHelperTrait; +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; + +class EntityMergerHelperTraitTest extends KernelTestCase +{ + use EntityMergerHelperTrait; + + public function setUp(): void + { + self::bootKernel(); + $this->property_accessor = self::getContainer()->get(PropertyAccessorInterface::class); + } + + public function testUseCallback(): void + { + $obj1 = new MergeTestClass(); + $obj1->non_nullable_string = 'obj1'; + $obj2 = new MergeTestClass(); + $obj2->non_nullable_string = 'obj2'; + + $tmp = $this->useCallback(function ($target_value, $other_value, $target, $other, $field) use ($obj1, $obj2) { + $this->assertSame($obj1, $target); + $this->assertSame($obj2, $other); + $this->assertSame('non_nullable_string', $field); + $this->assertSame('obj1', $target_value); + $this->assertSame('obj2', $other_value); + + return 'callback'; + + }, $obj1, $obj2, 'non_nullable_string'); + + //merge should return the target object + $this->assertSame($obj1, $tmp); + //And it should have the value from the callback set + $this->assertSame('callback', $obj1->non_nullable_string); + } + + public function testOtherFunctionIfNotNull(): void + { + $obj1 = new MergeTestClass(); + $obj1->string_property = null; + $obj2 = new MergeTestClass(); + $obj2->string_property = 'obj2'; + + $tmp = $this->useOtherValueIfNotNull($obj1, $obj2, 'string_property'); + $this->assertSame($obj1, $tmp); + $this->assertSame('obj2', $obj1->string_property); + + $obj1->string_property = 'obj1'; + $tmp = $this->useOtherValueIfNotNull($obj1, $obj2, 'string_property'); + $this->assertSame($obj1, $tmp); + $this->assertSame('obj1', $tmp->string_property); + + $obj1->string_property = null; + $obj2->string_property = null; + $this->assertSame($obj1, $this->useOtherValueIfNotNull($obj1, $obj2, 'string_property')); + $this->assertNull($obj1->string_property); + } + + public function testOtherFunctionIfNotEmpty(): void + { + $obj1 = new MergeTestClass(); + $obj1->string_property = null; + $obj2 = new MergeTestClass(); + $obj2->string_property = 'obj2'; + + $tmp = $this->useOtherValueIfNotEmtpy($obj1, $obj2, 'string_property'); + $this->assertSame($obj1, $tmp); + $this->assertSame('obj2', $obj1->string_property); + + $obj1->string_property = 'obj1'; + $tmp = $this->useOtherValueIfNotEmtpy($obj1, $obj2, 'string_property'); + $this->assertSame($obj1, $tmp); + $this->assertSame('obj1', $tmp->string_property); + + $obj1->string_property = null; + $obj2->string_property = null; + $this->assertSame($obj1, $this->useOtherValueIfNotEmtpy($obj1, $obj2, 'string_property')); + $this->assertNull($obj1->string_property); + + $obj1->string_property = ''; + $obj2->string_property = 'test'; + $this->assertSame($obj1, $this->useOtherValueIfNotEmtpy($obj1, $obj2, 'string_property')); + $this->assertSame('test', $obj1->string_property); + } + + public function testUseLargerValue(): void + { + $obj1 = new MergeTestClass(); + $obj1->int_property = 1; + $obj2 = new MergeTestClass(); + $obj2->int_property = 2; + + $tmp = $this->useLargerValue($obj1, $obj2, 'int_property'); + $this->assertSame($obj1, $tmp); + $this->assertSame(2, $obj1->int_property); + + $obj1->int_property = 3; + $obj2->int_property = 2; + + $tmp = $this->useLargerValue($obj1, $obj2, 'int_property'); + $this->assertSame($obj1, $tmp); + $this->assertSame(3, $obj1->int_property); + } + + public function testUseSmallerValue(): void + { + $obj1 = new MergeTestClass(); + $obj1->int_property = 1; + $obj2 = new MergeTestClass(); + $obj2->int_property = 2; + + $tmp = $this->useSmallerValue($obj1, $obj2, 'int_property'); + $this->assertSame($obj1, $tmp); + $this->assertSame(1, $obj1->int_property); + + $obj1->int_property = 3; + $obj2->int_property = 2; + + $tmp = $this->useSmallerValue($obj1, $obj2, 'int_property'); + $this->assertSame($obj1, $tmp); + $this->assertSame(2, $obj1->int_property); + } + + public function testUseTrueValue(): void + { + $obj1 = new MergeTestClass(); + $obj1->bool_property = false; + $obj2 = new MergeTestClass(); + $obj2->bool_property = true; + + $tmp = $this->useTrueValue($obj1, $obj2, 'bool_property'); + $this->assertSame($obj1, $tmp); + $this->assertTrue($obj1->bool_property); + + $obj1->bool_property = true; + $obj2->bool_property = false; + $this->assertTrue($this->useTrueValue($obj1, $obj2, 'bool_property')->bool_property); + + $obj1->bool_property = false; + $obj2->bool_property = false; + $this->assertFalse($this->useTrueValue($obj1, $obj2, 'bool_property')->bool_property); + } + + public function testMergeTags(): void + { + $obj1 = new MergeTestClass(); + $obj1->string_property = 'tag1,tag2,tag3'; + $obj2 = new MergeTestClass(); + $obj2->string_property = 'tag2,tag3,tag4'; + + $tmp = $this->mergeTags($obj1, $obj2, 'string_property'); + $this->assertSame($obj1, $tmp); + $this->assertSame('tag1,tag2,tag3,tag4', $obj1->string_property); + } + + public function testAreStringsEqual(): void + { + $this->assertTrue($this->areStringsEqual('test', 'test')); + $this->assertTrue($this->areStringsEqual('test', 'TEST')); + $this->assertTrue($this->areStringsEqual('test', 'Test')); + $this->assertTrue($this->areStringsEqual('test', ' Test ')); + $this->assertTrue($this->areStringsEqual('Test ', 'test')); + + $this->assertFalse($this->areStringsEqual('test', 'test2')); + $this->assertFalse($this->areStringsEqual('test', 'test 1')); + } + + public function testMergeTextWithSeparator(): void + { + $obj1 = new MergeTestClass(); + $obj1->string_property = 'Test1'; + $obj2 = new MergeTestClass(); + $obj2->string_property = 'Test2'; + + $tmp = $this->mergeTextWithSeparator($obj1, $obj2, 'string_property', ' # '); + $this->assertSame($obj1, $tmp); + $this->assertSame('Test1 # Test2', $obj1->string_property); + + //If thee text is the same, it should not be duplicated + $obj1->string_property = 'Test1'; + $obj2->string_property = 'Test1'; + $this->assertSame($obj1, $this->mergeTextWithSeparator($obj1, $obj2, 'string_property', ' # ')); + $this->assertSame('Test1', $obj1->string_property); + } + + public function testMergeComment(): void + { + $obj1 = new Part(); + $obj1->setName('Test1'); + $obj1->setComment('Comment1'); + $obj2 = new Part(); + $obj2->setName('Test2'); + $obj2->setComment('Comment2'); + + $tmp = $this->mergeComment($obj1, $obj2); + $this->assertSame($obj1, $tmp); + $this->assertSame("Comment1\n\nTest2:\nComment2", $obj1->getComment()); + + //If the comment is the same, it should not be duplicated + $obj1->setComment('Comment1'); + $obj2->setComment('Comment1'); + $this->assertSame($obj1, $this->mergeComment($obj1, $obj2)); + $this->assertSame('Comment1', $obj1->getComment()); + } +} diff --git a/tests/Services/EntityMergers/Mergers/MergeTestClass.php b/tests/Services/EntityMergers/Mergers/MergeTestClass.php new file mode 100644 index 00000000..da7ad67c --- /dev/null +++ b/tests/Services/EntityMergers/Mergers/MergeTestClass.php @@ -0,0 +1,46 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Tests\Services\EntityMergers\Mergers; + +use App\Entity\Parts\Category; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; + +class MergeTestClass +{ + public int $int_property = 0; + public ?float $float_property = null; + public ?string $string_property = null; + public string $non_nullable_string = ''; + public bool $bool_property = false; + + public Collection $collection; + + public ?Category $category; + + public function __construct() + { + $this->collection = new ArrayCollection(); + } +} \ No newline at end of file