From f55ef8d47d3b33aebf563ec1fc6abd1483033653 Mon Sep 17 00:00:00 2001 From: BacLuc Date: Sat, 6 Dec 2025 22:39:43 +0100 Subject: [PATCH 1/3] api: rewrite PurgeHttpCacheListenerTest without Prophecy It is the last place where we use Prophecy, api-platform doesn't want to use it anymore and it blocks the update to php 8.5. -> remove it --- .../HttpCache/PurgeHttpCacheListenerTest.php | 626 +++++++++++------- 1 file changed, 370 insertions(+), 256 deletions(-) diff --git a/api/tests/HttpCache/PurgeHttpCacheListenerTest.php b/api/tests/HttpCache/PurgeHttpCacheListenerTest.php index 225f6ba8ef..fbd7c166dd 100644 --- a/api/tests/HttpCache/PurgeHttpCacheListenerTest.php +++ b/api/tests/HttpCache/PurgeHttpCacheListenerTest.php @@ -37,74 +37,60 @@ use Doctrine\ORM\UnitOfWork; use FOS\HttpCacheBundle\CacheManager; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; +use Symfony\Bundle\MakerBundle\Doctrine\StaticReflectionService; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; +use function PHPUnit\Framework\assertThat; +use function PHPUnit\Framework\containsEqual; +use function PHPUnit\Framework\logicalAnd; + /** * @author Kévin Dunglas * * @internal */ class PurgeHttpCacheListenerTest extends TestCase { - use ProphecyTrait; - - private ObjectProphecy $cacheManagerProphecy; - private ObjectProphecy $resourceClassResolverProphecy; - private ObjectProphecy $uowProphecy; - private ObjectProphecy $emProphecy; - private ObjectProphecy $propertyAccessorProphecy; - private ObjectProphecy $iriConverterProphecy; - private ObjectProphecy $metadataFactoryProphecy; + private CacheManager $cacheManagerProphecy; + private ResourceClassResolverInterface $resourceClassResolverProphecy; + private UnitOfWork $uowProphecy; + private EntityManagerInterface $emProphecy; + private PropertyAccessorInterface $propertyAccessorProphecy; + private IriConverterInterface $iriConverterProphecy; + private ResourceMetadataCollectionFactoryInterface $metadataFactoryProphecy; protected function setUp(): void { - $this->cacheManagerProphecy = $this->prophesize(CacheManager::class); - $this->cacheManagerProphecy->flush()->willReturn(0); - - $this->resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $this->resourceClassResolverProphecy->isResourceClass(Argument::type('string'))->willReturn(true); - $this->resourceClassResolverProphecy->getResourceClass(Argument::type(Dummy::class))->willReturn(Dummy::class); - - $this->uowProphecy = $this->prophesize(UnitOfWork::class); - - $this->emProphecy = $this->prophesize(EntityManagerInterface::class); - $this->emProphecy->detach(Argument::any()); - $this->emProphecy->getUnitOfWork()->willReturn($this->uowProphecy->reveal()); - - $classMetadataProphecy = $this->prophesize(ClassMetadata::class); - $classMetadataProphecy->getAssociationMappings()->willReturn([ - 'relatedDummy' => [ - 'targetEntity' => RelatedDummy::class, - 'isOwningSide' => true, - 'inversedBy' => 'dummies', - 'mappedBy' => null, - ], - 'relatedOwningDummy' => [ - 'targetEntity' => RelatedOwningDummy::class, - 'isOwningSide' => true, - 'inversedBy' => 'ownedDummy', - 'mappedBy' => null, - ], - ]); - $classMetadataProphecy->setFieldValue(Argument::any(), Argument::any(), Argument::any())->will(function ($args) { - $entity = $args[0]; - $field = $args[1]; - $value = $args[2]; - $entity->{$field} = $value; - }); - $this->emProphecy->getClassMetadata(Dummy::class)->willReturn($classMetadataProphecy->reveal()); + $this->cacheManagerProphecy = $this->createMock(CacheManager::class); + $this->cacheManagerProphecy->method('flush')->willReturn(0); + + $this->resourceClassResolverProphecy = $this->createMock(ResourceClassResolverInterface::class); + $this->resourceClassResolverProphecy->method('isResourceClass')->willReturn(true); + $this->resourceClassResolverProphecy->method('getResourceClass')->willReturn(Dummy::class); - $this->propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class); - $this->propertyAccessorProphecy->isReadable(Argument::type(Dummy::class), 'relatedDummy')->willReturn(true); - $this->propertyAccessorProphecy->isReadable(Argument::type(Dummy::class), 'relatedOwningDummy')->willReturn(false); - $this->propertyAccessorProphecy->getValue(Argument::type(Dummy::class), 'relatedDummy')->willReturn(null); - $this->propertyAccessorProphecy->getValue(Argument::type(Dummy::class), 'relatedOwningDummy')->willReturn(null); + $this->uowProphecy = $this->createMock(UnitOfWork::class); - $this->metadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); + $this->emProphecy = $this->createMock(EntityManagerInterface::class); + $this->emProphecy->method('getUnitOfWork')->willReturnCallback(fn () => $this->uowProphecy); + + $dummyClassMetadata = new ClassMetadata(Dummy::class); + $dummyClassMetadata->mapManyToOne(['fieldName' => 'relatedDummy', 'targetEntity' => RelatedDummy::class, 'inversedBy' => 'dummies']); + $dummyClassMetadata->mapOneToOne(['fieldName' => 'relatedOwningDummy', 'targetEntity' => RelatedOwningDummy::class, 'inversedBy' => 'ownedDummy']); + $dummyClassMetadata->wakeupReflection(new StaticReflectionService()); + $this->emProphecy->method('getClassMetadata')->with(Dummy::class)->willReturn($dummyClassMetadata); + + $this->propertyAccessorProphecy = $this->createMock(PropertyAccessorInterface::class); + $this->propertyAccessorProphecy->method('isReadable')->willReturnCallback(function ($obj, $prop) { + if ($obj instanceof Dummy && 'relatedDummy' === $prop) { + return true; + } + + return false; + }); + $this->propertyAccessorProphecy->method('getValue')->willReturn(null); + + $this->metadataFactoryProphecy = $this->createMock(ResourceMetadataCollectionFactoryInterface::class); $operation = new GetCollection()->withShortName('Dummy')->withClass(Dummy::class); $operation2 = new GetCollection()->withShortName('DummyAsSubresource')->withClass(Dummy::class); - $this->metadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadataCollection('Dummy', [ + $this->metadataFactoryProphecy->method('create')->with(Dummy::class)->willReturn(new ResourceMetadataCollection('Dummy', [ new ApiResource('Dummy') ->withShortName('Dummy') ->withOperations(new Operations([ @@ -113,10 +99,21 @@ protected function setUp(): void { ])), ])); - $this->iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $this->iriConverterProphecy->getIriFromResource(Argument::type(Dummy::class), UrlGeneratorInterface::ABS_PATH, $operation)->willReturn('/dummies'); - $this->iriConverterProphecy->getIriFromResource(Argument::type(Dummy::class), UrlGeneratorInterface::ABS_PATH, $operation2)->will(function ($args) { return '/related_dummies/'.$args[0]->getRelatedDummy()->getId().'/dummies'; }); - $this->iriConverterProphecy->getIriFromResource(Argument::type(Dummy::class))->will(function ($args) { return '/dummies/'.$args[0]->getId(); }); + $this->iriConverterProphecy = $this->createMock(IriConverterInterface::class); + $this->iriConverterProphecy->method('getIriFromResource')->willReturnCallback(function (object|string $resource, ...$args) use ($operation, $operation2): ?string { + if ($resource instanceof Dummy) { + if (isset($args[1]) && $args[1] === $operation) { + return '/dummies'; + } + if (isset($args[1]) && $args[1] === $operation2) { + return '/related_dummies/'.$resource->getRelatedDummy()->getId().'/dummies'; + } + + return '/dummies/'.$resource->getId(); + } + + return null; + }); } /** @@ -136,36 +133,73 @@ public function testOnFlush(): void { $toDeleteNoPurge = new DummyNoGetOperation(); $toDeleteNoPurge->setId('5'); - $cacheManagerProphecy = $this->prophesize(CacheManager::class); - $cacheManagerProphecy->invalidateTags(['/dummies'])->willReturn($cacheManagerProphecy)->shouldBeCalled(); - $cacheManagerProphecy->invalidateTags(['/dummies/3'])->willReturn($cacheManagerProphecy)->shouldBeCalled(); - $cacheManagerProphecy->invalidateTags(['/dummies/4'])->willReturn($cacheManagerProphecy)->shouldBeCalled(); - $cacheManagerProphecy->flush()->willReturn(0); + $cacheManagerProphecy = $this->createMock(CacheManager::class); + $cacheManagerInvalidateTagsCalls = []; + $cacheManagerProphecy + ->method('invalidateTags') + ->willReturnCallback(function (array $tags) use (&$cacheManagerInvalidateTagsCalls, $cacheManagerProphecy) { + $cacheManagerInvalidateTagsCalls[] = $tags; - $metadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); + return $cacheManagerProphecy; + }) + ; + $cacheManagerProphecy->method('flush')->willReturn(0); + + $metadataFactoryProphecy = $this->createMock(ResourceMetadataCollectionFactoryInterface::class); $operation = new GetCollection()->withShortName('Dummy')->withClass(Dummy::class); - $metadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadataCollection('Dummy', [ - new ApiResource('Dummy') - ->withShortName('Dummy') - ->withOperations(new Operations([ - 'get' => $operation, - ])), - ]))->shouldBeCalled(); - $metadataFactoryProphecy->create(DummyNoGetOperation::class)->willReturn(new ResourceMetadataCollection('DummyNoGetOperation', [ - new ApiResource('DummyNoGetOperation') - ->withShortName('DummyNoGetOperation'), - ]))->shouldBeCalled(); - - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $iriConverterProphecy->getIriFromResource(Argument::type(Dummy::class), UrlGeneratorInterface::ABS_PATH, Argument::type(GetCollection::class))->willReturn('/dummies')->shouldBeCalled(); - $iriConverterProphecy->getIriFromResource($toDelete1)->willReturn('/dummies/3')->shouldBeCalled(); - $iriConverterProphecy->getIriFromResource($toDelete2)->willReturn('/dummies/4')->shouldBeCalled(); - $iriConverterProphecy->getIriFromResource($toDeleteNoPurge)->willReturn(null)->shouldBeCalled(); - - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $resourceClassResolverProphecy->isResourceClass(Argument::type('string'))->willReturn(true)->shouldBeCalled(); - $resourceClassResolverProphecy->getResourceClass(Argument::type(Dummy::class))->willReturn(Dummy::class)->shouldBeCalled(); - $resourceClassResolverProphecy->getResourceClass(Argument::type(DummyNoGetOperation::class))->willReturn(DummyNoGetOperation::class)->shouldBeCalled(); + $metadataFactoryProphecy + ->method('create') + ->willReturnCallback(function (string $class) use ($operation) { + switch ($class) { + case Dummy::class: + return new ResourceMetadataCollection('Dummy', [ + new ApiResource('Dummy') + ->withShortName('Dummy') + ->withOperations(new Operations([ + 'get' => $operation, + ])), + ]); + + case DummyNoGetOperation::class: + return new ResourceMetadataCollection('DummyNoGetOperation', [ + new ApiResource('DummyNoGetOperation') + ->withShortName('DummyNoGetOperation'), + ]); + + default: + TestCase::fail('Unexpected class passed to metadata factory: '.$class); + } + }) + ; + + $iriConverterProphecy = $this->createMock(IriConverterInterface::class); + $iriConverterProphecy + ->method('getIriFromResource') + ->willReturnCallback(function (object|string $resource, ...$args) use (&$toDelete1, &$toDelete2, &$toDeleteNoPurge): ?string { + if ($resource == $toDelete1) { + return '/dummies/3'; + } + if ($resource == $toDelete2) { + return '/dummies/4'; + } + if ($resource == $toDeleteNoPurge) { + return null; + } + if ($resource instanceof Dummy && isset($args[0], $args[1]) && UrlGeneratorInterface::ABS_PATH === $args[0] && $args[1] instanceof GetCollection) { + return '/dummies'; + } + + return null; + }) + ; + + $resourceClassResolverProphecy = $this->createMock(ResourceClassResolverInterface::class); + $resourceClassResolverProphecy->expects($this->atLeastOnce())->method('isResourceClass')->willReturn(true); + $resourceClassResolverProphecy->method('getResourceClass')->willReturnCallback( + function ($resource): string { + return $resource::class; + } + ); $uowMock = $this->createMock(UnitOfWork::class); $uowMock->method('getScheduledEntityInsertions')->willReturn([$toInsert1, $toInsert2]); @@ -175,33 +209,41 @@ public function testOnFlush(): void { $uowMock->method('getScheduledCollectionDeletions')->willReturn([]); $uowMock->method('getEntityChangeSet')->willReturn([]); - $emProphecy = $this->prophesize(EntityManagerInterface::class); - $emProphecy->getUnitOfWork()->willReturn($uowMock)->shouldBeCalled(); - $emProphecy->detach(Argument::any()); + $entityManager = $this->createMock(EntityManagerInterface::class); + $entityManager->expects($this->atLeastOnce())->method('getUnitOfWork')->willReturn($uowMock); $dummyClassMetadata = new ClassMetadata(Dummy::class); $dummyClassMetadata->mapManyToOne(['fieldName' => 'relatedDummy', 'targetEntity' => RelatedDummy::class, 'inversedBy' => 'dummies']); $dummyClassMetadata->mapOneToOne(['fieldName' => 'relatedOwningDummy', 'targetEntity' => RelatedOwningDummy::class, 'inversedBy' => 'ownedDummy']); - $emProphecy->getClassMetadata(Dummy::class)->willReturn($dummyClassMetadata)->shouldBeCalled(); - $emProphecy->getClassMetadata(DummyNoGetOperation::class)->willReturn(new ClassMetadata(DummyNoGetOperation::class))->shouldBeCalled(); - $em = $emProphecy->reveal(); - - $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class); - $propertyAccessorProphecy->isReadable(Argument::type(Dummy::class), 'relatedDummy')->willReturn(true); - $propertyAccessorProphecy->isReadable(Argument::type(Dummy::class), 'relatedOwningDummy')->willReturn(false); - $propertyAccessorProphecy->getValue(Argument::type(Dummy::class), 'relatedDummy')->willReturn(null); - $propertyAccessorProphecy->getValue(Argument::type(Dummy::class), 'relatedOwningDummy')->willReturn(null); + $entityManager->expects($this->atLeast(2))->method('getClassMetadata') + ->willReturnCallback(function (string $class) use ($dummyClassMetadata) { + return match ($class) { + Dummy::class => $dummyClassMetadata, + RelatedDummy::class => new ClassMetadata(DummyNoGetOperation::class), + DummyNoGetOperation::class => new ClassMetadata(DummyNoGetOperation::class), + default => throw new \InvalidArgumentException('Unexpected class: '.$class), + }; + }) + ; + + $propertyAccessorProphecy = $this->createMock(PropertyAccessorInterface::class); + $propertyAccessorProphecy->method('isReadable')->willReturnCallback(function ($obj, $prop) { + return $obj instanceof Dummy && in_array($prop, ['relatedDummy', 'relatedOwningDummy'], true) && ('relatedDummy' === $prop || 'relatedOwningDummy' === $prop); + }); + $propertyAccessorProphecy->method('getValue')->willReturn(null); $listener = new PurgeHttpCacheListener( - iriConverter: $iriConverterProphecy->reveal(), - resourceClassResolver: $resourceClassResolverProphecy->reveal(), - propertyAccessor: $propertyAccessorProphecy->reveal(), - resourceMetadataCollectionFactory: $metadataFactoryProphecy->reveal(), - cacheManager: $cacheManagerProphecy->reveal(), - em: $em, + iriConverter: $iriConverterProphecy, + resourceClassResolver: $resourceClassResolverProphecy, + propertyAccessor: $propertyAccessorProphecy, + resourceMetadataCollectionFactory: $metadataFactoryProphecy, + cacheManager: $cacheManagerProphecy, + em: $entityManager, ); $listener->onFlush(); $listener->postFlush(); + + assertThat($cacheManagerInvalidateTagsCalls, logicalAnd(containsEqual(['/dummies']), containsEqual(['/dummies/3']), containsEqual(['/dummies/4']))); } public function testPreUpdate(): void { @@ -214,39 +256,61 @@ public function testPreUpdate(): void { $dummy = new Dummy(); $dummy->setId('1'); - $cacheManagerProphecy = $this->prophesize(CacheManager::class); - $cacheManagerProphecy->invalidateTags(['/related_dummies/old#dummies'])->shouldBeCalled()->willReturn($cacheManagerProphecy); - $cacheManagerProphecy->invalidateTags(['/related_dummies/new#dummies'])->shouldBeCalled()->willReturn($cacheManagerProphecy); - $cacheManagerProphecy->flush()->willReturn(0); - - $metadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $iriConverterProphecy->getIriFromResource($oldRelatedDummy)->willReturn('/related_dummies/old')->shouldBeCalled(); - $iriConverterProphecy->getIriFromResource($newRelatedDummy)->willReturn('/related_dummies/new')->shouldBeCalled(); - - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $resourceClassResolverProphecy->isResourceClass(Argument::type('string'))->willReturn(true)->shouldBeCalled(); - - $emProphecy = $this->prophesize(EntityManagerInterface::class); + $cacheManagerProphecy = $this->createMock(CacheManager::class); + $cacheManagerProphecy->expects($this->exactly(2)) + ->method('invalidateTags') + ->willReturnCallback(function (array $tags) use ($cacheManagerProphecy) { + static $i = 0; + $expected = [ + ['/related_dummies/old#dummies'], + ['/related_dummies/new#dummies'], + ]; + TestCase::assertEquals($expected[$i], $tags); + ++$i; + + return $cacheManagerProphecy; + }) + ; + $cacheManagerProphecy->method('flush')->willReturn(0); + + $metadataFactoryProphecy = $this->createMock(ResourceMetadataCollectionFactoryInterface::class); + + $iriConverterProphecy = $this->createMock(IriConverterInterface::class); + $iriConverterProphecy->expects($this->exactly(2)) + ->method('getIriFromResource') + ->willReturnCallback(function ($resource) use ($oldRelatedDummy, $newRelatedDummy) { + static $i = 0; + $expected = [$oldRelatedDummy, $newRelatedDummy]; + TestCase::assertSame($expected[$i], $resource); + $ret = ['/related_dummies/old', '/related_dummies/new'][$i]; + ++$i; + + return $ret; + }) + ; + + $resourceClassResolverProphecy = $this->createMock(ResourceClassResolverInterface::class); + $resourceClassResolverProphecy->expects($this->atLeastOnce())->method('isResourceClass')->willReturn(true); + + $emProphecy = $this->createMock(EntityManagerInterface::class); $classMetadata = new ClassMetadata(Dummy::class); $classMetadata->mapManyToOne(['fieldName' => 'relatedDummy', 'targetEntity' => RelatedDummy::class, 'inversedBy' => 'dummies']); - $emProphecy->getClassMetadata(Dummy::class)->willReturn($classMetadata)->shouldBeCalled(); + $emProphecy->expects($this->once())->method('getClassMetadata')->with(Dummy::class)->willReturn($classMetadata); $changeSet = ['relatedDummy' => [$oldRelatedDummy, $newRelatedDummy]]; - $em = $emProphecy->reveal(); + $em = $emProphecy; $eventArgs = new PreUpdateEventArgs($dummy, $em, $changeSet); - $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class); + $propertyAccessorProphecy = $this->createMock(PropertyAccessorInterface::class); $listener = new PurgeHttpCacheListener( - iriConverter: $iriConverterProphecy->reveal(), - resourceClassResolver: $resourceClassResolverProphecy->reveal(), - propertyAccessor: $propertyAccessorProphecy->reveal(), - resourceMetadataCollectionFactory: $metadataFactoryProphecy->reveal(), - cacheManager: $cacheManagerProphecy->reveal(), - em: $em, + iriConverter: $iriConverterProphecy, + resourceClassResolver: $resourceClassResolverProphecy, + propertyAccessor: $propertyAccessorProphecy, + resourceMetadataCollectionFactory: $metadataFactoryProphecy, + cacheManager: $cacheManagerProphecy, + em: $emProphecy, ); $listener->preUpdate($eventArgs); $listener->postFlush(); @@ -256,37 +320,37 @@ public function testNothingToPurge(): void { $dummyNoGetOperation = new DummyNoGetOperation(); $dummyNoGetOperation->setId('1'); - $purgerProphecy = $this->prophesize(PurgerInterface::class); - $purgerProphecy->purge([])->shouldNotBeCalled(); + $purgerProphecy = $this->createMock(PurgerInterface::class); + $purgerProphecy->expects($this->never())->method('purge'); - $cacheManagerProphecy = $this->prophesize(CacheManager::class); - $cacheManagerProphecy->invalidateTags(Argument::any())->shouldNotBeCalled(); - $cacheManagerProphecy->flush()->willReturn(0); + $cacheManagerProphecy = $this->createMock(CacheManager::class); + $cacheManagerProphecy->expects($this->never())->method('invalidateTags'); + $cacheManagerProphecy->method('flush')->willReturn(0); - $metadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); + $metadataFactoryProphecy = $this->createMock(ResourceMetadataCollectionFactoryInterface::class); - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); + $iriConverterProphecy = $this->createMock(IriConverterInterface::class); - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); + $resourceClassResolverProphecy = $this->createMock(ResourceClassResolverInterface::class); - $emProphecy = $this->prophesize(EntityManagerInterface::class); + $emProphecy = $this->createMock(EntityManagerInterface::class); $classMetadata = new ClassMetadata(DummyNoGetOperation::class); - $emProphecy->getClassMetadata(DummyNoGetOperation::class)->willReturn($classMetadata)->shouldBeCalled(); + $emProphecy->expects($this->once())->method('getClassMetadata')->with(DummyNoGetOperation::class)->willReturn($classMetadata); $changeSet = ['lorem' => 'ipsum']; - $em = $emProphecy->reveal(); + $em = $emProphecy; $eventArgs = new PreUpdateEventArgs($dummyNoGetOperation, $em, $changeSet); - $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class); + $propertyAccessorProphecy = $this->createMock(PropertyAccessorInterface::class); $listener = new PurgeHttpCacheListener( - iriConverter: $iriConverterProphecy->reveal(), - resourceClassResolver: $resourceClassResolverProphecy->reveal(), - propertyAccessor: $propertyAccessorProphecy->reveal(), - resourceMetadataCollectionFactory: $metadataFactoryProphecy->reveal(), - cacheManager: $cacheManagerProphecy->reveal(), - em: $em, + iriConverter: $iriConverterProphecy, + resourceClassResolver: $resourceClassResolverProphecy, + propertyAccessor: $propertyAccessorProphecy, + resourceMetadataCollectionFactory: $metadataFactoryProphecy, + cacheManager: $cacheManagerProphecy, + em: $emProphecy, ); $listener->preUpdate($eventArgs); $listener->postFlush(); @@ -295,42 +359,42 @@ public function testNothingToPurge(): void { public function testNotAResourceClass(): void { $nonResource = new NotAResource('foo', 'bar'); - $cacheManagerProphecy = $this->prophesize(CacheManager::class); - $cacheManagerProphecy->invalidateTags(Argument::any())->shouldNotBeCalled(); - $cacheManagerProphecy->flush()->willReturn(0); + $cacheManagerProphecy = $this->createMock(CacheManager::class); + $cacheManagerProphecy->expects($this->never())->method('invalidateTags'); + $cacheManagerProphecy->method('flush')->willReturn(0); - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $iriConverterProphecy->getIriFromResource($nonResource)->shouldNotBeCalled(); + $iriConverterProphecy = $this->createMock(IriConverterInterface::class); + $iriConverterProphecy->expects($this->never())->method('getIriFromResource'); - $metadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); + $metadataFactoryProphecy = $this->createMock(ResourceMetadataCollectionFactoryInterface::class); - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $resourceClassResolverProphecy->isResourceClass(NotAResource::class)->willReturn(false)->shouldBeCalled(); + $resourceClassResolverProphecy = $this->createMock(ResourceClassResolverInterface::class); + $resourceClassResolverProphecy->expects($this->once())->method('isResourceClass')->with(NotAResource::class)->willReturn(false); - $uowProphecy = $this->prophesize(UnitOfWork::class); - $uowProphecy->getScheduledEntityInsertions()->willReturn([$nonResource])->shouldBeCalled(); - $uowProphecy->getScheduledEntityDeletions()->willReturn([])->shouldBeCalled(); - $uowProphecy->getScheduledEntityUpdates()->willReturn([])->shouldBeCalled(); - $uowProphecy->getScheduledCollectionUpdates()->willReturn([])->shouldBeCalled(); - $uowProphecy->getScheduledCollectionDeletions()->willReturn([])->shouldBeCalled(); + $uowProphecy = $this->createMock(UnitOfWork::class); + $uowProphecy->expects($this->once())->method('getScheduledEntityInsertions')->willReturn([$nonResource]); + $uowProphecy->expects($this->once())->method('getScheduledEntityDeletions')->willReturn([]); + $uowProphecy->expects($this->once())->method('getScheduledEntityUpdates')->willReturn([]); + $uowProphecy->expects($this->once())->method('getScheduledCollectionUpdates')->willReturn([]); + $uowProphecy->expects($this->once())->method('getScheduledCollectionDeletions')->willReturn([]); - $emProphecy = $this->prophesize(EntityManagerInterface::class); - $emProphecy->getUnitOfWork()->willReturn($uowProphecy->reveal())->shouldBeCalled(); + $emProphecy = $this->createMock(EntityManagerInterface::class); + $emProphecy->method('getUnitOfWork')->willReturn($uowProphecy); $dummyClassMetadata = new ClassMetadata(ContainNonResource::class); - $emProphecy->getClassMetadata(NotAResource::class)->willReturn($dummyClassMetadata); - $em = $emProphecy->reveal(); + $emProphecy->expects($this->once())->method('getClassMetadata')->with(NotAResource::class)->willReturn($dummyClassMetadata); + $em = $emProphecy; new OnFlushEventArgs($em); - $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class); + $propertyAccessorProphecy = $this->createMock(PropertyAccessorInterface::class); $listener = new PurgeHttpCacheListener( - iriConverter: $iriConverterProphecy->reveal(), - resourceClassResolver: $resourceClassResolverProphecy->reveal(), - propertyAccessor: $propertyAccessorProphecy->reveal(), - resourceMetadataCollectionFactory: $metadataFactoryProphecy->reveal(), - cacheManager: $cacheManagerProphecy->reveal(), - em: $em, + iriConverter: $iriConverterProphecy, + resourceClassResolver: $resourceClassResolverProphecy, + propertyAccessor: $propertyAccessorProphecy, + resourceMetadataCollectionFactory: $metadataFactoryProphecy, + cacheManager: $cacheManagerProphecy, + em: $emProphecy, ); $listener->onFlush(); } @@ -339,55 +403,74 @@ public function testPropertyIsNotAResourceClass(): void { $containNonResource = new ContainNonResource(); $nonResource = new NotAResource('foo', 'bar'); - $cacheManagerProphecy = $this->prophesize(CacheManager::class); - $cacheManagerProphecy->invalidateTags(Argument::any())->shouldNotBeCalled(); - $cacheManagerProphecy->flush()->willReturn(0); + $cacheManagerProphecy = $this->createMock(CacheManager::class); + $cacheManagerProphecy->expects($this->never())->method('invalidateTags'); + $cacheManagerProphecy->method('flush')->willReturn(0); - $metadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - $metadataFactoryProphecy->create(ContainNonResource::class)->willReturn(new ResourceMetadataCollection('ContainNonResource', [ + $metadataFactoryProphecy = $this->createMock(ResourceMetadataCollectionFactoryInterface::class); + $metadataFactoryProphecy->expects($this->once())->method('create')->with(ContainNonResource::class)->willReturn(new ResourceMetadataCollection('ContainNonResource', [ new ApiResource('ContainNonResource') ->withShortName('ContainNonResource'), - ]))->shouldBeCalled(); + ])); - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $iriConverterProphecy->getIriFromResource(ContainNonResource::class, UrlGeneratorInterface::ABS_PATH, Argument::any())->willReturn('/dummies/1'); - $iriConverterProphecy->getIriFromResource($nonResource)->shouldNotBeCalled(); + $iriConverterProphecy = $this->createMock(IriConverterInterface::class); + $that = $this; + $iriConverterProphecy->method('getIriFromResource')->willReturnCallback(function (object|string $resource, ...$args) use ($nonResource, $that): ?string { + $that->assertNotSame($nonResource, $resource, 'getIriFromResource should not be called with non-resource'); + if (ContainNonResource::class === $resource) { + return '/dummies/1'; + } - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $resourceClassResolverProphecy->getResourceClass(Argument::type(ContainNonResource::class))->willReturn(ContainNonResource::class)->shouldBeCalled(); - $resourceClassResolverProphecy->isResourceClass(ContainNonResource::class)->willReturn(true)->shouldBeCalled(); - $resourceClassResolverProphecy->isResourceClass(NotAResource::class)->willReturn(false)->shouldBeCalled(); + return null; + }); + + $resourceClassResolverProphecy = $this->createMock(ResourceClassResolverInterface::class); + $resourceClassResolverProphecy->method('getResourceClass')->willReturn(ContainNonResource::class); + $resourceClassResolverProphecy->expects($this->exactly(2))->method('isResourceClass')->willReturnCallback(function (string $class) { + if (ContainNonResource::class === $class) { + return true; + } + if (NotAResource::class === $class) { + return false; + } + TestCase::fail('Unexpected class passed to isResourceClass: '.$class); + }); - $uowProphecy = $this->prophesize(UnitOfWork::class); - $uowProphecy->getScheduledEntityInsertions()->willReturn([$containNonResource])->shouldBeCalled(); - $uowProphecy->getScheduledEntityDeletions()->willReturn([])->shouldBeCalled(); - $uowProphecy->getScheduledEntityUpdates()->willReturn([])->shouldBeCalled(); - $uowProphecy->getScheduledCollectionUpdates()->willReturn([])->shouldBeCalled(); - $uowProphecy->getScheduledCollectionDeletions()->willReturn([])->shouldBeCalled(); + $uowProphecy = $this->createMock(UnitOfWork::class); + $uowProphecy->expects($this->once())->method('getScheduledEntityInsertions')->willReturn([$containNonResource]); + $uowProphecy->expects($this->once())->method('getScheduledEntityDeletions')->willReturn([]); + $uowProphecy->expects($this->once())->method('getScheduledEntityUpdates')->willReturn([]); + $uowProphecy->expects($this->once())->method('getScheduledCollectionUpdates')->willReturn([]); + $uowProphecy->expects($this->once())->method('getScheduledCollectionDeletions')->willReturn([]); - $emProphecy = $this->prophesize(EntityManagerInterface::class); - $emProphecy->getUnitOfWork()->willReturn($uowProphecy->reveal())->shouldBeCalled(); + $emProphecy = $this->createMock(EntityManagerInterface::class); + $emProphecy->expects($this->once())->method('getUnitOfWork')->willReturn($uowProphecy); $dummyClassMetadata = new ClassMetadata(ContainNonResource::class); $dummyClassMetadata->mapManyToOne(['fieldName' => 'notAResource', 'targetEntity' => NotAResource::class, 'inversedBy' => 'resources']); $dummyClassMetadata->mapOneToMany(['fieldName' => 'collectionOfNotAResource', 'targetEntity' => NotAResource::class, 'mappedBy' => 'resource']); - $emProphecy->getClassMetadata(ContainNonResource::class)->willReturn($dummyClassMetadata); - $em = $emProphecy->reveal(); + $emProphecy->method('getClassMetadata')->willReturnCallback(function (string $class) use ($dummyClassMetadata) { + return match ($class) { + ContainNonResource::class => $dummyClassMetadata, + default => new ClassMetadata($class), + }; + }); + $em = $emProphecy; new OnFlushEventArgs($em); - $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class); - $propertyAccessorProphecy->isReadable(Argument::type(ContainNonResource::class), 'notAResource')->willReturn(true); - $propertyAccessorProphecy->isReadable(Argument::type(ContainNonResource::class), 'collectionOfNotAResource')->willReturn(true); - $propertyAccessorProphecy->getValue(Argument::type(ContainNonResource::class), 'notAResource')->shouldNotBeCalled(); - $propertyAccessorProphecy->getValue(Argument::type(ContainNonResource::class), 'collectionOfNotAResource')->shouldNotBeCalled(); + $propertyAccessorProphecy = $this->createMock(PropertyAccessorInterface::class); + $propertyAccessorProphecy->method('isReadable')->willReturnCallback(function ($obj, $prop) { + return $obj instanceof ContainNonResource && in_array($prop, ['notAResource', 'collectionOfNotAResource'], true); + }); + $propertyAccessorProphecy->expects($this->never())->method('getValue'); $listener = new PurgeHttpCacheListener( - iriConverter: $iriConverterProphecy->reveal(), - resourceClassResolver: $resourceClassResolverProphecy->reveal(), - propertyAccessor: $propertyAccessorProphecy->reveal(), - resourceMetadataCollectionFactory: $metadataFactoryProphecy->reveal(), - cacheManager: $cacheManagerProphecy->reveal(), - em: $em, + iriConverter: $iriConverterProphecy, + resourceClassResolver: $resourceClassResolverProphecy, + propertyAccessor: $propertyAccessorProphecy, + resourceMetadataCollectionFactory: $metadataFactoryProphecy, + cacheManager: $cacheManagerProphecy, + em: $emProphecy, ); $listener->onFlush(); } @@ -403,25 +486,37 @@ public function testInsertingShouldPurgeSubresourceCollections(): void { $relatedDummy->setId('100'); $toInsert1->setRelatedDummy($relatedDummy); - $this->uowProphecy->getScheduledEntityInsertions()->willReturn([$toInsert1]); - $this->uowProphecy->getScheduledEntityDeletions()->willReturn([]); - $this->uowProphecy->getScheduledEntityUpdates()->willReturn([])->shouldBeCalled(); - $this->uowProphecy->getScheduledCollectionUpdates()->willReturn([]); - $this->uowProphecy->getScheduledCollectionDeletions()->willReturn([]); + $this->uowProphecy->method('getScheduledEntityInsertions')->willReturn([$toInsert1]); + $this->uowProphecy->method('getScheduledEntityDeletions')->willReturn([]); + $this->uowProphecy->expects($this->once())->method('getScheduledEntityUpdates')->willReturn([]); + $this->uowProphecy->method('getScheduledCollectionUpdates')->willReturn([]); + $this->uowProphecy->method('getScheduledCollectionDeletions')->willReturn([]); // then - $this->cacheManagerProphecy->invalidateTags(['/dummies'])->willReturn($this->cacheManagerProphecy)->shouldBeCalled(); - $this->cacheManagerProphecy->invalidateTags(['/related_dummies/100/dummies'])->willReturn($this->cacheManagerProphecy)->shouldBeCalled(); + $this->cacheManagerProphecy->expects($this->exactly(2)) + ->method('invalidateTags') + ->willReturnCallback(function (array $tags) { + static $i = 0; + $expected = [ + ['/dummies'], + ['/related_dummies/100/dummies'], + ]; + TestCase::assertEquals($expected[$i], $tags); + ++$i; + + return $this->cacheManagerProphecy; + }) + ; // when - $em = $this->emProphecy->reveal(); + $em = $this->emProphecy; $listener = new PurgeHttpCacheListener( - iriConverter: $this->iriConverterProphecy->reveal(), - resourceClassResolver: $this->resourceClassResolverProphecy->reveal(), - propertyAccessor: $this->propertyAccessorProphecy->reveal(), - resourceMetadataCollectionFactory: $this->metadataFactoryProphecy->reveal(), - cacheManager: $this->cacheManagerProphecy->reveal(), + iriConverter: $this->iriConverterProphecy, + resourceClassResolver: $this->resourceClassResolverProphecy, + propertyAccessor: $this->propertyAccessorProphecy, + resourceMetadataCollectionFactory: $this->metadataFactoryProphecy, + cacheManager: $this->cacheManagerProphecy, em: $em, ); $listener->onFlush(); @@ -436,30 +531,41 @@ public function testDeleteShouldPurgeSubresourceCollections(): void { $relatedDummy->setId('100'); $toDelete1->setRelatedDummy($relatedDummy); - $uowMock = $this->createMock(UnitOfWork::class); - $uowMock->method('getScheduledEntityInsertions')->willReturn([]); - $uowMock->method('getScheduledEntityUpdates')->willReturn([]); - $uowMock->method('getScheduledEntityDeletions')->willReturn([$toDelete1]); - $uowMock->method('getScheduledCollectionUpdates')->willReturn([]); - $uowMock->method('getScheduledCollectionDeletions')->willReturn([]); - $uowMock->method('getEntityChangeSet')->willReturn([]); + $unitOfWork = $this->createMock(UnitOfWork::class); + $unitOfWork->method('getScheduledEntityInsertions')->willReturn([]); + $unitOfWork->method('getScheduledEntityUpdates')->willReturn([]); + $unitOfWork->method('getScheduledEntityDeletions')->willReturn([$toDelete1]); + $unitOfWork->method('getScheduledCollectionUpdates')->willReturn([]); + $unitOfWork->method('getScheduledCollectionDeletions')->willReturn([]); + $unitOfWork->method('getEntityChangeSet')->willReturn([]); - $this->emProphecy->getUnitOfWork()->willReturn($uowMock)->shouldBeCalled(); + $em = $this->createMock(EntityManagerInterface::class); + $em->method('getUnitOfWork')->willReturn($unitOfWork); // then - $this->cacheManagerProphecy->invalidateTags(['/dummies/1'])->willReturn($this->cacheManagerProphecy)->shouldBeCalled(); - $this->cacheManagerProphecy->invalidateTags(['/dummies'])->willReturn($this->cacheManagerProphecy)->shouldBeCalled(); - $this->cacheManagerProphecy->invalidateTags(['/related_dummies/100/dummies'])->willReturn($this->cacheManagerProphecy)->shouldBeCalled(); + $this->cacheManagerProphecy->expects($this->exactly(3)) + ->method('invalidateTags') + ->willReturnCallback(function (array $tags) { + static $i = 0; + $expected = [ + ['/dummies/1'], + ['/dummies'], + ['/related_dummies/100/dummies'], + ]; + TestCase::assertEquals($expected[$i], $tags); + ++$i; + + return $this->cacheManagerProphecy; + }) + ; // when - - $em = $this->emProphecy->reveal(); $listener = new PurgeHttpCacheListener( - iriConverter: $this->iriConverterProphecy->reveal(), - resourceClassResolver: $this->resourceClassResolverProphecy->reveal(), - propertyAccessor: $this->propertyAccessorProphecy->reveal(), - resourceMetadataCollectionFactory: $this->metadataFactoryProphecy->reveal(), - cacheManager: $this->cacheManagerProphecy->reveal(), + iriConverter: $this->iriConverterProphecy, + resourceClassResolver: $this->resourceClassResolverProphecy, + propertyAccessor: $this->propertyAccessorProphecy, + resourceMetadataCollectionFactory: $this->metadataFactoryProphecy, + cacheManager: $this->cacheManagerProphecy, em: $em, ); $listener->onFlush(); @@ -477,31 +583,39 @@ public function testUpdateShouldPurgeSubresourceCollections(): void { $relatedDummyOld = new RelatedDummy(); $relatedDummyOld->setId('99'); - $uowMock = $this->createMock(UnitOfWork::class); - $uowMock->method('getScheduledEntityInsertions')->willReturn([]); - $uowMock->method('getScheduledEntityUpdates')->willReturn([$toUpdate1]); - $uowMock->method('getScheduledEntityDeletions')->willReturn([]); - $uowMock->method('getScheduledCollectionUpdates')->willReturn([]); - $uowMock->method('getScheduledCollectionDeletions')->willReturn([]); - $uowMock->method('getEntityChangeSet')->willReturn(['relatedDummy' => [$relatedDummyOld, $relatedDummy]]); - - $this->emProphecy->getUnitOfWork()->willReturn($uowMock)->shouldBeCalled(); + $this->uowProphecy = $this->createMock(UnitOfWork::class); + $this->uowProphecy->method('getScheduledEntityInsertions')->willReturn([]); + $this->uowProphecy->method('getScheduledEntityUpdates')->willReturn([$toUpdate1]); + $this->uowProphecy->method('getScheduledEntityDeletions')->willReturn([]); + $this->uowProphecy->method('getScheduledCollectionUpdates')->willReturn([]); + $this->uowProphecy->method('getScheduledCollectionDeletions')->willReturn([]); + $this->uowProphecy->method('getEntityChangeSet')->willReturn(['relatedDummy' => [$relatedDummyOld, $relatedDummy]]); // then - $this->cacheManagerProphecy->invalidateTags(['/dummies/1'])->willReturn($this->cacheManagerProphecy)->shouldBeCalled(); - $this->cacheManagerProphecy->invalidateTags(['/related_dummies/100/dummies'])->willReturn($this->cacheManagerProphecy)->shouldBeCalled(); - $this->cacheManagerProphecy->invalidateTags(['/related_dummies/99/dummies'])->willReturn($this->cacheManagerProphecy)->shouldBeCalled(); + $this->cacheManagerProphecy->expects($this->exactly(3)) + ->method('invalidateTags') + ->willReturnCallback(function (array $tags) { + static $i = 0; + $expected = [ + ['/dummies/1'], + ['/related_dummies/100/dummies'], + ['/related_dummies/99/dummies'], + ]; + TestCase::assertEquals($expected[$i], $tags); + ++$i; + + return $this->cacheManagerProphecy; + }) + ; // when - - $em = $this->emProphecy->reveal(); $listener = new PurgeHttpCacheListener( - iriConverter: $this->iriConverterProphecy->reveal(), - resourceClassResolver: $this->resourceClassResolverProphecy->reveal(), - propertyAccessor: $this->propertyAccessorProphecy->reveal(), - resourceMetadataCollectionFactory: $this->metadataFactoryProphecy->reveal(), - cacheManager: $this->cacheManagerProphecy->reveal(), - em: $em, + iriConverter: $this->iriConverterProphecy, + resourceClassResolver: $this->resourceClassResolverProphecy, + propertyAccessor: $this->propertyAccessorProphecy, + resourceMetadataCollectionFactory: $this->metadataFactoryProphecy, + cacheManager: $this->cacheManagerProphecy, + em: $this->emProphecy, ); $listener->onFlush(); $listener->postFlush(); From adad3cd1519896345162841c39453eae68da9247 Mon Sep 17 00:00:00 2001 From: BacLuc Date: Sun, 7 Dec 2025 11:52:17 +0100 Subject: [PATCH 2/3] api: remove phpspec/prophecy-phpunit It's not needed anymore --- api/composer.json | 1 - api/composer.lock | 130 +--------------------------------------------- 2 files changed, 2 insertions(+), 129 deletions(-) diff --git a/api/composer.json b/api/composer.json index 9679eee5fd..4429adcfd0 100644 --- a/api/composer.json +++ b/api/composer.json @@ -70,7 +70,6 @@ "hautelook/alice-bundle": "2.16.0", "justinrainbow/json-schema": "6.6.3", "php-coveralls/php-coveralls": "2.9.0", - "phpspec/prophecy-phpunit": "2.4.0", "phpstan/phpstan": "2.1.33", "phpunit/phpunit": "12.5.4", "rector/rector": "2.2.14", diff --git a/api/composer.lock b/api/composer.lock index 7dc1bf714b..1ce6024098 100644 --- a/api/composer.lock +++ b/api/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "57cda39bd4bf6cf532e8ebf58ab7675f", + "content-hash": "48b6a8bc449778b431fc0bf7482b9595", "packages": [ { "name": "api-platform/doctrine-common", @@ -13032,132 +13032,6 @@ }, "time": "2025-11-06T10:39:48+00:00" }, - { - "name": "phpspec/prophecy", - "version": "v1.24.0", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "a24f1bda2d00a03877f7f99d9e6b150baf543f6d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/a24f1bda2d00a03877f7f99d9e6b150baf543f6d", - "reference": "a24f1bda2d00a03877f7f99d9e6b150baf543f6d", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.2 || ^2.0", - "php": "8.2.* || 8.3.* || 8.4.* || 8.5.*", - "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", - "sebastian/recursion-context": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", - "symfony/deprecation-contracts": "^2.5 || ^3.1" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.88", - "phpspec/phpspec": "^6.0 || ^7.0 || ^8.0", - "phpstan/phpstan": "^2.1.13", - "phpunit/phpunit": "^11.0 || ^12.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Prophecy\\": "src/Prophecy" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "dev", - "fake", - "mock", - "spy", - "stub" - ], - "support": { - "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.24.0" - }, - "time": "2025-11-21T13:10:52+00:00" - }, - { - "name": "phpspec/prophecy-phpunit", - "version": "v2.4.0", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy-phpunit.git", - "reference": "d3c28041d9390c9bca325a08c5b2993ac855bded" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy-phpunit/zipball/d3c28041d9390c9bca325a08c5b2993ac855bded", - "reference": "d3c28041d9390c9bca325a08c5b2993ac855bded", - "shasum": "" - }, - "require": { - "php": "^7.3 || ^8", - "phpspec/prophecy": "^1.18", - "phpunit/phpunit": "^9.1 || ^10.1 || ^11.0 || ^12.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.10" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "Prophecy\\PhpUnit\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christophe Coevoet", - "email": "stof@notk.org" - } - ], - "description": "Integrating the Prophecy mocking library in PHPUnit test cases", - "homepage": "http://phpspec.net", - "keywords": [ - "phpunit", - "prophecy" - ], - "support": { - "issues": "https://github.com/phpspec/prophecy-phpunit/issues", - "source": "https://github.com/phpspec/prophecy-phpunit/tree/v2.4.0" - }, - "time": "2025-05-13T13:52:32+00:00" - }, { "name": "phpstan/phpstan", "version": "2.1.33", @@ -15931,5 +15805,5 @@ "ext-iconv": "*" }, "platform-dev": {}, - "plugin-api-version": "2.9.0" + "plugin-api-version": "2.6.0" } From a6002bdd8f0f3990f84472d5ba29a42fe4b3b065 Mon Sep 17 00:00:00 2001 From: BacLuc Date: Mon, 22 Dec 2025 20:10:58 +0100 Subject: [PATCH 3/3] api: update reference.php --- api/config/reference.php | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/api/config/reference.php b/api/config/reference.php index 52ecacaa90..9db6ab4898 100644 --- a/api/config/reference.php +++ b/api/config/reference.php @@ -474,7 +474,7 @@ * max_host_connections?: int, // The maximum number of connections to a single host. * default_options?: array{ * headers?: array, - * vars?: list, + * vars?: array, * max_redirects?: int, // The maximum number of redirects to follow. * http_version?: scalar|null, // The default HTTP version, typically 1.1 or 2.0, leave to null for the best version. * resolve?: array, @@ -497,7 +497,7 @@ * md5?: mixed, * }, * crypto_method?: scalar|null, // The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants. - * extra?: list, + * extra?: array, * rate_limiter?: scalar|null, // Rate limiter name to use for throttling requests. // Default: null * caching?: bool|array{ // Caching configuration. * enabled?: bool, // Default: false @@ -550,7 +550,7 @@ * md5?: mixed, * }, * crypto_method?: scalar|null, // The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants. - * extra?: list, + * extra?: array, * rate_limiter?: scalar|null, // Rate limiter name to use for throttling requests. // Default: null * caching?: bool|array{ // Caching configuration. * enabled?: bool, // Default: false @@ -1241,7 +1241,7 @@ * inflector?: scalar|null, // Specify an inflector to use. // Default: "api_platform.metadata.inflector" * validator?: array{ * serialize_payload_fields?: mixed, // Set to null to serialize all payload fields when a validation error is thrown, or set the fields you want to include explicitly. // Default: [] - * query_parameter_validation?: bool, // Default: true + * query_parameter_validation?: bool, // Deprecated: Will be removed in API Platform 5.0. // Default: true * }, * eager_loading?: bool|array{ * enabled?: bool, // Default: true @@ -1251,11 +1251,13 @@ * }, * handle_symfony_errors?: bool, // Allows to handle symfony exceptions. // Default: false * enable_swagger?: bool, // Enable the Swagger documentation and export. // Default: true + * enable_json_streamer?: bool, // Enable json streamer. // Default: false * enable_swagger_ui?: bool, // Enable Swagger UI // Default: true * enable_re_doc?: bool, // Enable ReDoc // Default: true * enable_entrypoint?: bool, // Enable the entrypoint // Default: true * enable_docs?: bool, // Enable the docs // Default: true * enable_profiler?: bool, // Enable the data collector and the WebProfilerBundle integration. // Default: true + * enable_phpdoc_parser?: bool, // Enable resource metadata collector using PHPStan PhpDocParser. // Default: true * enable_link_security?: bool, // Enable security for Links (sub resources) // Default: false * collection?: array{ * exists_parameter_name?: scalar|null, // The name of the query parameter to filter on nullable field values. // Default: "exists" @@ -1271,6 +1273,7 @@ * }, * }, * mapping?: array{ + * imports?: list, * paths?: list, * }, * resource_class_directories?: list, @@ -1368,9 +1371,12 @@ * license?: array{ * name?: scalar|null, // The license name used for the API. // Default: null * url?: scalar|null, // URL to the license used for the API. MUST be in the format of a URL. // Default: null + * identifier?: scalar|null, // An SPDX license expression for the API. The identifier field is mutually exclusive of the url field. // Default: null * }, * swagger_ui_extra_configuration?: mixed, // To pass extra configuration to Swagger UI, like docExpansion or filter. // Default: [] * overrideResponses?: bool, // Whether API Platform adds automatic responses to the OpenAPI documentation. // Default: true + * error_resource_class?: scalar|null, // The class used to represent errors in the OpenAPI documentation. // Default: null + * validation_error_resource_class?: scalar|null, // The class used to represent validation errors in the OpenAPI documentation. // Default: null * }, * maker?: bool|array{ * enabled?: bool, // Default: true @@ -1461,7 +1467,9 @@ * parameters?: mixed, * strict_query_parameter_validation?: mixed, * hide_hydra_operation?: mixed, + * json_stream?: mixed, * extra_properties?: mixed, + * map?: mixed, * route_name?: mixed, * errors?: mixed, * read?: mixed, @@ -1512,6 +1520,11 @@ * intercept_redirects?: bool, // Default: false * excluded_ajax_paths?: scalar|null, // Default: "^/((index|app(_[\\w]+)?)\\.php/)?_wdt" * } + * @psalm-type MakerConfig = array{ + * root_namespace?: scalar|null, // Default: "App" + * generate_final_classes?: bool, // Default: true + * generate_final_entities?: bool, // Default: false + * } * @psalm-type DoctrineMigrationsConfig = array{ * enable_service_migrations?: bool, // Whether to enable fetching migrations from the service container. // Default: false * migrations_paths?: array, @@ -1750,6 +1763,9 @@ * uploadable?: scalar|null, // Default: "Gedmo\\Uploadable\\UploadableListener" * reference_integrity?: scalar|null, // Default: "Gedmo\\ReferenceIntegrity\\ReferenceIntegrityListener" * }, + * softdeleteable?: array{ + * handle_post_flush_event?: bool, // Default: false + * }, * uploadable?: array{ * default_file_path?: scalar|null, // Default: null * mime_type_guesser_class?: scalar|null, // Default: "Stof\\DoctrineExtensionsBundle\\Uploadable\\MimeTypeGuesserAdapter" @@ -1902,6 +1918,7 @@ * traces_sampler?: scalar|null, * profiles_sample_rate?: float, // The sampling factor to apply to profiles. A value of 0 will deny sending any profiles, and a value of 1 will send all profiles. Profiles are sampled in relation to traces_sample_rate * enable_logs?: bool, + * enable_metrics?: bool, // Default: true * attach_stacktrace?: bool, * attach_metric_code_locations?: bool, * context_lines?: int, @@ -1918,6 +1935,7 @@ * before_send_check_in?: scalar|null, * before_send_metrics?: scalar|null, * before_send_log?: scalar|null, + * before_send_metric?: scalar|null, * trace_propagation_targets?: mixed, * tags?: array, * error_types?: scalar|null, @@ -1936,7 +1954,7 @@ * http_ssl_verify_peer?: bool, * http_compression?: bool, * capture_silenced_errors?: bool, - * max_request_body_size?: "none"|"small"|"medium"|"always", + * max_request_body_size?: "none"|"never"|"small"|"medium"|"always", * class_serializers?: array, * }, * messenger?: bool|array{ @@ -2242,11 +2260,6 @@ * return_expiration?: scalar|null, // When true, the response will include the token expiration timestamp // Default: false * return_expiration_parameter_name?: scalar|null, // The default response parameter name containing the refresh token expiration timestamp // Default: "refresh_token_expiration" * } - * @psalm-type MakerConfig = array{ - * root_namespace?: scalar|null, // Default: "App" - * generate_final_classes?: bool, // Default: true - * generate_final_entities?: bool, // Default: false - * } * @psalm-type ConfigType = array{ * imports?: ImportsConfig, * parameters?: ParametersConfig,