From 436a6e8907b1387c95a08fc5233acf81930d37f8 Mon Sep 17 00:00:00 2001 From: Jeff Druery Date: Mon, 24 Jul 2023 10:33:01 -0400 Subject: [PATCH 01/20] Attempt to fix issue with multiple entities --- .devcontainer/Dockerfile | 16 +++ .devcontainer/devcontainer.json | 42 ++++++ .docker/php/Dockerfile | 2 +- composer.json | 26 ++-- src/Tree/Strategy/ORM/Closure.php | 29 ++++- .../Tree/Fixture/Issue2652/Category2.php | 122 ++++++++++++++++++ .../Fixture/Issue2652/Category2Closure.php | 46 +++++++ tests/Gedmo/Tree/Issue/Issue2652Test.php | 116 +++++++++++++++++ 8 files changed, 382 insertions(+), 17 deletions(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 tests/Gedmo/Tree/Fixture/Issue2652/Category2.php create mode 100644 tests/Gedmo/Tree/Fixture/Issue2652/Category2Closure.php create mode 100644 tests/Gedmo/Tree/Issue/Issue2652Test.php diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000000..a29091b1bd --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,16 @@ +# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.0/containers/php/.devcontainer/base.Dockerfile + +# [Choice] PHP version (use -bullseye variants on local arm64/Apple Silicon): 8, 8.1, 8.0, 7, 7.4, 7.3, 8-bullseye, 8.1-bullseye, 8.0-bullseye, 7-bullseye, 7.4-bullseye, 7.3-bullseye, 8-buster, 8.1-buster, 8.0-buster, 7-buster, 7.4-buster +ARG VARIANT="8.1-apache-bullseye" +FROM mcr.microsoft.com/vscode/devcontainers/php:0-${VARIANT} + +# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 +ARG NODE_VERSION="none" +RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + +# [Optional] Uncomment this line to install global node packages. +# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..52c5447e04 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,42 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.0/containers/php +{ + "name": "PHP", + "build": { + "dockerfile": "Dockerfile", + "args": { + // Update VARIANT to pick a PHP version: 8, 8.1, 8.0, 7, 7.4 + // Append -bullseye or -buster to pin to an OS version. + // Use -bullseye variants on local on arm64/Apple Silicon. + "VARIANT": "8.1", + "NODE_VERSION": "16" + } + }, + + // Configure tool-specific properties. + "customizations": { + // Configure properties specific to VS Code. + "vscode": { + // Set *default* container specific settings.json values on container create. + "settings": { + "php.validate.executablePath": "/usr/local/bin/php" + }, + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "xdebug.php-debug", + "bmewburn.vscode-intelephense-client", + "mrmlnc.vscode-apache" + ] + } + }, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [8080], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "sudo chmod a+x \"$(pwd)\" && sudo rm -rf /var/www/html && sudo ln -s \"$(pwd)\" /var/www/html" + + // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "vscode" +} diff --git a/.docker/php/Dockerfile b/.docker/php/Dockerfile index df45d64fac..fe70cf95c4 100644 --- a/.docker/php/Dockerfile +++ b/.docker/php/Dockerfile @@ -19,7 +19,7 @@ RUN apt-get update \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ # Install PHP extensions - && docker-php-ext-install zip \ + # && docker-php-ext-install zip \ && docker-php-ext-install pcntl \ && docker-php-ext-install bcmath \ && pecl install mongodb \ diff --git a/composer.json b/composer.json index 2f3000eac1..1b55f188fb 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ "wiki": "https://github.com/Atlantic18/DoctrineExtensions/tree/main/doc" }, "require": { - "php": "^7.4 || ^8.0", + "php": "^7.2 || ^8.2", "behat/transliterator": "^1.2", "doctrine/collections": "^1.2 || ^2.0", "doctrine/common": "^2.13 || ^3.0", @@ -57,19 +57,17 @@ "doctrine/dbal": "^3.2", "doctrine/doctrine-bundle": "^2.3", "doctrine/mongodb-odm": "^2.3", - "doctrine/orm": "^2.14.0 || ^3.0", - "friendsofphp/php-cs-fixer": "^3.14.0", - "nesbot/carbon": "^2.71 || ^3.0", - "phpstan/phpstan": "^1.11", - "phpstan/phpstan-doctrine": "^1.4", - "phpstan/phpstan-phpunit": "^1.4", - "phpunit/phpunit": "^9.6", - "rector/rector": "^1.1", - "symfony/console": "^5.4 || ^6.0 || ^7.0", - "symfony/doctrine-bridge": "^5.4 || ^6.0 || ^7.0", - "symfony/phpunit-bridge": "^6.0 || ^7.0", - "symfony/uid": "^5.4 || ^6.0 || ^7.0", - "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + "doctrine/orm": "^2.14.0", + "friendsofphp/php-cs-fixer": "^3.4.0 <3.10", + "nesbot/carbon": "^2.55", + "phpstan/phpstan": "^1.10.2", + "phpstan/phpstan-doctrine": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^8.5 || ^9.5 || ^10.0", + "rector/rector": "^0.15.20", + "symfony/console": "^4.4 || ^5.3 || ^6.0", + "symfony/phpunit-bridge": "^6.0", + "symfony/yaml": "^4.4 || ^5.3 || ^6.0" }, "conflict": { "doctrine/annotations": "<1.13 || >=3.0", diff --git a/src/Tree/Strategy/ORM/Closure.php b/src/Tree/Strategy/ORM/Closure.php index d455224df6..8bfd380118 100644 --- a/src/Tree/Strategy/ORM/Closure.php +++ b/src/Tree/Strategy/ORM/Closure.php @@ -284,8 +284,13 @@ public function processPostPersist($em, $entity, AdapterInterface $ea) { $uow = $em->getUnitOfWork(); $emHash = spl_object_id($em); + $entityClass = get_class($entity); - while ($node = array_shift($this->pendingChildNodeInserts[$emHash])) { + foreach ($this->pendingChildNodeInserts[$emHash] as $node) { + $nodeClass = get_class($node); + //print_r("$nodeClass $entityClass\n"); + if ($entityClass !== $nodeClass) continue; + unset($this->pendingChildNodeInserts[$emHash][spl_object_id($node)]); $meta = $em->getClassMetadata(get_class($node)); $config = $this->listener->getConfiguration($em, $meta->getName()); @@ -311,6 +316,13 @@ public function processPostPersist($em, $entity, AdapterInterface $ea) $depthColumnName => 0, ], ]; + $entriesMeta = [ + [ + 'class' => $closureClass, + 'ancestorColumnName' => $ancestorColumnName, + 'descendantColumnName' => $descendantColumnName, + ], + ]; if ($parent) { $dql = "SELECT c, a FROM {$closureMeta->getName()} c"; @@ -329,6 +341,11 @@ public function processPostPersist($em, $entity, AdapterInterface $ea) $descendantColumnName => $nodeId, $depthColumnName => $ancestor['depth'] + 1, ]; + $entriesMeta[] = [ + 'class' => $closureClass, + 'ancestorColumnName' => $ancestorColumnName, + 'descendantColumnName' => $descendantColumnName, + ]; } if ($mustPostpone) { @@ -348,7 +365,15 @@ public function processPostPersist($em, $entity, AdapterInterface $ea) $levelProp->setValue($node, 1); } - foreach ($entries as $closure) { + //print_r($entries); + foreach ($entries as $key => $closure) { + //$ancestorColumnName = $entriesMeta[$key]['ancestorColumnName']; + //$descendantColumnName = $entriesMeta[$key]['descendantColumnName']; + //$existing = $em->getRepository($entriesMeta[$key]['class'])->findBy([ + // $ancestorColumnName => $closure[$ancestorColumnName], + // $descendantColumnName => $closure[$descendantColumnName], + //]); + //if (count($existing)) continue; if (!$em->getConnection()->insert($closureTable, $closure)) { throw new RuntimeException('Failed to insert new Closure record'); } diff --git a/tests/Gedmo/Tree/Fixture/Issue2652/Category2.php b/tests/Gedmo/Tree/Fixture/Issue2652/Category2.php new file mode 100644 index 0000000000..cf89c44d68 --- /dev/null +++ b/tests/Gedmo/Tree/Fixture/Issue2652/Category2.php @@ -0,0 +1,122 @@ + 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\Issue2652; + +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\ClosureTreeRepository; + +/** + * @Gedmo\Tree(type="closure") + * @Gedmo\TreeClosure(class="Category2Closure") + * @ORM\Entity(repositoryClass="Gedmo\Tree\Entity\Repository\ClosureTreeRepository") + */ +#[ORM\Entity(repositoryClass: ClosureTreeRepository::class)] +#[Gedmo\Tree(type: 'closure')] +#[Gedmo\TreeClosure(class: Category2Closure::class)] +class Category2 +{ + /** + * @var int|null + * + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") + */ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: Types::INTEGER)] + private $id; + + /** + * @var string|null + * + * @ORM\Column(name="title", type="string", length=64) + */ + #[ORM\Column(name: 'title', type: Types::STRING, length: 64)] + private $title; + + /** + * @var int|null + * + * @ORM\Column(name="level", type="integer", nullable=true) + * @Gedmo\TreeLevel + */ + #[ORM\Column(name: 'level', type: Types::INTEGER, nullable: true)] + #[Gedmo\TreeLevel] + private $level; + + /** + * @var self|null + * + * @Gedmo\TreeParent + * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE") + * @ORM\ManyToOne(targetEntity="Category2", inversedBy="children") + */ + #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] + #[ORM\JoinColumn(name: 'category2_id', referencedColumnName: 'id', onDelete: 'CASCADE')] + #[Gedmo\TreeParent] + private $parent; + + /** + * @var Collection + */ + private $closures; + + public function __construct() + { + $this->closures = new ArrayCollection(); + } + + public function getId(): ?int + { + 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 addClosure(Category2Closure $closure): void + { + $this->closures[] = $closure; + } + + public function setLevel(?int $level): void + { + $this->level = $level; + } + + public function getLevel(): ?int + { + return $this->level; + } +} diff --git a/tests/Gedmo/Tree/Fixture/Issue2652/Category2Closure.php b/tests/Gedmo/Tree/Fixture/Issue2652/Category2Closure.php new file mode 100644 index 0000000000..751c00f158 --- /dev/null +++ b/tests/Gedmo/Tree/Fixture/Issue2652/Category2Closure.php @@ -0,0 +1,46 @@ + 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\Issue2652; + +use Doctrine\ORM\Mapping as ORM; +use Gedmo\Tree\Entity\MappedSuperclass\AbstractClosure; + +/** + * @ORM\Entity + * @ORM\Table( + * indexes={@ORM\Index(name="closure_category2_depth_idx", columns={"depth"})}, + * uniqueConstraints={@ORM\UniqueConstraint(name="closure_category2_unique_idx", columns={ + * "ancestor", "descendant" + * })} + * ) + */ +#[ORM\Entity] +#[ORM\UniqueConstraint(name: 'closure_category2_unique_idx', columns: ['ancestor', 'descendant'])] +#[ORM\Index(name: 'closure_category2_depth_idx', columns: ['depth'])] +class Category2Closure extends AbstractClosure +{ + /** + * @ORM\ManyToOne(targetEntity="Gedmo\Tests\Tree\Fixture\Issue2652\Category2") + * @ORM\JoinColumn(name="ancestor", referencedColumnName="id", nullable=false, onDelete="CASCADE") + */ + #[ORM\ManyToOne(targetEntity: Category2::class)] + #[ORM\JoinColumn(name: 'ancestor', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] + protected $ancestor; + + /** + * @ORM\ManyToOne(targetEntity="Gedmo\Tests\Tree\Fixture\Issue2652\Category2") + * @ORM\JoinColumn(name="descendant", referencedColumnName="id", nullable=false, onDelete="CASCADE") + */ + #[ORM\ManyToOne(targetEntity: Category2::class)] + #[ORM\JoinColumn(name: 'descendant', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] + protected $descendant; +} diff --git a/tests/Gedmo/Tree/Issue/Issue2652Test.php b/tests/Gedmo/Tree/Issue/Issue2652Test.php new file mode 100644 index 0000000000..f334d2153b --- /dev/null +++ b/tests/Gedmo/Tree/Issue/Issue2652Test.php @@ -0,0 +1,116 @@ + 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; + +use Doctrine\Common\EventManager; +use Gedmo\Exception\UnexpectedValueException; +use Gedmo\Tests\Tool\BaseTestCaseORM; +use Gedmo\Tests\Tree\Fixture\Closure\Category; +use Gedmo\Tests\Tree\Fixture\Closure\CategoryClosure; +use Gedmo\Tests\Tree\Fixture\Closure\CategoryWithoutLevel; +use Gedmo\Tests\Tree\Fixture\Closure\CategoryWithoutLevelClosure; +use Gedmo\Tests\Tree\Fixture\Closure\News; +use Gedmo\Tests\Tree\Fixture\Closure\Person; +use Gedmo\Tests\Tree\Fixture\Closure\PersonClosure; +use Gedmo\Tests\Tree\Fixture\Closure\User; +use Gedmo\Tree\Strategy\ORM\Closure; +use Gedmo\Tree\TreeListener; + +use Gedmo\Tests\Tree\Fixture\Issue2652\Category2; +use Gedmo\Tests\Tree\Fixture\Issue2652\Category2Closure; + +/** + * These are tests for Tree behavior + * + * @author Gustavo Adrian + * @author Gediminas Morkevicius + */ +final class Issue2652Test extends BaseTestCaseORM +{ + public const CATEGORY = Category::class; + public const CLOSURE = CategoryClosure::class; + public const PERSON = Person::class; + public const USER = User::class; + public const PERSON_CLOSURE = PersonClosure::class; + public const NEWS = News::class; + public const CATEGORY_WITHOUT_LEVEL = CategoryWithoutLevel::class; + public const CATEGORY_WITHOUT_LEVEL_CLOSURE = CategoryWithoutLevelClosure::class; + + public const CATEGORY2 = Category2::class; + public const CLOSURE2 = Category2Closure::class; + + /** + * @var TreeListener + */ + protected $listener; + + protected function setUp(): void + { + parent::setUp(); + + $this->listener = new TreeListener(); + + $evm = new EventManager(); + $evm->addEventSubscriber($this->listener); + + $this->getDefaultMockSqliteEntityManager($evm); + $this->populate(); + } + + public function testAddMultipleEntityTypes(): void + { + $repo = $this->em->getRepository(self::CATEGORY); + + $food = $repo->findOneBy(['title' => 'Food']); + print_r($food); + $dql = 'SELECT c FROM '.self::CLOSURE.' c'; + $dql .= ' WHERE c.ancestor = :ancestor'; + $query = $this->em->createQuery($dql); + $query->setParameter('ancestor', $food); + + $foodClosures = $query->getResult(); + static::assertCount(1, $foodClosures); + + $repo = $this->em->getRepository(self::CATEGORY2); + $monkey = $repo->findOneBy(['title' => 'Monkey']); + $dql = 'SELECT c FROM '.self::CLOSURE2.' c'; + $dql .= ' WHERE c.ancestor = :ancestor'; + $query = $this->em->createQuery($dql); + $query->setParameter('ancestor', $monkey); + + $monkeyClosures = $query->getResult(); + static::assertCount(1, $monkeyClosures); + } + + protected function getUsedEntityFixtures(): array + { + return [ + self::CATEGORY, + self::CLOSURE, + self::CATEGORY2, + self::CLOSURE2, + ]; + } + + private function populate(): void + { + $food = new Category(); + $food->setTitle('Food'); + $this->em->persist($food); + + $monkey = new Category2(); + $monkey->setTitle('Monkey'); + $this->em->persist($monkey); + + $this->em->flush(); + } +} From 30bbe08d259732427dd9f21674ef103588a66975 Mon Sep 17 00:00:00 2001 From: Jeff Druery Date: Mon, 24 Jul 2023 11:04:00 -0400 Subject: [PATCH 02/20] Issue #2652 fix --- src/Tree/Strategy/ORM/Closure.php | 16 +++++++--------- tests/Gedmo/Tree/Issue/Issue2652Test.php | 18 ++---------------- 2 files changed, 9 insertions(+), 25 deletions(-) diff --git a/src/Tree/Strategy/ORM/Closure.php b/src/Tree/Strategy/ORM/Closure.php index 8bfd380118..54a0c8d58b 100644 --- a/src/Tree/Strategy/ORM/Closure.php +++ b/src/Tree/Strategy/ORM/Closure.php @@ -288,7 +288,6 @@ public function processPostPersist($em, $entity, AdapterInterface $ea) foreach ($this->pendingChildNodeInserts[$emHash] as $node) { $nodeClass = get_class($node); - //print_r("$nodeClass $entityClass\n"); if ($entityClass !== $nodeClass) continue; unset($this->pendingChildNodeInserts[$emHash][spl_object_id($node)]); $meta = $em->getClassMetadata(get_class($node)); @@ -365,15 +364,14 @@ public function processPostPersist($em, $entity, AdapterInterface $ea) $levelProp->setValue($node, 1); } - //print_r($entries); foreach ($entries as $key => $closure) { - //$ancestorColumnName = $entriesMeta[$key]['ancestorColumnName']; - //$descendantColumnName = $entriesMeta[$key]['descendantColumnName']; - //$existing = $em->getRepository($entriesMeta[$key]['class'])->findBy([ - // $ancestorColumnName => $closure[$ancestorColumnName], - // $descendantColumnName => $closure[$descendantColumnName], - //]); - //if (count($existing)) continue; + $ancestorColumnName = $entriesMeta[$key]['ancestorColumnName']; + $descendantColumnName = $entriesMeta[$key]['descendantColumnName']; + $existing = $em->getRepository($entriesMeta[$key]['class'])->findBy([ + $ancestorColumnName => $closure[$ancestorColumnName], + $descendantColumnName => $closure[$descendantColumnName], + ]); + if (count($existing)) continue; if (!$em->getConnection()->insert($closureTable, $closure)) { throw new RuntimeException('Failed to insert new Closure record'); } diff --git a/tests/Gedmo/Tree/Issue/Issue2652Test.php b/tests/Gedmo/Tree/Issue/Issue2652Test.php index f334d2153b..fd61e97d76 100644 --- a/tests/Gedmo/Tree/Issue/Issue2652Test.php +++ b/tests/Gedmo/Tree/Issue/Issue2652Test.php @@ -16,17 +16,11 @@ use Gedmo\Tests\Tool\BaseTestCaseORM; use Gedmo\Tests\Tree\Fixture\Closure\Category; use Gedmo\Tests\Tree\Fixture\Closure\CategoryClosure; -use Gedmo\Tests\Tree\Fixture\Closure\CategoryWithoutLevel; -use Gedmo\Tests\Tree\Fixture\Closure\CategoryWithoutLevelClosure; -use Gedmo\Tests\Tree\Fixture\Closure\News; -use Gedmo\Tests\Tree\Fixture\Closure\Person; -use Gedmo\Tests\Tree\Fixture\Closure\PersonClosure; -use Gedmo\Tests\Tree\Fixture\Closure\User; +use Gedmo\Tests\Tree\Fixture\Issue2652\Category2; +use Gedmo\Tests\Tree\Fixture\Issue2652\Category2Closure; use Gedmo\Tree\Strategy\ORM\Closure; use Gedmo\Tree\TreeListener; -use Gedmo\Tests\Tree\Fixture\Issue2652\Category2; -use Gedmo\Tests\Tree\Fixture\Issue2652\Category2Closure; /** * These are tests for Tree behavior @@ -38,13 +32,6 @@ final class Issue2652Test extends BaseTestCaseORM { public const CATEGORY = Category::class; public const CLOSURE = CategoryClosure::class; - public const PERSON = Person::class; - public const USER = User::class; - public const PERSON_CLOSURE = PersonClosure::class; - public const NEWS = News::class; - public const CATEGORY_WITHOUT_LEVEL = CategoryWithoutLevel::class; - public const CATEGORY_WITHOUT_LEVEL_CLOSURE = CategoryWithoutLevelClosure::class; - public const CATEGORY2 = Category2::class; public const CLOSURE2 = Category2Closure::class; @@ -71,7 +58,6 @@ public function testAddMultipleEntityTypes(): void $repo = $this->em->getRepository(self::CATEGORY); $food = $repo->findOneBy(['title' => 'Food']); - print_r($food); $dql = 'SELECT c FROM '.self::CLOSURE.' c'; $dql .= ' WHERE c.ancestor = :ancestor'; $query = $this->em->createQuery($dql); From a387a46f85e7fc5021abc03409e977ce8e55e3a7 Mon Sep 17 00:00:00 2001 From: Jeff Druery Date: Mon, 24 Jul 2023 11:11:48 -0400 Subject: [PATCH 03/20] Revert versions to original --- .devcontainer/Dockerfile | 16 ------------- .devcontainer/devcontainer.json | 42 --------------------------------- .docker/php/Dockerfile | 26 -------------------- composer.json | 4 ++-- 4 files changed, 2 insertions(+), 86 deletions(-) delete mode 100644 .devcontainer/Dockerfile delete mode 100644 .devcontainer/devcontainer.json delete mode 100644 .docker/php/Dockerfile diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index a29091b1bd..0000000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.0/containers/php/.devcontainer/base.Dockerfile - -# [Choice] PHP version (use -bullseye variants on local arm64/Apple Silicon): 8, 8.1, 8.0, 7, 7.4, 7.3, 8-bullseye, 8.1-bullseye, 8.0-bullseye, 7-bullseye, 7.4-bullseye, 7.3-bullseye, 8-buster, 8.1-buster, 8.0-buster, 7-buster, 7.4-buster -ARG VARIANT="8.1-apache-bullseye" -FROM mcr.microsoft.com/vscode/devcontainers/php:0-${VARIANT} - -# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 -ARG NODE_VERSION="none" -RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi - -# [Optional] Uncomment this section to install additional OS packages. -# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ -# && apt-get -y install --no-install-recommends - -# [Optional] Uncomment this line to install global node packages. -# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 52c5447e04..0000000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,42 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: -// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.0/containers/php -{ - "name": "PHP", - "build": { - "dockerfile": "Dockerfile", - "args": { - // Update VARIANT to pick a PHP version: 8, 8.1, 8.0, 7, 7.4 - // Append -bullseye or -buster to pin to an OS version. - // Use -bullseye variants on local on arm64/Apple Silicon. - "VARIANT": "8.1", - "NODE_VERSION": "16" - } - }, - - // Configure tool-specific properties. - "customizations": { - // Configure properties specific to VS Code. - "vscode": { - // Set *default* container specific settings.json values on container create. - "settings": { - "php.validate.executablePath": "/usr/local/bin/php" - }, - - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "xdebug.php-debug", - "bmewburn.vscode-intelephense-client", - "mrmlnc.vscode-apache" - ] - } - }, - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - "forwardPorts": [8080], - - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "sudo chmod a+x \"$(pwd)\" && sudo rm -rf /var/www/html && sudo ln -s \"$(pwd)\" /var/www/html" - - // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. - "remoteUser": "vscode" -} diff --git a/.docker/php/Dockerfile b/.docker/php/Dockerfile deleted file mode 100644 index fe70cf95c4..0000000000 --- a/.docker/php/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -# syntax=docker/dockerfile:1 - -ARG PHP_VERSION=8.2-cli - -FROM composer:2 AS composer - -FROM php:$PHP_VERSION AS php - -COPY --from=composer /usr/bin/composer /usr/bin/composer - -RUN apt-get update \ - && apt-get satisfy -qq --yes --no-install-recommends \ - "git (>= 1:2.30.2), git (<< 1:3), \ - libzip-dev (>= 1.7.3), libzip-dev (<< 2), \ - unzip (>= 6), unzip (<< 7), \ - zip (>= 3), zip (<< 4), \ - zlib1g-dev (>= 1:1.2.11.dfsg), zlib1g-dev (<< 1:2)" \ - && apt-get -y autoremove \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ - # Install PHP extensions - # && docker-php-ext-install zip \ - && docker-php-ext-install pcntl \ - && docker-php-ext-install bcmath \ - && pecl install mongodb \ - && docker-php-ext-enable mongodb diff --git a/composer.json b/composer.json index 1b55f188fb..813e101e83 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ "wiki": "https://github.com/Atlantic18/DoctrineExtensions/tree/main/doc" }, "require": { - "php": "^7.2 || ^8.2", + "php": "^7.2 || ^8.0", "behat/transliterator": "^1.2", "doctrine/collections": "^1.2 || ^2.0", "doctrine/common": "^2.13 || ^3.0", @@ -63,7 +63,7 @@ "phpstan/phpstan": "^1.10.2", "phpstan/phpstan-doctrine": "^1.0", "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^8.5 || ^9.5 || ^10.0", + "phpunit/phpunit": "^8.5 || ^9.5", "rector/rector": "^0.15.20", "symfony/console": "^4.4 || ^5.3 || ^6.0", "symfony/phpunit-bridge": "^6.0", From bc9d5f649ffb2f5cb4b31c61b40f4a179613613d Mon Sep 17 00:00:00 2001 From: Jeff Druery Date: Mon, 24 Jul 2023 19:49:46 -0400 Subject: [PATCH 04/20] Conform to coding standards --- src/Tree/Strategy/ORM/Closure.php | 8 ++++++-- tests/Gedmo/Tree/Issue/Issue2652Test.php | 2 -- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Tree/Strategy/ORM/Closure.php b/src/Tree/Strategy/ORM/Closure.php index 54a0c8d58b..30c89cba50 100644 --- a/src/Tree/Strategy/ORM/Closure.php +++ b/src/Tree/Strategy/ORM/Closure.php @@ -288,7 +288,9 @@ public function processPostPersist($em, $entity, AdapterInterface $ea) foreach ($this->pendingChildNodeInserts[$emHash] as $node) { $nodeClass = get_class($node); - if ($entityClass !== $nodeClass) continue; + if ($entityClass !== $nodeClass) { + continue; + } unset($this->pendingChildNodeInserts[$emHash][spl_object_id($node)]); $meta = $em->getClassMetadata(get_class($node)); $config = $this->listener->getConfiguration($em, $meta->getName()); @@ -371,7 +373,9 @@ public function processPostPersist($em, $entity, AdapterInterface $ea) $ancestorColumnName => $closure[$ancestorColumnName], $descendantColumnName => $closure[$descendantColumnName], ]); - if (count($existing)) continue; + if (count($existing)) { + continue; + } if (!$em->getConnection()->insert($closureTable, $closure)) { throw new RuntimeException('Failed to insert new Closure record'); } diff --git a/tests/Gedmo/Tree/Issue/Issue2652Test.php b/tests/Gedmo/Tree/Issue/Issue2652Test.php index fd61e97d76..05190064e0 100644 --- a/tests/Gedmo/Tree/Issue/Issue2652Test.php +++ b/tests/Gedmo/Tree/Issue/Issue2652Test.php @@ -12,13 +12,11 @@ namespace Gedmo\Tests\Tree; use Doctrine\Common\EventManager; -use Gedmo\Exception\UnexpectedValueException; use Gedmo\Tests\Tool\BaseTestCaseORM; use Gedmo\Tests\Tree\Fixture\Closure\Category; use Gedmo\Tests\Tree\Fixture\Closure\CategoryClosure; use Gedmo\Tests\Tree\Fixture\Issue2652\Category2; use Gedmo\Tests\Tree\Fixture\Issue2652\Category2Closure; -use Gedmo\Tree\Strategy\ORM\Closure; use Gedmo\Tree\TreeListener; From 24d48351aa07516aebe6fdba5a0e0b2fbeca26b2 Mon Sep 17 00:00:00 2001 From: Jeff Druery Date: Tue, 25 Jul 2023 07:01:38 -0400 Subject: [PATCH 05/20] Recommended updates after review by maintainer --- src/Tree/Strategy/ORM/Closure.php | 7 +++++-- tests/Gedmo/Tree/Fixture/Issue2652/Category2.php | 2 +- tests/Gedmo/Tree/Issue/Issue2652Test.php | 11 +++++------ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Tree/Strategy/ORM/Closure.php b/src/Tree/Strategy/ORM/Closure.php index 30c89cba50..abe3e4969d 100644 --- a/src/Tree/Strategy/ORM/Closure.php +++ b/src/Tree/Strategy/ORM/Closure.php @@ -289,8 +289,11 @@ public function processPostPersist($em, $entity, AdapterInterface $ea) foreach ($this->pendingChildNodeInserts[$emHash] as $node) { $nodeClass = get_class($node); if ($entityClass !== $nodeClass) { + // Do not update if it's a different type of entity from what has been persisted + // otherwise it's not guaranteed that the entity has actually been persisted continue; } + unset($this->pendingChildNodeInserts[$emHash][spl_object_id($node)]); $meta = $em->getClassMetadata(get_class($node)); $config = $this->listener->getConfiguration($em, $meta->getName()); @@ -369,11 +372,11 @@ public function processPostPersist($em, $entity, AdapterInterface $ea) foreach ($entries as $key => $closure) { $ancestorColumnName = $entriesMeta[$key]['ancestorColumnName']; $descendantColumnName = $entriesMeta[$key]['descendantColumnName']; - $existing = $em->getRepository($entriesMeta[$key]['class'])->findBy([ + $existingClosure = $em->getRepository($entriesMeta[$key]['class'])->findBy([ $ancestorColumnName => $closure[$ancestorColumnName], $descendantColumnName => $closure[$descendantColumnName], ]); - if (count($existing)) { + if ([] !== $existingClosure) { continue; } if (!$em->getConnection()->insert($closureTable, $closure)) { diff --git a/tests/Gedmo/Tree/Fixture/Issue2652/Category2.php b/tests/Gedmo/Tree/Fixture/Issue2652/Category2.php index cf89c44d68..ebe42d58cf 100644 --- a/tests/Gedmo/Tree/Fixture/Issue2652/Category2.php +++ b/tests/Gedmo/Tree/Fixture/Issue2652/Category2.php @@ -95,7 +95,7 @@ public function getTitle(): ?string return $this->title; } - public function setParent(self $parent = null): void + public function setParent(?self $parent = null): void { $this->parent = $parent; } diff --git a/tests/Gedmo/Tree/Issue/Issue2652Test.php b/tests/Gedmo/Tree/Issue/Issue2652Test.php index 05190064e0..6c486979f0 100644 --- a/tests/Gedmo/Tree/Issue/Issue2652Test.php +++ b/tests/Gedmo/Tree/Issue/Issue2652Test.php @@ -19,7 +19,6 @@ use Gedmo\Tests\Tree\Fixture\Issue2652\Category2Closure; use Gedmo\Tree\TreeListener; - /** * These are tests for Tree behavior * @@ -28,15 +27,15 @@ */ final class Issue2652Test extends BaseTestCaseORM { - public const CATEGORY = Category::class; - public const CLOSURE = CategoryClosure::class; - public const CATEGORY2 = Category2::class; - public const CLOSURE2 = Category2Closure::class; + private const CATEGORY = Category::class; + private const CLOSURE = CategoryClosure::class; + private const CATEGORY2 = Category2::class; + private const CLOSURE2 = Category2Closure::class; /** * @var TreeListener */ - protected $listener; + private $listener; protected function setUp(): void { From b414efb346500c2d24a2b292565d89b884e5bd8a Mon Sep 17 00:00:00 2001 From: Jeff Druery Date: Tue, 25 Jul 2023 08:52:03 -0400 Subject: [PATCH 06/20] Remove unneeded code --- src/Tree/Strategy/ORM/Closure.php | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/Tree/Strategy/ORM/Closure.php b/src/Tree/Strategy/ORM/Closure.php index abe3e4969d..d218a272cf 100644 --- a/src/Tree/Strategy/ORM/Closure.php +++ b/src/Tree/Strategy/ORM/Closure.php @@ -320,13 +320,6 @@ public function processPostPersist($em, $entity, AdapterInterface $ea) $depthColumnName => 0, ], ]; - $entriesMeta = [ - [ - 'class' => $closureClass, - 'ancestorColumnName' => $ancestorColumnName, - 'descendantColumnName' => $descendantColumnName, - ], - ]; if ($parent) { $dql = "SELECT c, a FROM {$closureMeta->getName()} c"; @@ -345,11 +338,6 @@ public function processPostPersist($em, $entity, AdapterInterface $ea) $descendantColumnName => $nodeId, $depthColumnName => $ancestor['depth'] + 1, ]; - $entriesMeta[] = [ - 'class' => $closureClass, - 'ancestorColumnName' => $ancestorColumnName, - 'descendantColumnName' => $descendantColumnName, - ]; } if ($mustPostpone) { @@ -369,10 +357,8 @@ public function processPostPersist($em, $entity, AdapterInterface $ea) $levelProp->setValue($node, 1); } - foreach ($entries as $key => $closure) { - $ancestorColumnName = $entriesMeta[$key]['ancestorColumnName']; - $descendantColumnName = $entriesMeta[$key]['descendantColumnName']; - $existingClosure = $em->getRepository($entriesMeta[$key]['class'])->findBy([ + foreach ($entries as $closure) { + $existingClosure = $em->getRepository($closureClass)->findBy([ $ancestorColumnName => $closure[$ancestorColumnName], $descendantColumnName => $closure[$descendantColumnName], ]); From 83243d423c460522740463906fd0c1f111d76ce0 Mon Sep 17 00:00:00 2001 From: Jeff Druery Date: Tue, 25 Jul 2023 10:05:01 -0400 Subject: [PATCH 07/20] Make comment more clear --- src/Tree/Strategy/ORM/Closure.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tree/Strategy/ORM/Closure.php b/src/Tree/Strategy/ORM/Closure.php index d218a272cf..5f22b4f1c1 100644 --- a/src/Tree/Strategy/ORM/Closure.php +++ b/src/Tree/Strategy/ORM/Closure.php @@ -289,8 +289,8 @@ public function processPostPersist($em, $entity, AdapterInterface $ea) foreach ($this->pendingChildNodeInserts[$emHash] as $node) { $nodeClass = get_class($node); if ($entityClass !== $nodeClass) { - // Do not update if it's a different type of entity from what has been persisted - // otherwise it's not guaranteed that the entity has actually been persisted + // Do not update if this node is a different type of entity from what has been persisted + // otherwise it's not guaranteed that the node has actually been persisted continue; } From e394706cfe22bd87dffe02b2b748f555f62c018b Mon Sep 17 00:00:00 2001 From: Jeff Druery Date: Fri, 28 Jul 2023 09:37:45 -0400 Subject: [PATCH 08/20] Revert to original file --- .docker/php/Dockerfile | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .docker/php/Dockerfile diff --git a/.docker/php/Dockerfile b/.docker/php/Dockerfile new file mode 100644 index 0000000000..71cfdd558e --- /dev/null +++ b/.docker/php/Dockerfile @@ -0,0 +1,26 @@ +# syntax=docker/dockerfile:1 + +ARG PHP_VERSION=8.2-cli + +FROM composer:2 AS composer + +FROM php:$PHP_VERSION AS php + +COPY --from=composer /usr/bin/composer /usr/bin/composer + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + git=1:2.30.2-1 \ + libzip-dev=1.7.3-1 \ + unzip=6.0-26+deb11u1 \ + zip=3.0-12 \ + zlib1g-dev=1:1.2.11.dfsg-2+deb11u2 \ + && apt-get -y autoremove \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ + # Install PHP extensions + && docker-php-ext-install zip \ + && docker-php-ext-install pcntl \ + && docker-php-ext-install bcmath \ + && pecl install mongodb \ + && docker-php-ext-enable mongodb From f84b6e3dd8d495a41a30b7e8bb3b8480d0aea4af Mon Sep 17 00:00:00 2001 From: Jeff Druery Date: Sun, 30 Jul 2023 17:37:38 -0400 Subject: [PATCH 09/20] Clarify reason for unset of pendingChildNodeInserts --- src/Tree/Strategy/ORM/Closure.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Tree/Strategy/ORM/Closure.php b/src/Tree/Strategy/ORM/Closure.php index 5f22b4f1c1..35b526db6b 100644 --- a/src/Tree/Strategy/ORM/Closure.php +++ b/src/Tree/Strategy/ORM/Closure.php @@ -294,7 +294,9 @@ public function processPostPersist($em, $entity, AdapterInterface $ea) continue; } + // The closure for this node will now be inserted. Remove the node from the list of pending inserts to indicate this. unset($this->pendingChildNodeInserts[$emHash][spl_object_id($node)]); + $meta = $em->getClassMetadata(get_class($node)); $config = $this->listener->getConfiguration($em, $meta->getName()); From 7fc5196893d76cbc370e6e96e93665c478fe77b9 Mon Sep 17 00:00:00 2001 From: Jeff Druery Date: Mon, 31 Jul 2023 07:45:30 -0400 Subject: [PATCH 10/20] Add changelog note for #2653 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea079941e4..4abd3a9f8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,7 @@ a release. ### Fixed - References: fixed condition in `XML` Driver that did not allow to retrieve from the entity definition the `mappedBy` and `inversedBy` fields. - Fix bug collecting metadata for inherited mapped classes +- Tree: Fix issue with null ids inserted into closure table when persisting more than one type of entity using the closure strategy (#2653) ## [3.12.0] - 2023-07-08 ### Added From 0cd11418caf0854000cbad700cf6c7aba159a158 Mon Sep 17 00:00:00 2001 From: Jeff Druery Date: Mon, 14 Aug 2023 09:36:53 -0400 Subject: [PATCH 11/20] Ensure the node is persisted before inserting closure --- src/Tree/Strategy/ORM/Closure.php | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Tree/Strategy/ORM/Closure.php b/src/Tree/Strategy/ORM/Closure.php index 35b526db6b..3f6ea4a0f2 100644 --- a/src/Tree/Strategy/ORM/Closure.php +++ b/src/Tree/Strategy/ORM/Closure.php @@ -284,24 +284,21 @@ public function processPostPersist($em, $entity, AdapterInterface $ea) { $uow = $em->getUnitOfWork(); $emHash = spl_object_id($em); - $entityClass = get_class($entity); foreach ($this->pendingChildNodeInserts[$emHash] as $node) { - $nodeClass = get_class($node); - if ($entityClass !== $nodeClass) { - // Do not update if this node is a different type of entity from what has been persisted - // otherwise it's not guaranteed that the node has actually been persisted + $meta = $em->getClassMetadata(get_class($node)); + $identifier = $meta->getSingleIdentifierFieldName(); + $nodeId = $meta->getReflectionProperty($identifier)->getValue($node); + + if (is_null($nodeId)) { + // Do not update if the node has not been persisted yet continue; } // The closure for this node will now be inserted. Remove the node from the list of pending inserts to indicate this. unset($this->pendingChildNodeInserts[$emHash][spl_object_id($node)]); - $meta = $em->getClassMetadata(get_class($node)); $config = $this->listener->getConfiguration($em, $meta->getName()); - - $identifier = $meta->getSingleIdentifierFieldName(); - $nodeId = $meta->getReflectionProperty($identifier)->getValue($node); $parent = $meta->getReflectionProperty($config['parent'])->getValue($node); $closureClass = $config['closure']; From c83d25937ee36c1ae0fd1d72a7d1dfd6b770b532 Mon Sep 17 00:00:00 2001 From: Jeff Druery Date: Fri, 8 Sep 2023 16:01:25 -0400 Subject: [PATCH 12/20] Use null === instead is_null --- src/Tree/Strategy/ORM/Closure.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tree/Strategy/ORM/Closure.php b/src/Tree/Strategy/ORM/Closure.php index 3f6ea4a0f2..5bdc5bf593 100644 --- a/src/Tree/Strategy/ORM/Closure.php +++ b/src/Tree/Strategy/ORM/Closure.php @@ -290,8 +290,8 @@ public function processPostPersist($em, $entity, AdapterInterface $ea) $identifier = $meta->getSingleIdentifierFieldName(); $nodeId = $meta->getReflectionProperty($identifier)->getValue($node); - if (is_null($nodeId)) { - // Do not update if the node has not been persisted yet + if (null === $nodeId) { + // Do not update if the node has not been persisted yet. continue; } From 65ebca2befc1b75fe2cd4e7159f20476d017cf61 Mon Sep 17 00:00:00 2001 From: JDruery Date: Tue, 12 Sep 2023 08:23:57 -0400 Subject: [PATCH 13/20] revert to upstream --- .docker/php/Dockerfile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.docker/php/Dockerfile b/.docker/php/Dockerfile index 71cfdd558e..df45d64fac 100644 --- a/.docker/php/Dockerfile +++ b/.docker/php/Dockerfile @@ -9,12 +9,12 @@ FROM php:$PHP_VERSION AS php COPY --from=composer /usr/bin/composer /usr/bin/composer RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - git=1:2.30.2-1 \ - libzip-dev=1.7.3-1 \ - unzip=6.0-26+deb11u1 \ - zip=3.0-12 \ - zlib1g-dev=1:1.2.11.dfsg-2+deb11u2 \ + && apt-get satisfy -qq --yes --no-install-recommends \ + "git (>= 1:2.30.2), git (<< 1:3), \ + libzip-dev (>= 1.7.3), libzip-dev (<< 2), \ + unzip (>= 6), unzip (<< 7), \ + zip (>= 3), zip (<< 4), \ + zlib1g-dev (>= 1:1.2.11.dfsg), zlib1g-dev (<< 1:2)" \ && apt-get -y autoremove \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ From c338cc605f40732d8fb0b2bb3b11043a6ba84c0e Mon Sep 17 00:00:00 2001 From: JDruery Date: Fri, 27 Oct 2023 15:20:21 -0400 Subject: [PATCH 14/20] Process node types one at a time in setLevelFieldOnPendingNodes --- src/Tree/Strategy/ORM/Closure.php | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Tree/Strategy/ORM/Closure.php b/src/Tree/Strategy/ORM/Closure.php index 5bdc5bf593..73541428a0 100644 --- a/src/Tree/Strategy/ORM/Closure.php +++ b/src/Tree/Strategy/ORM/Closure.php @@ -515,7 +515,8 @@ protected function getJoinColumnFieldName($association) */ protected function setLevelFieldOnPendingNodes(ObjectManager $em) { - if (!empty($this->pendingNodesLevelProcess)) { + while (!empty($this->pendingNodesLevelProcess)) { + // Nodes need to be processed one class at a time. Each iteration through the while loop will process one type, starting with the first item on the list. $first = array_slice($this->pendingNodesLevelProcess, 0, 1); $first = array_shift($first); @@ -531,6 +532,10 @@ protected function setLevelFieldOnPendingNodes(ObjectManager $em) $uow = $em->getUnitOfWork(); foreach ($this->pendingNodesLevelProcess as $node) { + if (get_class($node) !== $className) { + // Only process nodes of the same time as the first element + continue; + } $children = $em->getRepository($meta->getName())->children($node); foreach ($children as $child) { @@ -558,6 +563,14 @@ protected function setLevelFieldOnPendingNodes(ObjectManager $em) // Now we update levels foreach ($this->pendingNodesLevelProcess as $nodeId => $node) { + if (get_class($node) !== $className) { + // Only process nodes of the same time as the first element + continue; + } + + // This node will now be processed. Therefore, remove it from the list of pending nodes. + unset($this->pendingNodesLevelProcess[$nodeId]); + // Update new level $level = $levels[$nodeId]; $levelProp = $meta->getReflectionProperty($config['level']); @@ -570,8 +583,6 @@ protected function setLevelFieldOnPendingNodes(ObjectManager $em) $levelProp->setValue($node, $level); $uow->setOriginalEntityProperty(spl_object_id($node), $config['level'], $level); } - - $this->pendingNodesLevelProcess = []; } } From 82fd6b2102c8b33fef2ad7cc87c7dd592c8b5375 Mon Sep 17 00:00:00 2001 From: JDruery Date: Fri, 27 Oct 2023 15:33:38 -0400 Subject: [PATCH 15/20] Missing $className variable --- src/Tree/Strategy/ORM/Closure.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Tree/Strategy/ORM/Closure.php b/src/Tree/Strategy/ORM/Closure.php index 73541428a0..d6b83a70db 100644 --- a/src/Tree/Strategy/ORM/Closure.php +++ b/src/Tree/Strategy/ORM/Closure.php @@ -522,7 +522,8 @@ protected function setLevelFieldOnPendingNodes(ObjectManager $em) assert(null !== $first); - $meta = $em->getClassMetadata(get_class($first)); + $className = get_class($first); + $meta = $em->getClassMetadata($className); unset($first); $identifier = $meta->getIdentifier(); $mapping = $meta->getFieldMapping($identifier[0]); From 2be48b9ccc8097fd728d8520130e45627acb6815 Mon Sep 17 00:00:00 2001 From: Jeff Druery Date: Mon, 30 Oct 2023 09:28:18 -0400 Subject: [PATCH 16/20] Correct typo in comment --- src/Tree/Strategy/ORM/Closure.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tree/Strategy/ORM/Closure.php b/src/Tree/Strategy/ORM/Closure.php index d6b83a70db..b836537d01 100644 --- a/src/Tree/Strategy/ORM/Closure.php +++ b/src/Tree/Strategy/ORM/Closure.php @@ -534,7 +534,7 @@ protected function setLevelFieldOnPendingNodes(ObjectManager $em) foreach ($this->pendingNodesLevelProcess as $node) { if (get_class($node) !== $className) { - // Only process nodes of the same time as the first element + // Only process nodes of the same type as the first element continue; } $children = $em->getRepository($meta->getName())->children($node); From 29fd9bf26dfe819a84b3b63607307c782aa94751 Mon Sep 17 00:00:00 2001 From: Jeff Druery Date: Mon, 30 Oct 2023 10:14:43 -0400 Subject: [PATCH 17/20] Modify test for code coverage --- tests/Gedmo/Tree/Issue/Issue2652Test.php | 26 +++++++++++------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/tests/Gedmo/Tree/Issue/Issue2652Test.php b/tests/Gedmo/Tree/Issue/Issue2652Test.php index 6c486979f0..6e8b7b1a60 100644 --- a/tests/Gedmo/Tree/Issue/Issue2652Test.php +++ b/tests/Gedmo/Tree/Issue/Issue2652Test.php @@ -47,11 +47,22 @@ protected function setUp(): void $evm->addEventSubscriber($this->listener); $this->getDefaultMockSqliteEntityManager($evm); - $this->populate(); } public function testAddMultipleEntityTypes(): void { + $food = new Category(); + $food->setTitle('Food'); + $this->em->persist($food); + + $monkey = new Category2(); + $monkey->setTitle('Monkey'); + $this->em->persist($monkey); + + // Before issue #2652 was fixed, null values would be inserted into the closure table at this next line and an exception would be thrown + $this->em->flush(); + + // Ensure that the closures were correctly inserted $repo = $this->em->getRepository(self::CATEGORY); $food = $repo->findOneBy(['title' => 'Food']); @@ -83,17 +94,4 @@ protected function getUsedEntityFixtures(): array self::CLOSURE2, ]; } - - private function populate(): void - { - $food = new Category(); - $food->setTitle('Food'); - $this->em->persist($food); - - $monkey = new Category2(); - $monkey->setTitle('Monkey'); - $this->em->persist($monkey); - - $this->em->flush(); - } } From eac3291cb319daa63253ef344876d36b8adc8e2b Mon Sep 17 00:00:00 2001 From: JDruery Date: Tue, 7 Nov 2023 16:14:47 -0500 Subject: [PATCH 18/20] Comply with coding standards --- src/Tree/Strategy/ORM/Closure.php | 2 +- tests/Gedmo/Tree/Fixture/Issue2652/Category2.php | 15 ++++++--------- tests/Gedmo/Tree/Issue/Issue2652Test.php | 5 +---- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/Tree/Strategy/ORM/Closure.php b/src/Tree/Strategy/ORM/Closure.php index b836537d01..79fdad74c4 100644 --- a/src/Tree/Strategy/ORM/Closure.php +++ b/src/Tree/Strategy/ORM/Closure.php @@ -516,7 +516,7 @@ protected function getJoinColumnFieldName($association) protected function setLevelFieldOnPendingNodes(ObjectManager $em) { while (!empty($this->pendingNodesLevelProcess)) { - // Nodes need to be processed one class at a time. Each iteration through the while loop will process one type, starting with the first item on the list. + // Nodes need to be processed one class at a time. Each iteration through the while loop will process one type, starting with the first item on the list. $first = array_slice($this->pendingNodesLevelProcess, 0, 1); $first = array_shift($first); diff --git a/tests/Gedmo/Tree/Fixture/Issue2652/Category2.php b/tests/Gedmo/Tree/Fixture/Issue2652/Category2.php index ebe42d58cf..50e6e67642 100644 --- a/tests/Gedmo/Tree/Fixture/Issue2652/Category2.php +++ b/tests/Gedmo/Tree/Fixture/Issue2652/Category2.php @@ -21,6 +21,7 @@ /** * @Gedmo\Tree(type="closure") * @Gedmo\TreeClosure(class="Category2Closure") + * * @ORM\Entity(repositoryClass="Gedmo\Tree\Entity\Repository\ClosureTreeRepository") */ #[ORM\Entity(repositoryClass: ClosureTreeRepository::class)] @@ -41,34 +42,30 @@ class Category2 private $id; /** - * @var string|null - * * @ORM\Column(name="title", type="string", length=64) */ #[ORM\Column(name: 'title', type: Types::STRING, length: 64)] - private $title; + private ?string $title = null; /** - * @var int|null - * * @ORM\Column(name="level", type="integer", nullable=true) + * * @Gedmo\TreeLevel */ #[ORM\Column(name: 'level', type: Types::INTEGER, nullable: true)] #[Gedmo\TreeLevel] - private $level; + private ?int $level = null; /** - * @var self|null - * * @Gedmo\TreeParent + * * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE") * @ORM\ManyToOne(targetEntity="Category2", inversedBy="children") */ #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] #[ORM\JoinColumn(name: 'category2_id', referencedColumnName: 'id', onDelete: 'CASCADE')] #[Gedmo\TreeParent] - private $parent; + private ?\Gedmo\Tests\Tree\Fixture\Issue2652\Category2 $parent = null; /** * @var Collection diff --git a/tests/Gedmo/Tree/Issue/Issue2652Test.php b/tests/Gedmo/Tree/Issue/Issue2652Test.php index 6e8b7b1a60..b7255025f1 100644 --- a/tests/Gedmo/Tree/Issue/Issue2652Test.php +++ b/tests/Gedmo/Tree/Issue/Issue2652Test.php @@ -32,10 +32,7 @@ final class Issue2652Test extends BaseTestCaseORM private const CATEGORY2 = Category2::class; private const CLOSURE2 = Category2Closure::class; - /** - * @var TreeListener - */ - private $listener; + private TreeListener $listener; protected function setUp(): void { From ce419ecf5fb5734baa593d85ad4cfd60e6f45fca Mon Sep 17 00:00:00 2001 From: JDruery Date: Mon, 26 Aug 2024 15:12:03 -0400 Subject: [PATCH 19/20] Match php version of upstream --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 813e101e83..578f27b58a 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ "wiki": "https://github.com/Atlantic18/DoctrineExtensions/tree/main/doc" }, "require": { - "php": "^7.2 || ^8.0", + "php": "^7.4 || ^8.0", "behat/transliterator": "^1.2", "doctrine/collections": "^1.2 || ^2.0", "doctrine/common": "^2.13 || ^3.0", From 484aa78f84e83d7a450978c2bb3f713d994418b7 Mon Sep 17 00:00:00 2001 From: JDruery Date: Tue, 27 Aug 2024 12:14:13 -0400 Subject: [PATCH 20/20] Match packages of upstream --- composer.json | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index 578f27b58a..2f3000eac1 100644 --- a/composer.json +++ b/composer.json @@ -57,17 +57,19 @@ "doctrine/dbal": "^3.2", "doctrine/doctrine-bundle": "^2.3", "doctrine/mongodb-odm": "^2.3", - "doctrine/orm": "^2.14.0", - "friendsofphp/php-cs-fixer": "^3.4.0 <3.10", - "nesbot/carbon": "^2.55", - "phpstan/phpstan": "^1.10.2", - "phpstan/phpstan-doctrine": "^1.0", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^8.5 || ^9.5", - "rector/rector": "^0.15.20", - "symfony/console": "^4.4 || ^5.3 || ^6.0", - "symfony/phpunit-bridge": "^6.0", - "symfony/yaml": "^4.4 || ^5.3 || ^6.0" + "doctrine/orm": "^2.14.0 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.14.0", + "nesbot/carbon": "^2.71 || ^3.0", + "phpstan/phpstan": "^1.11", + "phpstan/phpstan-doctrine": "^1.4", + "phpstan/phpstan-phpunit": "^1.4", + "phpunit/phpunit": "^9.6", + "rector/rector": "^1.1", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/doctrine-bridge": "^5.4 || ^6.0 || ^7.0", + "symfony/phpunit-bridge": "^6.0 || ^7.0", + "symfony/uid": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" }, "conflict": { "doctrine/annotations": "<1.13 || >=3.0",