From d24e3a6e37247192860d3a933942b68728536e67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Marodon?= Date: Mon, 16 Sep 2024 13:55:17 +0200 Subject: [PATCH 1/4] feat: Add support of Symfony UIDs at binary format --- src/Tree/Strategy/ORM/Nested.php | 18 +- .../RootAssociationCategoryBinaryUuid.php | 166 ++++++++++++++++++ tests/Gedmo/Tree/NestedTreePositionTest.php | 68 +++++++ 3 files changed, 247 insertions(+), 5 deletions(-) create mode 100644 tests/Gedmo/Tree/Fixture/RootAssociationCategoryBinaryUuid.php diff --git a/src/Tree/Strategy/ORM/Nested.php b/src/Tree/Strategy/ORM/Nested.php index f74e1e5cde..7585fafdfa 100644 --- a/src/Tree/Strategy/ORM/Nested.php +++ b/src/Tree/Strategy/ORM/Nested.php @@ -508,8 +508,10 @@ public function updateNode(EntityManagerInterface $em, $node, $parent, $position $qb = $em->createQueryBuilder(); $qb->update($config['useObjectClass'], 'node'); if (isset($config['root'])) { + $wrappedNewRoot = AbstractWrapper::wrap($newRoot, $em); + $newRootId = $wrappedNewRoot->getIdentifier(); $qb->set('node.'.$config['root'], ':rid'); - $qb->setParameter('rid', $newRoot); + $qb->setParameter('rid', $newRootId, $meta->getTypeOfField($identifierField)); $wrapped->setPropertyValue($config['root'], $newRoot); $em->getUnitOfWork()->setOriginalEntityProperty($oid, $config['root'], $newRoot); } @@ -522,7 +524,7 @@ public function updateNode(EntityManagerInterface $em, $node, $parent, $position $wrappedNewParent = AbstractWrapper::wrap($newParent, $em); $newParentId = $wrappedNewParent->getIdentifier(); $qb->set('node.'.$config['parent'], ':pid'); - $qb->setParameter('pid', $newParentId); + $qb->setParameter('pid', $newParentId, $meta->getTypeOfField($identifierField)); $wrapped->setPropertyValue($config['parent'], $newParent); $em->getUnitOfWork()->setOriginalEntityProperty($oid, $config['parent'], $newParent); } @@ -530,7 +532,7 @@ public function updateNode(EntityManagerInterface $em, $node, $parent, $position $qb->set('node.'.$config['right'], $right + $diff); // node id cannot be null $qb->where($qb->expr()->eq('node.'.$identifierField, ':id')); - $qb->setParameter('id', $nodeId); + $qb->setParameter('id', $nodeId, $meta->getTypeOfField($identifierField)); $qb->getQuery()->getSingleScalarResult(); $wrapped->setPropertyValue($config['left'], $left + $diff); $wrapped->setPropertyValue($config['right'], $right + $diff); @@ -589,6 +591,11 @@ public function shiftRL(EntityManagerInterface $em, $class, $first, $delta, $roo $meta = $em->getClassMetadata($class); $config = $this->listener->getConfiguration($em, $class); + if (isset($config['root']) && $root) { + $rootIdentifierField = $meta->getSingleIdentifierFieldName(); + $rootId = AbstractWrapper::wrap($root, $em)->getIdentifier(); + } + $sign = ($delta >= 0) ? ' + ' : ' - '; $absDelta = abs($delta); $qb = $em->createQueryBuilder(); @@ -597,7 +604,8 @@ public function shiftRL(EntityManagerInterface $em, $class, $first, $delta, $roo ->where($qb->expr()->gte('node.'.$config['left'], $first)); if (isset($config['root'])) { $qb->andWhere($qb->expr()->eq('node.'.$config['root'], ':rid')); - $qb->setParameter('rid', $root); + $id = $meta->getIdentifier(); + $qb->setParameter('rid', $rootId, $meta->getTypeOfField($rootIdentifierField)); } $qb->getQuery()->getSingleScalarResult(); @@ -607,7 +615,7 @@ public function shiftRL(EntityManagerInterface $em, $class, $first, $delta, $roo ->where($qb->expr()->gte('node.'.$config['right'], $first)); if (isset($config['root'])) { $qb->andWhere($qb->expr()->eq('node.'.$config['root'], ':rid')); - $qb->setParameter('rid', $root); + $qb->setParameter('rid', $rootId, $meta->getTypeOfField($rootIdentifierField)); } $qb->getQuery()->getSingleScalarResult(); diff --git a/tests/Gedmo/Tree/Fixture/RootAssociationCategoryBinaryUuid.php b/tests/Gedmo/Tree/Fixture/RootAssociationCategoryBinaryUuid.php new file mode 100644 index 0000000000..a83999c4bb --- /dev/null +++ b/tests/Gedmo/Tree/Fixture/RootAssociationCategoryBinaryUuid.php @@ -0,0 +1,166 @@ + http://www.gediminasm.org + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gedmo\Tests\Tree\Fixture; + +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping as ORM; +use Gedmo\Mapping\Annotation as Gedmo; +use Gedmo\Tree\Entity\Repository\NestedTreeRepository; +use Symfony\Bridge\Doctrine\Types\UuidType; +use Symfony\Component\Uid\Uuid; + +/** + * @ORM\Entity(repositoryClass="Gedmo\Tree\Entity\Repository\NestedTreeRepository") + * + * @Gedmo\Tree(type="nested") + */ +#[ORM\Entity(repositoryClass: NestedTreeRepository::class)] +#[Gedmo\Tree(type: 'nested')] +class RootAssociationCategoryBinaryUuid +{ + /** + * @var Collection + * + * @ORM\OneToMany(targetEntity="RootAssociationCategory", mappedBy="parent") + */ + #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] + protected $children; + /** + * @ORM\Column(name="id", type="uuid", nullable=false) + * @ORM\Id + * @ORM\GeneratedValue(strategy="NONE") + */ + #[ORM\Id] + #[ORM\GeneratedValue(strategy: 'NONE')] + #[ORM\Column(name: 'id', type: UuidType::NAME, nullable: false)] + private ?Uuid $id = null; + + /** + * @ORM\Column(name="title", type="string", length=64) + */ + #[ORM\Column(name: 'title', type: Types::STRING, length: 64)] + private ?string $title = null; + + /** + * @var int|null + * + * @Gedmo\TreeLeft + * + * @ORM\Column(name="lft", type="integer") + */ + #[ORM\Column(name: 'lft', type: Types::INTEGER)] + #[Gedmo\TreeLeft] + private int $lft; + + /** + * @var int|null + * + * @Gedmo\TreeRight + * + * @ORM\Column(name="rgt", type="integer") + */ + #[ORM\Column(name: 'rgt', type: Types::INTEGER)] + #[Gedmo\TreeRight] + private int $rgt; + + /** + * @Gedmo\TreeParent + * + * @ORM\ManyToOne(targetEntity="RootAssociationCategory", inversedBy="children") + * @ORM\JoinColumns({ + * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE") + * }) + */ + #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] + #[ORM\JoinColumn(name: 'parent_id', referencedColumnName: 'id', onDelete: 'CASCADE')] + #[Gedmo\TreeParent] + private ?RootAssociationCategoryBinaryUuid $parent = null; + + /** + * @var self|null + * + * @Gedmo\TreeRoot + * + * @ORM\ManyToOne(targetEntity="RootAssociationCategory") + * @ORM\JoinColumns({ + * @ORM\JoinColumn(name="tree_root", referencedColumnName="id", onDelete="CASCADE") + * }) + */ + #[ORM\ManyToOne(targetEntity: self::class)] + #[ORM\JoinColumn(name: 'tree_root', referencedColumnName: 'id', onDelete: 'CASCADE')] + #[Gedmo\TreeRoot] + private ?RootAssociationCategoryBinaryUuid$root; + + /** + * @var int|null + * + * @Gedmo\TreeLevel + * + * @ORM\Column(name="lvl", type="integer") + */ + #[ORM\Column(name: 'lvl', type: Types::INTEGER)] + #[Gedmo\TreeLevel] + private int $level; + + public function __construct() + { + $this->id = Uuid::v7(); + $this->children = new ArrayCollection(); + } + + public function getId(): ?Uuid + { + return $this->id; + } + + public function setTitle(?string $title): void + { + $this->title = $title; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setParent(?self $parent = null): void + { + $this->parent = $parent; + } + + public function getParent(): ?self + { + return $this->parent; + } + + public function getRoot(): ?self + { + return $this->root; + } + + public function getLeft(): ?int + { + return $this->lft; + } + + public function getRight(): ?int + { + return $this->rgt; + } + + public function getLevel(): ?int + { + return $this->level; + } +} diff --git a/tests/Gedmo/Tree/NestedTreePositionTest.php b/tests/Gedmo/Tree/NestedTreePositionTest.php index 18c73e8664..93736d9c67 100644 --- a/tests/Gedmo/Tree/NestedTreePositionTest.php +++ b/tests/Gedmo/Tree/NestedTreePositionTest.php @@ -14,6 +14,7 @@ use Doctrine\Common\EventManager; use Gedmo\Tests\Tool\BaseTestCaseORM; use Gedmo\Tests\Tree\Fixture\Category; +use Gedmo\Tests\Tree\Fixture\RootAssociationCategoryBinaryUuid; use Gedmo\Tests\Tree\Fixture\RootCategory; use Gedmo\Tree\TreeListener; @@ -26,6 +27,7 @@ final class NestedTreePositionTest extends BaseTestCaseORM { private const CATEGORY = Category::class; private const ROOT_CATEGORY = RootCategory::class; + private const ROOT_CATEGORY_BINARY_UUID = RootAssociationCategoryBinaryUuid::class; protected function setUp(): void { @@ -335,6 +337,72 @@ public function testRootTreePositionedInserts(): void static::assertTrue($repo->verify()); } + /** + * This test is the same as above (testRootTreePositionedInserts), apart testing behavior of binary column for UUID + */ + public function testRootBinaryUuidTreePositionedInserts(): void + { + $repo = $this->em->getRepository(self::ROOT_CATEGORY_BINARY_UUID); + + // test child positioned inserts + $food = new RootAssociationCategoryBinaryUuid(); + $food->setTitle('Food'); + + $fruits = new RootAssociationCategoryBinaryUuid(); + $fruits->setTitle('Fruits'); + + $vegitables = new RootAssociationCategoryBinaryUuid(); + $vegitables->setTitle('Vegitables'); + + $milk = new RootAssociationCategoryBinaryUuid(); + $milk->setTitle('Milk'); + + $meat = new RootAssociationCategoryBinaryUuid(); + $meat->setTitle('Meat'); + + $repo + ->persistAsFirstChild($food) + ->persistAsFirstChildOf($fruits, $food) + ->persistAsFirstChildOf($vegitables, $food) + ->persistAsLastChildOf($milk, $food) + ->persistAsLastChildOf($meat, $food); + + $this->em->flush(); + + static::assertSame(4, $fruits->getLeft()); + static::assertSame(5, $fruits->getRight()); + + static::assertSame(2, $vegitables->getLeft()); + static::assertSame(3, $vegitables->getRight()); + + static::assertSame(6, $milk->getLeft()); + static::assertSame(7, $milk->getRight()); + + static::assertSame(8, $meat->getLeft()); + static::assertSame(9, $meat->getRight()); + + // test sibling positioned inserts + $cookies = new RootAssociationCategoryBinaryUuid(); + $cookies->setTitle('Cookies'); + + $drinks = new RootAssociationCategoryBinaryUuid(); + $drinks->setTitle('Drinks'); + + $repo + ->persistAsNextSiblingOf($cookies, $milk) + ->persistAsPrevSiblingOf($drinks, $milk); + + $this->em->flush(); + + static::assertSame(6, $drinks->getLeft()); + static::assertSame(7, $drinks->getRight()); + + static::assertSame(10, $cookies->getLeft()); + static::assertSame(11, $cookies->getRight()); + + static::assertTrue($repo->verify()); + } + public function testRootlessTreeTopLevelInserts(): void { $repo = $this->em->getRepository(self::CATEGORY); From 61faafd09726d8873af8d2e1cfe1a24cbb0cc3aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Marodon?= Date: Mon, 16 Sep 2024 13:57:15 +0200 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=91=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérémy Marodon --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea079941e4..88c702847a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ a release. --- ## [Unreleased] +### Added +- Tree: Support of Symfony UIDs at binary format ## [3.16.1] ### Fixed From 1c3f3d21037cea270ca96c57b53d905784765d5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Marodon?= Date: Mon, 16 Sep 2024 14:36:15 +0200 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=91=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérémy Marodon --- src/Tree/Strategy/ORM/Nested.php | 18 +++++++++--------- .../RootAssociationCategoryBinaryUuid.php | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Tree/Strategy/ORM/Nested.php b/src/Tree/Strategy/ORM/Nested.php index 7585fafdfa..570adb302c 100644 --- a/src/Tree/Strategy/ORM/Nested.php +++ b/src/Tree/Strategy/ORM/Nested.php @@ -577,10 +577,10 @@ public function max(EntityManagerInterface $em, $class, $rootId = 0) /** * Shift tree left and right values by delta * - * @param string $class - * @param int $first - * @param int $delta - * @param int|string $root + * @param string $class + * @param int $first + * @param int $delta + * @param int|string|object|null $root * * @phpstan-param class-string $class * @@ -589,11 +589,11 @@ public function max(EntityManagerInterface $em, $class, $rootId = 0) public function shiftRL(EntityManagerInterface $em, $class, $first, $delta, $root = null) { $meta = $em->getClassMetadata($class); + $identifierField = $meta->getSingleIdentifierFieldName(); $config = $this->listener->getConfiguration($em, $class); - if (isset($config['root']) && $root) { - $rootIdentifierField = $meta->getSingleIdentifierFieldName(); - $rootId = AbstractWrapper::wrap($root, $em)->getIdentifier(); + if (isset($config['root']) && is_object($root)) { + $root = AbstractWrapper::wrap($root, $em)->getIdentifier(); } $sign = ($delta >= 0) ? ' + ' : ' - '; @@ -605,7 +605,7 @@ public function shiftRL(EntityManagerInterface $em, $class, $first, $delta, $roo if (isset($config['root'])) { $qb->andWhere($qb->expr()->eq('node.'.$config['root'], ':rid')); $id = $meta->getIdentifier(); - $qb->setParameter('rid', $rootId, $meta->getTypeOfField($rootIdentifierField)); + $qb->setParameter('rid', $root, $meta->getTypeOfField($identifierField)); } $qb->getQuery()->getSingleScalarResult(); @@ -615,7 +615,7 @@ public function shiftRL(EntityManagerInterface $em, $class, $first, $delta, $roo ->where($qb->expr()->gte('node.'.$config['right'], $first)); if (isset($config['root'])) { $qb->andWhere($qb->expr()->eq('node.'.$config['root'], ':rid')); - $qb->setParameter('rid', $rootId, $meta->getTypeOfField($rootIdentifierField)); + $qb->setParameter('rid', $root, $meta->getTypeOfField($identifierField)); } $qb->getQuery()->getSingleScalarResult(); diff --git a/tests/Gedmo/Tree/Fixture/RootAssociationCategoryBinaryUuid.php b/tests/Gedmo/Tree/Fixture/RootAssociationCategoryBinaryUuid.php index a83999c4bb..6e9ed27bed 100644 --- a/tests/Gedmo/Tree/Fixture/RootAssociationCategoryBinaryUuid.php +++ b/tests/Gedmo/Tree/Fixture/RootAssociationCategoryBinaryUuid.php @@ -53,7 +53,7 @@ class RootAssociationCategoryBinaryUuid private ?string $title = null; /** - * @var int|null + * @var int * * @Gedmo\TreeLeft * @@ -64,7 +64,7 @@ class RootAssociationCategoryBinaryUuid private int $lft; /** - * @var int|null + * @var int * * @Gedmo\TreeRight * @@ -100,10 +100,10 @@ class RootAssociationCategoryBinaryUuid #[ORM\ManyToOne(targetEntity: self::class)] #[ORM\JoinColumn(name: 'tree_root', referencedColumnName: 'id', onDelete: 'CASCADE')] #[Gedmo\TreeRoot] - private ?RootAssociationCategoryBinaryUuid$root; + private ?RootAssociationCategoryBinaryUuid $root = null; /** - * @var int|null + * @var int * * @Gedmo\TreeLevel * From aef8cdf9710ed710682a2e2336ea0d1bfb65aab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Marodon?= Date: Mon, 16 Sep 2024 14:38:52 +0200 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=91=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérémy Marodon --- src/Tree/Strategy/ORM/Nested.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Tree/Strategy/ORM/Nested.php b/src/Tree/Strategy/ORM/Nested.php index 570adb302c..b0e1c185c6 100644 --- a/src/Tree/Strategy/ORM/Nested.php +++ b/src/Tree/Strategy/ORM/Nested.php @@ -604,7 +604,6 @@ public function shiftRL(EntityManagerInterface $em, $class, $first, $delta, $roo ->where($qb->expr()->gte('node.'.$config['left'], $first)); if (isset($config['root'])) { $qb->andWhere($qb->expr()->eq('node.'.$config['root'], ':rid')); - $id = $meta->getIdentifier(); $qb->setParameter('rid', $root, $meta->getTypeOfField($identifierField)); } $qb->getQuery()->getSingleScalarResult();