mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 01:25:55 +02:00
Started implementing building blocks for the merge system
This commit is contained in:
parent
83ad99215f
commit
01784a9d1f
5 changed files with 401 additions and 0 deletions
29
src/Services/EntityMergers/EntityMerger.php
Normal file
29
src/Services/EntityMergers/EntityMerger.php
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
|
||||||
|
namespace App\Services\EntityMergers;
|
||||||
|
|
||||||
|
class EntityMerger
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
185
src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php
Normal file
185
src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
|
||||||
|
namespace App\Services\EntityMergers\Mergers;
|
||||||
|
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||||
|
use Symfony\Contracts\Service\Attribute\Required;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This trait provides helper methods for entity mergers.
|
||||||
|
* By default, it uses the value from the target entity, unless it not fullfills a condition.
|
||||||
|
*/
|
||||||
|
trait EntityMergerHelperTrait
|
||||||
|
{
|
||||||
|
protected PropertyAccessorInterface $property_accessor;
|
||||||
|
|
||||||
|
#[Required]
|
||||||
|
public function setPropertyAccessor(PropertyAccessorInterface $property_accessor): void
|
||||||
|
{
|
||||||
|
$this->property_accessor = $property_accessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Choice the value to use from the target or the other entity by using a callback function.
|
||||||
|
*
|
||||||
|
* @param callable $callback The callback to use. The signature is: function($target_value, $other_value, $target, $other, $field). The callback should return the value to use.
|
||||||
|
* @param object $target The target entity
|
||||||
|
* @param object $other The other entity
|
||||||
|
* @param string $field The field to use
|
||||||
|
* @return object The target entity with the value set
|
||||||
|
*/
|
||||||
|
protected function useCallback(callable $callback, object $target, object $other, string $field): object
|
||||||
|
{
|
||||||
|
//Get the values from the entities
|
||||||
|
$target_value = $this->property_accessor->getValue($target, $field);
|
||||||
|
$other_value = $this->property_accessor->getValue($other, $field);
|
||||||
|
|
||||||
|
//Call the callback, with the signature: function($target_value, $other_value, $target, $other, $field)
|
||||||
|
//The callback should return the value to use
|
||||||
|
$value = $callback($target_value, $other_value, $target, $other, $field);
|
||||||
|
|
||||||
|
//Set the value
|
||||||
|
$this->property_accessor->setValue($target, $field, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the value from the other entity, if the value from the target entity is null.
|
||||||
|
*
|
||||||
|
* @param object $target The target entity
|
||||||
|
* @param object $other The other entity
|
||||||
|
* @param string $field The field to use
|
||||||
|
* @return object The target entity with the value set
|
||||||
|
*/
|
||||||
|
protected function useOtherValueIfNotNull(object $target, object $other, string $field): object
|
||||||
|
{
|
||||||
|
$this->useCallback(
|
||||||
|
function ($target_value, $other_value) {
|
||||||
|
return $target_value ?? $other_value;
|
||||||
|
},
|
||||||
|
$target,
|
||||||
|
$other,
|
||||||
|
$field
|
||||||
|
);
|
||||||
|
|
||||||
|
return $target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the value from the other entity, if the value from the target entity is empty.
|
||||||
|
*
|
||||||
|
* @param object $target The target entity
|
||||||
|
* @param object $other The other entity
|
||||||
|
* @param string $field The field to use
|
||||||
|
* @return object The target entity with the value set
|
||||||
|
*/
|
||||||
|
protected function useOtherValueIfNotEmtpy(object $target, object $other, string $field): object
|
||||||
|
{
|
||||||
|
$this->useCallback(
|
||||||
|
function ($target_value, $other_value) {
|
||||||
|
return empty($target_value) ? $other_value : $target_value;
|
||||||
|
},
|
||||||
|
$target,
|
||||||
|
$other,
|
||||||
|
$field
|
||||||
|
);
|
||||||
|
|
||||||
|
return $target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the larger value from the target and the other entity for the given field.
|
||||||
|
*
|
||||||
|
* @param object $target
|
||||||
|
* @param object $other
|
||||||
|
* @param string $field
|
||||||
|
* @return object
|
||||||
|
*/
|
||||||
|
protected function useLargerValue(object $target, object $other, string $field): object
|
||||||
|
{
|
||||||
|
$this->useCallback(
|
||||||
|
function ($target_value, $other_value) {
|
||||||
|
return max($target_value, $other_value);
|
||||||
|
},
|
||||||
|
$target,
|
||||||
|
$other,
|
||||||
|
$field
|
||||||
|
);
|
||||||
|
|
||||||
|
return $target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the smaller value from the target and the other entity for the given field.
|
||||||
|
*
|
||||||
|
* @param object $target
|
||||||
|
* @param object $other
|
||||||
|
* @param string $field
|
||||||
|
* @return object
|
||||||
|
*/
|
||||||
|
protected function useSmallerValue(object $target, object $other, string $field): object
|
||||||
|
{
|
||||||
|
$this->useCallback(
|
||||||
|
function ($target_value, $other_value) {
|
||||||
|
return min($target_value, $other_value);
|
||||||
|
},
|
||||||
|
$target,
|
||||||
|
$other,
|
||||||
|
$field
|
||||||
|
);
|
||||||
|
|
||||||
|
return $target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge the collections from the target and the other entity for the given field and put all items into the target collection.
|
||||||
|
* @param object $target
|
||||||
|
* @param object $other
|
||||||
|
* @param string $field
|
||||||
|
* @return object
|
||||||
|
*/
|
||||||
|
protected function mergeCollections(object $target, object $other, string $field): object
|
||||||
|
{
|
||||||
|
$target_collection = $this->property_accessor->getValue($target, $field);
|
||||||
|
$other_collection = $this->property_accessor->getValue($other, $field);
|
||||||
|
|
||||||
|
if (!$target_collection instanceof Collection) {
|
||||||
|
throw new \InvalidArgumentException("The target field $field is not a collection");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Clone the items from the other collection
|
||||||
|
$clones = [];
|
||||||
|
foreach ($other_collection as $item) {
|
||||||
|
$clones[] = clone $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tmp = array_merge($target_collection->toArray(), $clones);
|
||||||
|
|
||||||
|
//Create a new collection with the clones and merge it into the target collection
|
||||||
|
$this->property_accessor->setValue($target, $field, $tmp);
|
||||||
|
|
||||||
|
return $target;
|
||||||
|
}
|
||||||
|
}
|
47
src/Services/EntityMergers/Mergers/EntityMergerInterface.php
Normal file
47
src/Services/EntityMergers/Mergers/EntityMergerInterface.php
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
|
||||||
|
namespace App\Services\EntityMergers\Mergers;
|
||||||
|
|
||||||
|
|
||||||
|
interface EntityMergerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determines if this merger supports merging the other entity into the target entity.
|
||||||
|
* @param object $target
|
||||||
|
* @param object $other
|
||||||
|
* @param array $context
|
||||||
|
* @return bool True if this merger supports merging the other entity into the target entity, false otherwise
|
||||||
|
*/
|
||||||
|
public function supports(object $target, object $other, array $context = []): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge the other entity into the target entity.
|
||||||
|
* The target entity will be modified and returned.
|
||||||
|
* @param object $target
|
||||||
|
* @param object $other
|
||||||
|
* @param array $context
|
||||||
|
* @return object
|
||||||
|
*/
|
||||||
|
public function merge(object $target, object $other, array $context = []): object;
|
||||||
|
}
|
51
src/Services/EntityMergers/Mergers/PartMerger.php
Normal file
51
src/Services/EntityMergers/Mergers/PartMerger.php
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
|
||||||
|
namespace App\Services\EntityMergers\Mergers;
|
||||||
|
|
||||||
|
use App\Entity\Parts\Part;
|
||||||
|
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||||
|
|
||||||
|
#[Autoconfigure(public: true)]
|
||||||
|
class PartMerger implements EntityMergerInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
use EntityMergerHelperTrait;
|
||||||
|
|
||||||
|
public function supports(object $target, object $other, array $context = []): bool
|
||||||
|
{
|
||||||
|
return $target instanceof Part && $other instanceof Part;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function merge(object $target, object $other, array $context = []): object
|
||||||
|
{
|
||||||
|
if (!$target instanceof Part || !$other instanceof Part) {
|
||||||
|
throw new \InvalidArgumentException('The target and the other entity must be instances of Part');
|
||||||
|
}
|
||||||
|
|
||||||
|
//Merge the fields
|
||||||
|
$this->mergeCollections($target, $other, 'partLots');
|
||||||
|
|
||||||
|
return $target;
|
||||||
|
}
|
||||||
|
}
|
89
tests/Services/EntityMergers/Mergers/PartMergerTest.php
Normal file
89
tests/Services/EntityMergers/Mergers/PartMergerTest.php
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Tests\Services\EntityMergers\Mergers;
|
||||||
|
|
||||||
|
use App\Entity\Parts\Part;
|
||||||
|
use App\Entity\Parts\PartLot;
|
||||||
|
use App\Services\EntityMergers\Mergers\PartMerger;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
|
||||||
|
class PartMergerTest extends KernelTestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
/** @var PartMerger|null */
|
||||||
|
protected ?PartMerger $merger = null;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
$this->merger = self::getContainer()->get(PartMerger::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test also functions as test for EntityMergerHelperTrait::mergeCollections() so its pretty long.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testMergePartLots()
|
||||||
|
{
|
||||||
|
$lot1 = (new PartLot())->setAmount(2)->setNeedsRefill(true);
|
||||||
|
$lot2 = (new PartLot())->setInstockUnknown(true)->setVendorBarcode('test');
|
||||||
|
$lot3 = (new PartLot())->setDescription('lot3')->setAmount(3);
|
||||||
|
$lot4 = (new PartLot())->setDescription('lot4')->setComment('comment');
|
||||||
|
|
||||||
|
$part1 = (new Part())
|
||||||
|
->setName('Part 1')
|
||||||
|
->addPartLot($lot1)
|
||||||
|
->addPartLot($lot2);
|
||||||
|
|
||||||
|
$part2 = (new Part())
|
||||||
|
->setName('Part 2')
|
||||||
|
->addPartLot($lot3)
|
||||||
|
->addPartLot($lot4);
|
||||||
|
|
||||||
|
$merged = $this->merger->merge($part1, $part2);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Part::class, $merged);
|
||||||
|
//We should now have all 4 lots
|
||||||
|
$this->assertCount(4, $merged->getPartLots());
|
||||||
|
|
||||||
|
//The existing lots should be the same instance as before
|
||||||
|
$this->assertSame($lot1, $merged->getPartLots()->get(0));
|
||||||
|
$this->assertSame($lot2, $merged->getPartLots()->get(1));
|
||||||
|
//While the new lots should be new instances
|
||||||
|
$this->assertNotSame($lot3, $merged->getPartLots()->get(2));
|
||||||
|
$this->assertNotSame($lot4, $merged->getPartLots()->get(3));
|
||||||
|
|
||||||
|
//But the new lots, should be assigned to the target part and contain the same info
|
||||||
|
$clone3 = $merged->getPartLots()->get(2);
|
||||||
|
$clone4 = $merged->getPartLots()->get(3);
|
||||||
|
$this->assertSame($merged, $clone3->getPart());
|
||||||
|
$this->assertSame($merged, $clone4->getPart());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSupports()
|
||||||
|
{
|
||||||
|
$this->assertFalse($this->merger->supports(new \stdClass(), new \stdClass()));
|
||||||
|
$this->assertFalse($this->merger->supports(new \stdClass(), new Part()));
|
||||||
|
$this->assertTrue($this->merger->supports(new Part(), new Part()));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue