Skip to content

Commit ebe4536

Browse files
authored
Skip calculating old value when PersistentCollection is in change set (#144)
1 parent d07f09d commit ebe4536

7 files changed

Lines changed: 112 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.3.2] - 2026-06-10
9+
10+
### Fixed
11+
12+
- Fix purge route generation when Doctrine change sets contain a `PersistentCollection` by @Brajk19
13+
in https://github.com/sofascore/purgatory-bundle/pull/144
14+
815
## [1.3.1] - 2026-06-08
916

1017
### Fixed
@@ -55,6 +62,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5562

5663
- Initial release
5764

65+
[1.3.2]: https://github.com/sofascore/purgatory-bundle/compare/v1.3.1...v1.3.2
5866
[1.3.1]: https://github.com/sofascore/purgatory-bundle/compare/v1.3.0...v1.3.1
5967
[1.3.0]: https://github.com/sofascore/purgatory-bundle/compare/v1.2.1...v1.3.0
6068
[1.2.1]: https://github.com/sofascore/purgatory-bundle/compare/v1.2.0...v1.2.1

src/Listener/EntityChangeListener.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,6 @@ public function reset(): void
7575
private function handleChanges(LifecycleEventArgs $eventArgs, Action $action): void
7676
{
7777
$entity = $eventArgs->getObject();
78-
79-
/** @var array<string, array{mixed, mixed}> $entityChangeSet */
8078
$entityChangeSet = $eventArgs->getObjectManager()->getUnitOfWork()->getEntityChangeSet($entity);
8179

8280
foreach ($this->routeProviders as $routeProvider) {

src/RouteProvider/AbstractEntityRouteProvider.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Sofascore\PurgatoryBundle\RouteProvider;
66

7+
use Doctrine\ORM\PersistentCollection;
78
use Psr\Container\ContainerInterface;
89
use Sofascore\PurgatoryBundle\Cache\Configuration\Configuration;
910
use Sofascore\PurgatoryBundle\Cache\Configuration\ConfigurationLoaderInterface;
@@ -22,7 +23,7 @@
2223
abstract class AbstractEntityRouteProvider implements RouteProviderInterface
2324
{
2425
/**
25-
* @param array<string, array{mixed, mixed}> $entityChangeSet
26+
* @param array<string, array{mixed, mixed}|PersistentCollection<array-key, object>> $entityChangeSet
2627
*
2728
* @return array<int, string>
2829
*/
@@ -61,7 +62,7 @@ final public function provideRoutesFor(Action $action, object $entity, array $en
6162
}
6263

6364
/**
64-
* @param array<string, array{mixed, mixed}> $entityChangeSet
65+
* @param array<string, array{mixed, mixed}|PersistentCollection<array-key, object>> $entityChangeSet
6566
*
6667
* @return iterable<int, PurgeRoute>
6768
*/
@@ -113,9 +114,9 @@ private function processValidSubscriptions(Subscriptions $subscriptions, array $
113114
}
114115

115116
/**
116-
* @param array<string, list<?scalar>> $routeParamValues
117-
* @param array<string, array{type: string, values: list<mixed>, optional?: true}> $routeParamConfigs
118-
* @param array<string, array{mixed, mixed}> $entityChangeSet
117+
* @param array<string, list<?scalar>> $routeParamValues
118+
* @param array<string, array{type: string, values: list<mixed>, optional?: true}> $routeParamConfigs
119+
* @param array<string, array{mixed, mixed}|PersistentCollection<array-key, object>> $entityChangeSet
119120
*
120121
* @return list<array<string, ?scalar>>
121122
*/

src/RouteProvider/RouteProviderInterface.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Sofascore\PurgatoryBundle\RouteProvider;
66

7+
use Doctrine\ORM\PersistentCollection;
78
use Sofascore\PurgatoryBundle\Listener\Enum\Action;
89

910
/**
@@ -12,8 +13,8 @@
1213
interface RouteProviderInterface
1314
{
1415
/**
15-
* @param T $entity
16-
* @param array<string, array{mixed, mixed}> $entityChangeSet
16+
* @param T $entity
17+
* @param array<string, array{mixed, mixed}|PersistentCollection<array-key, object>> $entityChangeSet
1718
*
1819
* @return iterable<int, PurgeRoute>
1920
*/

src/RouteProvider/UpdatedEntityRouteProvider.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Sofascore\PurgatoryBundle\RouteProvider;
66

7+
use Doctrine\ORM\PersistentCollection;
78
use Psr\Container\ContainerInterface;
89
use Sofascore\PurgatoryBundle\Attribute\RouteParamValue\PropertyValues;
910
use Sofascore\PurgatoryBundle\Cache\Configuration\ConfigurationLoaderInterface;
@@ -94,8 +95,8 @@ protected function processRouteParamValues(array $routeParamValues, array $route
9495
/**
9596
* If possible, compute the old values for the route params.
9697
*
97-
* @param array<string, array{mixed, mixed}> $entityChangeSet
98-
* @param array{type: string, values: list<mixed>, optional?: true} $config
98+
* @param array<string, array{mixed, mixed}|PersistentCollection<array-key, object>> $entityChangeSet
99+
* @param array{type: string, values: list<mixed>, optional?: true} $config
99100
*
100101
* @return list<?scalar>
101102
*/
@@ -133,6 +134,11 @@ private function computeOldRouteParamValues(array $entityChangeSet, array $confi
133134
// Association check
134135
$head = $propertyPath->getElement(0);
135136

137+
// a dereferenced to-many association holds the old collection instead of an [old, new] pair
138+
if (($entityChangeSet[$head] ?? null) instanceof PersistentCollection) {
139+
continue;
140+
}
141+
136142
if (!isset($entityChangeSet[$head][0])) {
137143
continue;
138144
}

tests/Application/ApplicationTest.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Sofascore\PurgatoryBundle\Tests\Application;
66

7+
use Doctrine\Common\Collections\ArrayCollection;
78
use Doctrine\ORM\EntityManagerInterface;
89
use PHPUnit\Framework\Attributes\RequiresMethod;
910
use Sofascore\PurgatoryBundle\Test\InteractsWithPurgatory;
@@ -416,6 +417,43 @@ public function testPurgeManyTo(): void
416417
self::assertUrlIsPurged('/animal/'.$pet2->id.'/owner-details');
417418
}
418419

420+
/**
421+
* @see AnimalController::petOwnerDetails
422+
*/
423+
public function testPurgeAfterCollectionDereference(): void
424+
{
425+
$person = new Person();
426+
$person->firstName = 'Purga';
427+
$person->lastName = 'Tory';
428+
$person->gender = 'male';
429+
430+
$pet1 = new Animal();
431+
$pet1->name = 'Poppy';
432+
$pet1->owner = $person;
433+
$person->pets->add($pet1);
434+
435+
$pet2 = new Animal();
436+
$pet2->name = 'Bobby';
437+
$pet2->owner = $person;
438+
$person->pets->add($pet2);
439+
440+
$this->entityManager->persist($person);
441+
$this->entityManager->flush();
442+
443+
self::assertUrlIsPurged('/animal/'.$pet1->id.'/owner-details');
444+
self::assertUrlIsPurged('/animal/'.$pet2->id.'/owner-details');
445+
446+
self::clearPurger();
447+
448+
// the entity change set now contains the old collection instead of an [old, new] pair
449+
$person->pets = new ArrayCollection([$pet1]);
450+
$this->entityManager->flush();
451+
452+
self::assertUrlIsPurged('/animal/'.$pet1->id.'/owner-details');
453+
// old values are not computed for a dereferenced collection
454+
self::assertUrlIsNotPurged('/animal/'.$pet2->id.'/owner-details');
455+
}
456+
419457
/**
420458
* @see AnimalController::petOwnerDetailsAlternative
421459
*/

tests/RouteProvider/UpdatedEntityRouteProviderTest.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
namespace Sofascore\PurgatoryBundle\Tests\RouteProvider;
66

7+
use Doctrine\Common\Collections\ArrayCollection;
8+
use Doctrine\ORM\EntityManagerInterface;
9+
use Doctrine\ORM\Mapping\ClassMetadata;
10+
use Doctrine\ORM\PersistentCollection;
711
use PHPUnit\Framework\Attributes\CoversClass;
812
use PHPUnit\Framework\Attributes\RequiresMethod;
913
use PHPUnit\Framework\Attributes\TestWith;
@@ -234,6 +238,51 @@ public function testProvideRoutesToPurgeWithOldValues(): void
234238
self::assertSame(['name' => 'association_route', 'params' => ['param1' => 5]], (array) $routes[4]);
235239
}
236240

241+
public function testOldValuesFromDereferencedCollectionAreSkipped(): void
242+
{
243+
$routeProvider = $this->createRouteProvider([
244+
'stdClass::pets' => [
245+
[
246+
'routeName' => 'pets_route',
247+
'routeParams' => [
248+
'param1' => [
249+
'type' => PropertyValues::type(),
250+
'values' => ['pets[*].id'],
251+
],
252+
],
253+
],
254+
],
255+
], false);
256+
257+
$newPet = new \stdClass();
258+
$newPet->id = 3;
259+
260+
$entity = new \stdClass();
261+
$entity->pets = new ArrayCollection([$newPet]);
262+
263+
$oldPet1 = new \stdClass();
264+
$oldPet1->id = 1;
265+
$oldPet2 = new \stdClass();
266+
$oldPet2->id = 2;
267+
268+
$routes = [...$routeProvider->provideRoutesFor(
269+
action: Action::Update,
270+
entity: $entity,
271+
entityChangeSet: [
272+
'pets' => new PersistentCollection(
273+
self::createStub(EntityManagerInterface::class),
274+
new ClassMetadata(\stdClass::class),
275+
new ArrayCollection([$oldPet1, $oldPet2]),
276+
),
277+
],
278+
)];
279+
280+
self::assertCount(1, $routes);
281+
self::assertContainsOnlyInstancesOf(PurgeRoute::class, $routes);
282+
283+
self::assertSame(['name' => 'pets_route', 'params' => ['param1' => 3]], (array) $routes[0]);
284+
}
285+
237286
public function testProvideRoutesToPurgeWithArrayAccess(): void
238287
{
239288
$routeProvider = $this->createRouteProvider([

0 commit comments

Comments
 (0)