From 48e544fb843324e09a8dce932c3ec6c8d0ae25ec Mon Sep 17 00:00:00 2001 From: MrSpamer Bot Date: Tue, 10 Nov 2026 22:31:55 +0000 Subject: [PATCH 1/3] fix(entity-manager): mark a persisted entity as existing in the ChangeSet persist() put the entity into the IdentityMap but never into the ChangeSet, so find() right after persist() returned an instance the ChangeSet called new, and persisting the same instance again fired POST_CREATE a second time instead of POST_UPDATE. Fixes ClearTest::testClearRemovesChangeSetTracking. Co-Authored-By: Claude Opus 5.5 --- src/EntityManager.php | 3 ++ .../Elastic/EntityManager/PersistTest.phpt | 40 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/EntityManager.php b/src/EntityManager.php index dc8dbff3..186707ea 100644 --- a/src/EntityManager.php +++ b/src/EntityManager.php @@ -204,6 +204,9 @@ public function persist( $this->dispatchEvents->execute($entity, EventManager::POST_CREATE); $this->dispatchEvents->execute($entity, EventManager::POST_PERSIST); + // The entity is stored now: a second persist of this instance is an update, not a create. + $this->changeSet->markExisting($entity); + return $id; } diff --git a/tests/SpameriTests/Elastic/EntityManager/PersistTest.phpt b/tests/SpameriTests/Elastic/EntityManager/PersistTest.phpt index 7ac881ad..acf7d93e 100644 --- a/tests/SpameriTests/Elastic/EntityManager/PersistTest.phpt +++ b/tests/SpameriTests/Elastic/EntityManager/PersistTest.phpt @@ -195,6 +195,46 @@ class PersistTest extends \SpameriTests\Elastic\AbstractTestCase } + public function testSecondPersistOfSameInstanceFiresPostUpdate(): void + { + $createListener = new \SpameriTests\Elastic\Data\Listener\MockListener( + [\SpameriTests\Elastic\Data\Entity\Title::class], + \Spameri\Elastic\EventManager::POST_CREATE, + ); + $updateListener = new \SpameriTests\Elastic\Data\Listener\MockListener( + [\SpameriTests\Elastic\Data\Entity\Title::class], + \Spameri\Elastic\EventManager::POST_UPDATE, + ); + + /** @var \Spameri\Elastic\EventManager $eventManager */ + $eventManager = $this->container->getByType(\Spameri\Elastic\EventManager::class); + $eventManager->addListener( + \Spameri\Elastic\EventManager::POST_CREATE, + \SpameriTests\Elastic\Data\Entity\Title::class, + $createListener, + ); + $eventManager->addListener( + \Spameri\Elastic\EventManager::POST_UPDATE, + \SpameriTests\Elastic\Data\Entity\Title::class, + $updateListener, + ); + + /** @var \Spameri\Elastic\EntityManager $entityManager */ + $entityManager = $this->container->getByType(\Spameri\Elastic\EntityManager::class); + + $entity = new \SpameriTests\Elastic\Data\Entity\Title( + new \Spameri\Elastic\Entity\Property\EmptyElasticId(), + null, + ); + + $entityManager->persist($entity); + $entityManager->persist($entity); + + \Tester\Assert::same(1, $createListener->getCallCount()); + \Tester\Assert::same(1, $updateListener->getCallCount()); + } + + public function testPersistReturnsIdString(): void { /** @var \Spameri\Elastic\EntityManager $entityManager */ From bb6fd16e6700aad89ba5193c97366a83d309d862 Mon Sep 17 00:00:00 2001 From: MrSpamer Bot Date: Tue, 10 Nov 2026 22:31:55 +0000 Subject: [PATCH 2/3] chore(deps): raise lowest versions so the lowest-deps jobs run on PHP 8.2-8.5 - tracy/tracy ^2.9, symfony/console ^7.0.3: older ones have implicitly nullable parameters, an E_DEPRECATED (= test failure) on PHP 8.4+ - phpstan/phpstan ^1.11.10: <=1.11.9 crashes on PHP 8.5 (IS_READONLY type) - conflict with transitive versions that break on PHP 8.4+ (nette/neon, symfony/string, psr/http-factory) or lack mb_ord() (polyfill-mbstring <1.3, tests run with php -n, so no ext-mbstring) - phpstan-low.neon: drop the ignore that no longer matches PHP >=8.2 stays supported. Co-Authored-By: Claude Opus 5.5 --- composer.json | 12 +++++++++--- phpstan-low.neon | 5 ----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index ba77aab1..c76e0fd6 100644 --- a/composer.json +++ b/composer.json @@ -32,8 +32,8 @@ "guzzlehttp/guzzle": "^7.9", "nette/di": "^3.1.10", "nette/utils": "^4.0.0|^3.2.5", - "symfony/console": "^7.0.2", - "tracy/tracy": "^2.6.7", + "symfony/console": "^7.0.3", + "tracy/tracy": "^2.9", "monolog/monolog": "^3.0.0", "ezimuel/ringphp": "^1.2" }, @@ -41,7 +41,7 @@ "slevomat/coding-standard": "^8.15.0", "spameri/dependency-mocker": "^1.3", "nette/tester": "^v2.5.7", - "phpstan/phpstan": "^1.11.3", + "phpstan/phpstan": "^1.11.10", "php-coveralls/php-coveralls": "^2.1", "nette/bootstrap": "^3.1", "nette/http": "^3.0.7", @@ -67,5 +67,11 @@ "branch-alias": { "dev-master": "2.0-dev" } + }, + "conflict": { + "nette/neon": "<3.3.4 || >=3.4.0 <3.4.4", + "symfony/string": "<6.4.3 || >=7.0 <7.0.3", + "psr/http-factory": "<1.1", + "symfony/polyfill-mbstring": "<1.3" } } diff --git a/phpstan-low.neon b/phpstan-low.neon index 07703a2c..a8799eff 100644 --- a/phpstan-low.neon +++ b/phpstan-low.neon @@ -6,11 +6,6 @@ parameters: - %rootDir%/../../../phpstanConstants.php ignoreErrors: - - - message: "#^Unreachable statement \\- code above always terminates\\.$#" - count: 1 - path: src/Factory/EntityFactory.php - - message: "#^Variable \\$propertyValue might not be defined\\.$#" count: 1 From 931c281d63fe5c5bcd9376e575ed92e6ec82ee5b Mon Sep 17 00:00:00 2001 From: MrSpamer Bot Date: Tue, 10 Nov 2026 22:41:14 +0000 Subject: [PATCH 3/3] test(dump-index): refresh the index before dumping it The dump reads through search, so right after the restore it could see fewer than 11 documents and the test failed on \$exploded[10] (flaky, hit PHP 8.2 standard on #20). Co-Authored-By: Claude Opus 5.5 --- tests/SpameriTests/Elastic/Model/DumpIndex/Execute.phpt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/SpameriTests/Elastic/Model/DumpIndex/Execute.phpt b/tests/SpameriTests/Elastic/Model/DumpIndex/Execute.phpt index 9280688b..86af6a51 100644 --- a/tests/SpameriTests/Elastic/Model/DumpIndex/Execute.phpt +++ b/tests/SpameriTests/Elastic/Model/DumpIndex/Execute.phpt @@ -30,6 +30,9 @@ class Execute extends \SpameriTests\Elastic\AbstractTestCase $restoreIndex->setOutput(new \Symfony\Component\Console\Output\ConsoleOutput()); $restoreIndex->execute(__DIR__ . '/dumpData.json', 500); + + // The dump reads through search, which only sees documents after a refresh. + $clientProvider->client()->indices()->refresh(['index' => \SpameriTests\Elastic\Config::INDEX_DUMP]); }