diff --git a/src/Doctrine/Orm/AbstractPaginator.php b/src/Doctrine/Orm/AbstractPaginator.php index 3c3fa42e9ab..5324bf13217 100644 --- a/src/Doctrine/Orm/AbstractPaginator.php +++ b/src/Doctrine/Orm/AbstractPaginator.php @@ -32,7 +32,7 @@ public function __construct(DoctrinePaginator $paginator) { $query = $paginator->getQuery(); - if (null === ($firstResult = $query->getFirstResult()) || null === $maxResults = $query->getMaxResults()) { // @phpstan-ignore-line + if (null === ($firstResult = $query->getFirstResult()) || $firstResult < 0 || null === $maxResults = $query->getMaxResults()) { // @phpstan-ignore-line throw new InvalidArgumentException(sprintf('"%1$s::setFirstResult()" or/and "%1$s::setMaxResults()" was/were not applied to the query.', Query::class)); } diff --git a/src/Doctrine/Orm/Filter/ExistsFilter.php b/src/Doctrine/Orm/Filter/ExistsFilter.php index c5578d1b03d..67f89433501 100644 --- a/src/Doctrine/Orm/Filter/ExistsFilter.php +++ b/src/Doctrine/Orm/Filter/ExistsFilter.php @@ -18,6 +18,7 @@ use ApiPlatform\Doctrine\Orm\Util\QueryBuilderHelper; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; use ApiPlatform\Metadata\Operation; +use Doctrine\ORM\Mapping\AssociationMapping; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; @@ -223,7 +224,7 @@ protected function isNullableField(string $property, string $resourceClass): boo * * @see https://github.com/doctrine/doctrine2/blob/v2.5.4/lib/Doctrine/ORM/Tools/EntityGenerator.php#L1221-L1246 */ - private function isAssociationNullable(array $associationMapping): bool + private function isAssociationNullable(AssociationMapping|array $associationMapping): bool { if (!empty($associationMapping['id'])) { return false; diff --git a/src/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php b/src/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php index 1d1d1ccde34..9256d5fc056 100644 --- a/src/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php +++ b/src/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php @@ -16,6 +16,7 @@ use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\FieldMapping; use Doctrine\Persistence\ManagerRegistry; /** @@ -70,7 +71,11 @@ public function create(string $resourceClass, string $property, array $options = if ($doctrineClassMetadata instanceof ClassMetadata && \in_array($property, $doctrineClassMetadata->getFieldNames(), true)) { /** @var mixed[] */ $fieldMapping = $doctrineClassMetadata->getFieldMapping($property); - $propertyMetadata = $propertyMetadata->withDefault($fieldMapping['options']['default'] ?? $propertyMetadata->getDefault()); + if (class_exists(FieldMapping::class) && $fieldMapping instanceof FieldMapping) { + $propertyMetadata = $propertyMetadata->withDefault($fieldMapping->default ?? $propertyMetadata->getDefault()); + } else { + $propertyMetadata = $propertyMetadata->withDefault($fieldMapping['options']['default'] ?? $propertyMetadata->getDefault()); + } } return $propertyMetadata; diff --git a/tests/Doctrine/Orm/Extension/EagerLoadingExtensionTest.php b/tests/Doctrine/Orm/Extension/EagerLoadingExtensionTest.php index b1e38c1fa83..7b888cbdf7f 100644 --- a/tests/Doctrine/Orm/Extension/EagerLoadingExtensionTest.php +++ b/tests/Doctrine/Orm/Extension/EagerLoadingExtensionTest.php @@ -118,10 +118,10 @@ public function testApplyToCollection(): void $queryBuilderProphecy->getRootAliases()->willReturn(['o']); $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy); - $queryBuilderProphecy->leftJoin('o.relatedDummy', 'relatedDummy_a1')->shouldBeCalledTimes(1); - $queryBuilderProphecy->innerJoin('o.relatedDummy2', 'relatedDummy2_a2')->shouldBeCalledTimes(1); - $queryBuilderProphecy->addSelect('partial relatedDummy_a1.{id,name,embeddedDummy.name}')->shouldBeCalledTimes(1); - $queryBuilderProphecy->addSelect('partial relatedDummy2_a2.{id,name,embeddedDummy.name}')->shouldBeCalledTimes(1); + $queryBuilderProphecy->leftJoin('o.relatedDummy', 'relatedDummy_a1')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->innerJoin('o.relatedDummy2', 'relatedDummy2_a2')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addSelect('partial relatedDummy_a1.{id,name,embeddedDummy.name}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addSelect('partial relatedDummy2_a2.{id,name,embeddedDummy.name}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); $queryBuilderProphecy->getDQLPart('join')->willReturn([]); $queryBuilder = $queryBuilderProphecy->reveal(); @@ -224,26 +224,26 @@ public function testApplyToItem(): void $queryBuilderProphecy->getRootAliases()->willReturn(['o']); $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy); - $queryBuilderProphecy->leftJoin('o.relatedDummy', 'relatedDummy_a1')->shouldBeCalledTimes(1); - $queryBuilderProphecy->leftJoin('relatedDummy_a1.relation', 'relation_a2')->shouldBeCalledTimes(1); - $queryBuilderProphecy->leftJoin('relatedDummy_a1.thirdLevel', 'thirdLevel_a3')->shouldBeCalledTimes(1); - $queryBuilderProphecy->innerJoin('o.relatedDummy2', 'relatedDummy2_a4')->shouldBeCalledTimes(1); - $queryBuilderProphecy->leftJoin('o.relatedDummy3', 'relatedDummy3_a5')->shouldBeCalledTimes(1); - $queryBuilderProphecy->leftJoin('o.relatedDummy4', 'relatedDummy4_a6')->shouldBeCalledTimes(1); - $queryBuilderProphecy->leftJoin('o.singleInheritanceRelation', 'singleInheritanceRelation_a7')->shouldBeCalledTimes(1); - $queryBuilderProphecy->leftJoin('o.relatedDummies', 'relatedDummies_a8')->shouldBeCalledTimes(1); - $queryBuilderProphecy->leftJoin('relatedDummies_a8.relation', 'relation_a9')->shouldBeCalledTimes(1); - $queryBuilderProphecy->leftJoin('relatedDummies_a8.thirdLevel', 'thirdLevel_a10')->shouldBeCalledTimes(1); - $queryBuilderProphecy->addSelect('partial relatedDummy_a1.{id,name,embeddedDummy.name}')->shouldBeCalledTimes(1); - $queryBuilderProphecy->addSelect('partial thirdLevel_a3.{id}')->shouldBeCalledTimes(1); - $queryBuilderProphecy->addSelect('partial relation_a2.{id}')->shouldBeCalledTimes(1); - $queryBuilderProphecy->addSelect('partial relatedDummy2_a4.{id}')->shouldBeCalledTimes(1); - $queryBuilderProphecy->addSelect('partial relatedDummy3_a5.{id}')->shouldBeCalledTimes(1); - $queryBuilderProphecy->addSelect('partial relatedDummy4_a6.{id}')->shouldBeCalledTimes(1); - $queryBuilderProphecy->addSelect('singleInheritanceRelation_a7')->shouldBeCalledTimes(1); - $queryBuilderProphecy->addSelect('partial relatedDummies_a8.{id,name,embeddedDummy.name}')->shouldBeCalledTimes(1); - $queryBuilderProphecy->addSelect('partial relation_a9.{id}')->shouldBeCalledTimes(1); - $queryBuilderProphecy->addSelect('partial thirdLevel_a10.{id}')->shouldBeCalledTimes(1); + $queryBuilderProphecy->leftJoin('o.relatedDummy', 'relatedDummy_a1')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->leftJoin('relatedDummy_a1.relation', 'relation_a2')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->leftJoin('relatedDummy_a1.thirdLevel', 'thirdLevel_a3')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->innerJoin('o.relatedDummy2', 'relatedDummy2_a4')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->leftJoin('o.relatedDummy3', 'relatedDummy3_a5')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->leftJoin('o.relatedDummy4', 'relatedDummy4_a6')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->leftJoin('o.singleInheritanceRelation', 'singleInheritanceRelation_a7')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->leftJoin('o.relatedDummies', 'relatedDummies_a8')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->leftJoin('relatedDummies_a8.relation', 'relation_a9')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->leftJoin('relatedDummies_a8.thirdLevel', 'thirdLevel_a10')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addSelect('partial relatedDummy_a1.{id,name,embeddedDummy.name}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addSelect('partial thirdLevel_a3.{id}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addSelect('partial relation_a2.{id}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addSelect('partial relatedDummy2_a4.{id}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addSelect('partial relatedDummy3_a5.{id}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addSelect('partial relatedDummy4_a6.{id}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addSelect('singleInheritanceRelation_a7')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addSelect('partial relatedDummies_a8.{id,name,embeddedDummy.name}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addSelect('partial relation_a9.{id}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addSelect('partial thirdLevel_a10.{id}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); $queryBuilderProphecy->getDQLPart('join')->willReturn([]); $queryBuilderProphecy->getDQLPart('select')->willReturn([]); @@ -363,11 +363,13 @@ public function testMaxJoinsReached(): void $classMetadataProphecy->associationMappings = [ 'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => false]], 'targetEntity' => RelatedDummy::class], ]; + $classMetadataProphecy->hasField('relatedDummy')->willReturn(true); $relatedClassMetadataProphecy = $this->prophesize(ClassMetadata::class); $relatedClassMetadataProphecy->associationMappings = [ 'dummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => false]], 'targetEntity' => Dummy::class], ]; + $relatedClassMetadataProphecy->hasField('dummy')->willReturn(true); $emProphecy = $this->prophesize(EntityManager::class); $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal()); @@ -377,8 +379,8 @@ public function testMaxJoinsReached(): void $queryBuilderProphecy->getRootAliases()->willReturn(['o']); $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy); - $queryBuilderProphecy->innerJoin(Argument::type('string'), Argument::type('string'))->shouldBeCalled(); - $queryBuilderProphecy->addSelect(Argument::type('string'))->shouldBeCalled(); + $queryBuilderProphecy->innerJoin(Argument::type('string'), Argument::type('string'))->shouldBeCalled()->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addSelect(Argument::type('string'))->shouldBeCalled()->willReturn($queryBuilderProphecy); $queryBuilderProphecy->getDQLPart('join')->willReturn([]); $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true); @@ -410,11 +412,13 @@ public function testMaxDepth(): void $classMetadataProphecy->associationMappings = [ 'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => false]], 'targetEntity' => RelatedDummy::class], ]; + $classMetadataProphecy->hasField('relatedDummy')->willReturn(true); $relatedClassMetadataProphecy = $this->prophesize(ClassMetadata::class); $relatedClassMetadataProphecy->associationMappings = [ 'dummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => false]], 'targetEntity' => Dummy::class], ]; + $relatedClassMetadataProphecy->hasField('dummy')->willReturn(true); $dummyClassMetadataInterfaceProphecy = $this->prophesize(ClassMetadataInterface::class); $relatedClassMetadataInterfaceProphecy = $this->prophesize(ClassMetadataInterface::class); @@ -440,8 +444,8 @@ public function testMaxDepth(): void $queryBuilderProphecy->getRootAliases()->willReturn(['o']); $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy); - $queryBuilderProphecy->innerJoin(Argument::type('string'), Argument::type('string'))->shouldBeCalledTimes(2); - $queryBuilderProphecy->addSelect(Argument::type('string'))->shouldBeCalled(); + $queryBuilderProphecy->innerJoin(Argument::type('string'), Argument::type('string'))->shouldBeCalledTimes(2)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addSelect(Argument::type('string'))->shouldBeCalled()->willReturn($queryBuilderProphecy); $queryBuilderProphecy->getDQLPart('join')->willReturn([]); $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true, $classMetadataFactoryProphecy->reveal()); @@ -477,8 +481,8 @@ public function testForceEager(): void $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal()); $emProphecy->getClassMetadata(UnknownDummy::class)->shouldBeCalled()->willReturn($unknownClassMetadataProphecy->reveal()); - $queryBuilderProphecy->innerJoin('o.relation', 'relation_a1')->shouldBeCalledTimes(1); - $queryBuilderProphecy->addSelect('partial relation_a1.{id}')->shouldBeCalledTimes(1); + $queryBuilderProphecy->innerJoin('o.relation', 'relation_a1')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addSelect('partial relation_a1.{id}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); $queryBuilderProphecy->getDQLPart('join')->willReturn([]); $queryBuilderProphecy->getRootAliases()->willReturn(['o']); @@ -581,7 +585,7 @@ public function testResourceClassNotFoundExceptionPropertyNameCollection(): void $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); $queryBuilderProphecy->getRootAliases()->willReturn(['o']); $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy); - $queryBuilderProphecy->innerJoin('o.relation', 'relation_a1')->shouldBeCalledTimes(1); + $queryBuilderProphecy->innerJoin('o.relation', 'relation_a1')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); $queryBuilderProphecy->getDQLPart('join')->willReturn([]); $orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, true, true); @@ -635,10 +639,10 @@ public function testAttributes(): void $queryBuilderProphecy->getRootAliases()->willReturn(['o']); $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy); - $queryBuilderProphecy->leftJoin('o.relatedDummies', 'relatedDummies_a1')->shouldBeCalledTimes(1); - $queryBuilderProphecy->leftJoin('o.relatedDummy', 'relatedDummy_a2')->shouldBeCalledTimes(1); - $queryBuilderProphecy->addSelect('partial relatedDummies_a1.{id,name}')->shouldBeCalledTimes(1); - $queryBuilderProphecy->addSelect('partial relatedDummy_a2.{id,name}')->shouldBeCalledTimes(1); + $queryBuilderProphecy->leftJoin('o.relatedDummies', 'relatedDummies_a1')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->leftJoin('o.relatedDummy', 'relatedDummy_a2')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addSelect('partial relatedDummies_a1.{id,name}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addSelect('partial relatedDummy_a2.{id,name}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); $queryBuilderProphecy->getDQLPart('join')->willReturn([]); $queryBuilder = $queryBuilderProphecy->reveal(); @@ -723,8 +727,8 @@ public function testOnlyOneRelationNotInAttributes(): void $queryBuilderProphecy->getRootAliases()->willReturn(['o']); $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy); - $queryBuilderProphecy->leftJoin('o.relatedDummy', 'relatedDummy_a1')->shouldBeCalledTimes(1); - $queryBuilderProphecy->addSelect('partial relatedDummy_a1.{id,name}')->shouldBeCalledTimes(1); + $queryBuilderProphecy->leftJoin('o.relatedDummy', 'relatedDummy_a1')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addSelect('partial relatedDummy_a1.{id,name}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); $queryBuilderProphecy->getDQLPart('join')->willReturn([]); $queryBuilder = $queryBuilderProphecy->reveal(); @@ -760,10 +764,10 @@ public function testApplyToCollectionNoPartial(): void $queryBuilderProphecy->getRootAliases()->willReturn(['o']); $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy); - $queryBuilderProphecy->leftJoin('o.relatedDummy', 'relatedDummy_a1')->shouldBeCalledTimes(1); - $queryBuilderProphecy->innerJoin('o.relatedDummy2', 'relatedDummy2_a2')->shouldBeCalledTimes(1); - $queryBuilderProphecy->addSelect('relatedDummy_a1')->shouldBeCalledTimes(1); - $queryBuilderProphecy->addSelect('relatedDummy2_a2')->shouldBeCalledTimes(1); + $queryBuilderProphecy->leftJoin('o.relatedDummy', 'relatedDummy_a1')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->innerJoin('o.relatedDummy2', 'relatedDummy2_a2')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addSelect('relatedDummy_a1')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addSelect('relatedDummy2_a2')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); $queryBuilderProphecy->getDQLPart('join')->willReturn([]); $queryBuilderProphecy->getDQLPart('select')->willReturn([]); @@ -805,10 +809,10 @@ public function testApplyToCollectionWithANonReadableButFetchEagerProperty(): vo $queryBuilderProphecy->getRootAliases()->willReturn(['o']); $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy); - $queryBuilderProphecy->leftJoin('o.relatedDummy', 'relatedDummy_a1')->shouldBeCalledTimes(1); - $queryBuilderProphecy->innerJoin('o.relatedDummy2', 'relatedDummy2_a2')->shouldBeCalledTimes(1); - $queryBuilderProphecy->addSelect('relatedDummy_a1')->shouldBeCalledTimes(1); - $queryBuilderProphecy->addSelect('relatedDummy2_a2')->shouldBeCalledTimes(1); + $queryBuilderProphecy->leftJoin('o.relatedDummy', 'relatedDummy_a1')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->innerJoin('o.relatedDummy2', 'relatedDummy2_a2')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addSelect('relatedDummy_a1')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addSelect('relatedDummy2_a2')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); $queryBuilderProphecy->getDQLPart('join')->willReturn([]); $queryBuilderProphecy->getDQLPart('select')->willReturn([]); @@ -854,7 +858,7 @@ public function testApplyToCollectionWithExistingJoin(string $joinType): void ], ]); $queryBuilderProphecy->getDQLPart('select')->willReturn([]); - $queryBuilderProphecy->addSelect('existing_join_alias')->shouldBeCalledTimes(1); + $queryBuilderProphecy->addSelect('existing_join_alias')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy); $queryBuilder = $queryBuilderProphecy->reveal(); $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false); diff --git a/tests/Doctrine/Orm/Extension/OrderExtensionTest.php b/tests/Doctrine/Orm/Extension/OrderExtensionTest.php index 3717405f4cc..b4e9dea5a88 100644 --- a/tests/Doctrine/Orm/Extension/OrderExtensionTest.php +++ b/tests/Doctrine/Orm/Extension/OrderExtensionTest.php @@ -37,7 +37,7 @@ public function testApplyToCollectionWithValidOrder(): void $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); $queryBuilderProphecy->getDQLPart('orderBy')->shouldBeCalled()->willReturn([]); - $queryBuilderProphecy->addOrderBy('o.name', 'asc')->shouldBeCalled(); + $queryBuilderProphecy->addOrderBy('o.name', 'asc')->shouldBeCalled()->willReturn($queryBuilderProphecy); $classMetadataProphecy = $this->prophesize(ClassMetadata::class); $classMetadataProphecy->getIdentifier()->shouldBeCalled()->willReturn(['name']); @@ -79,7 +79,7 @@ public function testApplyToCollectionWithOrderOverridden(): void $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); $queryBuilderProphecy->getDQLPart('orderBy')->shouldBeCalled()->willReturn([]); - $queryBuilderProphecy->addOrderBy('o.foo', 'DESC')->shouldBeCalled(); + $queryBuilderProphecy->addOrderBy('o.foo', 'DESC')->shouldBeCalled()->willReturn($queryBuilderProphecy); $classMetadataProphecy = $this->prophesize(ClassMetadata::class); $classMetadataProphecy->getIdentifier()->shouldBeCalled()->willReturn(['name']); @@ -100,8 +100,8 @@ public function testApplyToCollectionWithOrderOverriddenWithNoDirection(): void $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); $queryBuilderProphecy->getDQLPart('orderBy')->shouldBeCalled()->willReturn([]); - $queryBuilderProphecy->addOrderBy('o.foo', 'ASC')->shouldBeCalled(); - $queryBuilderProphecy->addOrderBy('o.bar', 'DESC')->shouldBeCalled(); + $queryBuilderProphecy->addOrderBy('o.foo', 'ASC')->shouldBeCalled()->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addOrderBy('o.bar', 'DESC')->shouldBeCalled()->willReturn($queryBuilderProphecy); $classMetadataProphecy = $this->prophesize(ClassMetadata::class); $classMetadataProphecy->getIdentifier()->shouldBeCalled()->willReturn(['name']); @@ -123,8 +123,8 @@ public function testApplyToCollectionWithOrderOverriddenWithAssociation(): void $queryBuilderProphecy->getDQLPart('orderBy')->shouldBeCalled()->willReturn([]); $queryBuilderProphecy->getDQLPart('join')->willReturn(['o' => []])->shouldBeCalled(); - $queryBuilderProphecy->innerJoin('o.author', 'author_a1', null, null)->shouldBeCalled(); - $queryBuilderProphecy->addOrderBy('author_a1.name', 'ASC')->shouldBeCalled(); + $queryBuilderProphecy->innerJoin('o.author', 'author_a1', null, null)->shouldBeCalled()->willReturn($queryBuilderProphecy); + $queryBuilderProphecy->addOrderBy('author_a1.name', 'ASC')->shouldBeCalled()->willReturn($queryBuilderProphecy); $classMetadataProphecy = $this->prophesize(ClassMetadata::class); $classMetadataProphecy->getIdentifier()->shouldBeCalled()->willReturn(['name']); @@ -145,7 +145,7 @@ public function testApplyToCollectionWithOrderOverriddenWithEmbeddedAssociation( $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); $queryBuilderProphecy->getDQLPart('orderBy')->shouldBeCalled()->willReturn([]); $queryBuilderProphecy->getRootAliases()->willReturn(['o']); - $queryBuilderProphecy->addOrderBy('o.embeddedDummy.dummyName', 'DESC')->shouldBeCalled(); + $queryBuilderProphecy->addOrderBy('o.embeddedDummy.dummyName', 'DESC')->shouldBeCalled()->willReturn($queryBuilderProphecy); $classMetadataProphecy = $this->prophesize(ClassMetadata::class); $classMetadataProphecy->getIdentifier()->shouldBeCalled()->willReturn(['id']); diff --git a/tests/Doctrine/Orm/Extension/PaginationExtensionTest.php b/tests/Doctrine/Orm/Extension/PaginationExtensionTest.php index 98d325a3249..4daeea0862d 100644 --- a/tests/Doctrine/Orm/Extension/PaginationExtensionTest.php +++ b/tests/Doctrine/Orm/Extension/PaginationExtensionTest.php @@ -60,7 +60,7 @@ public function testApplyToCollection(): void $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); $queryBuilderProphecy->setFirstResult(40)->willReturn($queryBuilderProphecy)->shouldBeCalled(); - $queryBuilderProphecy->setMaxResults(40)->shouldBeCalled(); + $queryBuilderProphecy->setMaxResults(40)->shouldBeCalled()->willReturn($queryBuilderProphecy); $queryBuilder = $queryBuilderProphecy->reveal(); $extension = new PaginationExtension( @@ -79,7 +79,7 @@ public function testApplyToCollectionWithItemPerPageZero(): void $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); $queryBuilderProphecy->setFirstResult(0)->willReturn($queryBuilderProphecy)->shouldBeCalled(); - $queryBuilderProphecy->setMaxResults(0)->shouldBeCalled(); + $queryBuilderProphecy->setMaxResults(0)->shouldBeCalled()->willReturn($queryBuilderProphecy); $queryBuilder = $queryBuilderProphecy->reveal(); $extension = new PaginationExtension( @@ -101,7 +101,7 @@ public function testApplyToCollectionWithItemPerPageZeroAndPage2(): void $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); $queryBuilderProphecy->setFirstResult(0)->willReturn($queryBuilderProphecy)->shouldNotBeCalled(); - $queryBuilderProphecy->setMaxResults(0)->shouldNotBeCalled(); + $queryBuilderProphecy->setMaxResults(0)->shouldNotBeCalled()->willReturn($queryBuilderProphecy); $queryBuilder = $queryBuilderProphecy->reveal(); $extension = new PaginationExtension( @@ -123,7 +123,7 @@ public function testApplyToCollectionWithItemPerPageLessThan0(): void $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); $queryBuilderProphecy->setFirstResult(40)->willReturn($queryBuilderProphecy)->shouldNotBeCalled(); - $queryBuilderProphecy->setMaxResults(40)->shouldNotBeCalled(); + $queryBuilderProphecy->setMaxResults(40)->shouldNotBeCalled()->willReturn($queryBuilderProphecy); $queryBuilder = $queryBuilderProphecy->reveal(); $extension = new PaginationExtension( @@ -142,7 +142,7 @@ public function testApplyToCollectionWithItemPerPageTooHigh(): void $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); $queryBuilderProphecy->setFirstResult(300)->willReturn($queryBuilderProphecy)->shouldBeCalled(); - $queryBuilderProphecy->setMaxResults(300)->shouldBeCalled(); + $queryBuilderProphecy->setMaxResults(300)->shouldBeCalled()->willReturn($queryBuilderProphecy); $queryBuilder = $queryBuilderProphecy->reveal(); $extension = new PaginationExtension( @@ -158,7 +158,7 @@ public function testApplyToCollectionWithGraphql(): void $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); $queryBuilderProphecy->setFirstResult(10)->willReturn($queryBuilderProphecy)->shouldBeCalled(); - $queryBuilderProphecy->setMaxResults(5)->shouldBeCalled(); + $queryBuilderProphecy->setMaxResults(5)->shouldBeCalled()->willReturn($queryBuilderProphecy); $queryBuilder = $queryBuilderProphecy->reveal(); $extension = new PaginationExtension( @@ -174,7 +174,7 @@ public function testApplyToCollectionNofilters(): void $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); $queryBuilderProphecy->setFirstResult(0)->willReturn($queryBuilderProphecy)->shouldBeCalled(); - $queryBuilderProphecy->setMaxResults(30)->shouldBeCalled(); + $queryBuilderProphecy->setMaxResults(30)->shouldBeCalled()->willReturn($queryBuilderProphecy); $queryBuilder = $queryBuilderProphecy->reveal(); $extension = new PaginationExtension( @@ -230,7 +230,7 @@ public function testApplyToCollectionWithMaximumItemsPerPage(): void $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); $queryBuilderProphecy->setFirstResult(0)->willReturn($queryBuilderProphecy)->shouldBeCalled(); - $queryBuilderProphecy->setMaxResults(80)->shouldBeCalled(); + $queryBuilderProphecy->setMaxResults(80)->shouldBeCalled()->willReturn($queryBuilderProphecy); $queryBuilder = $queryBuilderProphecy->reveal(); $extension = new PaginationExtension( diff --git a/tests/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactoryTest.php b/tests/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactoryTest.php index 6f1ff2790b3..5a3bce5f482 100644 --- a/tests/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactoryTest.php +++ b/tests/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactoryTest.php @@ -19,6 +19,7 @@ use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyPropertyWithDefaultValue; use Doctrine\ORM\Mapping\ClassMetadata as ORMClassMetadata; +use Doctrine\ORM\Mapping\FieldMapping; use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\ObjectManager; @@ -75,7 +76,7 @@ public function testCreateIsWritable(): void $propertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); $propertyMetadataFactory->create(Dummy::class, 'id', [])->shouldBeCalled()->willReturn($propertyMetadata); - $classMetadata = $this->prophesize(ClassMetadata::class); + $classMetadata = $this->prophesize(ORMClassMetadata::class); $classMetadata->getIdentifier()->shouldBeCalled()->willReturn(['id']); $classMetadata->getFieldNames()->shouldBeCalled()->willReturn([]); @@ -100,11 +101,20 @@ public function testCreateWithDefaultOption(): void $propertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); $propertyMetadataFactory->create(DummyPropertyWithDefaultValue::class, 'dummyDefaultOption', [])->shouldBeCalled()->willReturn($propertyMetadata); - $classMetadata = new ORMClassMetadata(DummyPropertyWithDefaultValue::class); - // @phpstan-ignore-next-line - $classMetadata->fieldMappings = [ - 'dummyDefaultOption' => ['options' => ['default' => 'default value']], - ]; + $classMetadata = $this->prophesize(ORMClassMetadata::class); + $classMetadata->getIdentifier()->shouldBeCalled()->willReturn(['id']); + $classMetadata->getFieldNames()->shouldBeCalled()->willReturn(['dummyDefaultOption']); + if (class_exists(FieldMapping::class)) { + $fieldMapping = new FieldMapping('string', 'dummyDefaultOption', 'dummyDefaultOption'); + $fieldMapping->default = 'default value'; + $classMetadata->fieldMappings = [$fieldMapping]; + $classMetadata->getFieldMapping('dummyDefaultOption')->willReturn($fieldMapping); + } else { + // @phpstan-ignore-next-line + $classMetadata->fieldMappings = [ + 'dummyDefaultOption' => ['options' => ['default' => 'default value']], + ]; + } $objectManager = $this->prophesize(ObjectManager::class); $objectManager->getClassMetadata(DummyPropertyWithDefaultValue::class)->shouldBeCalled()->willReturn($classMetadata); @@ -126,7 +136,7 @@ public function testCreateClassMetadataInfo(): void $propertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); $propertyMetadataFactory->create(Dummy::class, 'id', [])->shouldBeCalled()->willReturn($propertyMetadata); - $classMetadata = $this->prophesize(ClassMetadata::class); + $classMetadata = $this->prophesize(ORMClassMetadata::class); $classMetadata->getIdentifier()->shouldBeCalled()->willReturn(['id']); $classMetadata->isIdentifierNatural()->shouldBeCalled()->willReturn(true); $classMetadata->getFieldNames()->shouldBeCalled()->willReturn([]); diff --git a/tests/Doctrine/Orm/Metadata/Resource/DoctrineOrmLinkFactoryTest.php b/tests/Doctrine/Orm/Metadata/Resource/DoctrineOrmLinkFactoryTest.php index afde43377bc..2cc94779059 100644 --- a/tests/Doctrine/Orm/Metadata/Resource/DoctrineOrmLinkFactoryTest.php +++ b/tests/Doctrine/Orm/Metadata/Resource/DoctrineOrmLinkFactoryTest.php @@ -26,11 +26,12 @@ use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Model\Car; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\AssociationMapping; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\InverseSideMapping; use Doctrine\Persistence\ManagerRegistry; -use Doctrine\Persistence\Mapping\ClassMetadata; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; -use Doctrine\ORM\Mapping\InverseSideMapping; final class DoctrineOrmLinkFactoryTest extends TestCase { @@ -53,6 +54,12 @@ public function testCreateLinksFromRelations(): void $classMetadataProphecy->getAssociationMappedByTargetField('relatedNonResource')->willReturn('dummies'); $classMetadataProphecy->getAssociationMappedByTargetField('relatedDummy')->willReturn(null); $classMetadataProphecy->getAssociationMappedByTargetField('relatedDummies')->willReturn('dummies'); + if (class_exists(InverseSideMapping::class)) { + $classMetadataProphecy->getAssociationMapping('relatedNonResource')->willReturn(new class('a', 'a', 'a') extends AssociationMapping {}); + $classMetadataProphecy->getAssociationMapping('relatedDummy')->willReturn(new class('a', 'a', 'a') extends AssociationMapping {}); + $classMetadataProphecy->getAssociationMapping('relatedDummies')->willReturn(new class('a', 'a', 'a') extends InverseSideMapping {}); + } + $entityManagerProphecy = $this->prophesize(EntityManagerInterface::class); $entityManagerProphecy->getClassMetadata($class)->willReturn($classMetadataProphecy->reveal()); $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); @@ -74,34 +81,6 @@ public function testCreateLinksFromRelations(): void ), ], $doctrineOrmLinkFactory->createLinksFromRelations($operation)); } - - public function testCreateLinksFromRelationsDoctrine3(): void - { - if (!class_exists(InverseSideMapping::class)) { - $this->markTestSkipped(); - } - - $class = Dummy::class; - $operation = (new Get())->withClass($class); - - $classMetadataProphecy = $this->prophesize(ClassMetadata::class); - $classMetadataProphecy->hasAssociation('noMappedBy')->willReturn(true); - $classMetadataProphecy->getAssociationTargetClass('noMappedBy')->willReturn('NoMappedByClass'); - $classMetadataProphecy->getAssociationMappedByTargetField('noMappedBy')->shouldNotBeCalled(); - $entityManagerProphecy = $this->prophesize(EntityManagerInterface::class); - $entityManagerProphecy->getClassMetadata($class)->willReturn($classMetadataProphecy->reveal()); - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass($class)->willReturn($entityManagerProphecy->reveal()); - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create($class)->willReturn(new PropertyNameCollection(['noMappedBy'])); - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $resourceClassResolverProphecy->isResourceClass(Car::class)->shouldNotBeCalled(); - - $doctrineOrmLinkFactory = new DoctrineOrmLinkFactory($managerRegistryProphecy->reveal(), $propertyNameCollectionFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), new LinkFactoryStub()); - - self::assertEquals([ - ], $doctrineOrmLinkFactory->createLinksFromRelations($operation)); - } } class LinkFactoryStub implements LinkFactoryInterface, PropertyLinkFactoryInterface diff --git a/tests/Doctrine/Orm/PaginatorTest.php b/tests/Doctrine/Orm/PaginatorTest.php index 77eabe85f92..151d2379e06 100644 --- a/tests/Doctrine/Orm/PaginatorTest.php +++ b/tests/Doctrine/Orm/PaginatorTest.php @@ -15,7 +15,7 @@ use ApiPlatform\Doctrine\Orm\Paginator; use ApiPlatform\Exception\InvalidArgumentException; -use ApiPlatform\Tests\Fixtures\Query; +use Doctrine\ORM\Query; use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; @@ -78,7 +78,7 @@ private function getPaginator(int $firstResult = 1, int $maxResults = 15, int $t private function getPaginatorWithMalformedQuery(bool $maxResults = false): void { $query = $this->prophesize(Query::class); - $query->getFirstResult()->willReturn($maxResults ? 42 : null)->shouldBeCalled(); + $query->getFirstResult()->willReturn($maxResults ? 42 : -1)->shouldBeCalled(); if ($maxResults) { $query->getMaxResults()->willReturn(null)->shouldBeCalled(); diff --git a/tests/Doctrine/Orm/State/CollectionProviderTest.php b/tests/Doctrine/Orm/State/CollectionProviderTest.php index f376b550fcd..a6a80a4a2d7 100644 --- a/tests/Doctrine/Orm/State/CollectionProviderTest.php +++ b/tests/Doctrine/Orm/State/CollectionProviderTest.php @@ -17,21 +17,21 @@ use ApiPlatform\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface; use ApiPlatform\Doctrine\Orm\State\CollectionProvider; use ApiPlatform\Doctrine\Orm\State\Options; +use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; use ApiPlatform\Exception\RuntimeException; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\OperationResource; -use Doctrine\ORM\AbstractQuery; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Query; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ObjectManager; use Doctrine\Persistence\ObjectRepository; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; class CollectionProviderTest extends TestCase @@ -40,57 +40,80 @@ class CollectionProviderTest extends TestCase public function testGetCollection(): void { - $queryProphecy = $this->prophesize(AbstractQuery::class); - $queryProphecy->getResult()->willReturn([])->shouldBeCalled(); + $query = $this->createMock(Query::class); + $query->expects($this->once())->method('getResult')->willReturn([]); - $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); - $queryBuilderProphecy->getRootAliases()->willReturn(['alias']); - $queryBuilderProphecy->getQuery()->willReturn($queryProphecy->reveal())->shouldBeCalled(); - $queryBuilder = $queryBuilderProphecy->reveal(); + $queryBuilder = $this->createMock(QueryBuilder::class); + $queryBuilder->expects($this->once())->method('getQuery')->willReturn($query); - $repositoryProphecy = $this->prophesize(EntityRepository::class); - $repositoryProphecy->createQueryBuilder('o')->willReturn($queryBuilder)->shouldBeCalled(); + $repository = $this->createMock(EntityRepository::class); + $repository->expects($this->once())->method('createQueryBuilder')->with('o')->willReturn($queryBuilder); - $managerProphecy = $this->prophesize(ObjectManager::class); - $managerProphecy->getClassMetadata(OperationResource::class)->willReturn(new ClassMetadata(OperationResource::class)); - $managerProphecy->getRepository(OperationResource::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled(); + $manager = $this->createMock(ObjectManager::class); + $manager->expects($this->once())->method('getRepository')->with(OperationResource::class)->willReturn($repository); - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(OperationResource::class)->willReturn($managerProphecy->reveal())->shouldBeCalled(); + $managerRegistry = $this->createMock(ManagerRegistry::class); + $managerRegistry->expects($this->once())->method('getManagerForClass')->with(OperationResource::class)->willReturn($manager); $operation = (new GetCollection())->withClass(OperationResource::class)->withName('getCollection'); - $extensionProphecy = $this->prophesize(QueryCollectionExtensionInterface::class); - $extensionProphecy->applyToCollection($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), OperationResource::class, $operation, [])->shouldBeCalled(); + $extension = $this->createMock(QueryCollectionExtensionInterface::class); + $extension->expects($this->once())->method('applyToCollection')->with( + $queryBuilder, + new QueryNameGenerator(), + OperationResource::class, + $operation, + [] + ); + + $resourceMetadataCollectionFactory = $this->createMock(ResourceMetadataCollectionFactoryInterface::class); - $dataProvider = new CollectionProvider($this->prophesize(ResourceMetadataCollectionFactoryInterface::class)->reveal(), $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); + $dataProvider = new CollectionProvider($resourceMetadataCollectionFactory, $managerRegistry, [$extension]); $this->assertEquals([], $dataProvider->provide($operation)); } public function testQueryResultExtension(): void { - $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); - $queryBuilderProphecy->getRootAliases()->willReturn(['alias']); - $queryBuilder = $queryBuilderProphecy->reveal(); + $queryBuilderMock = $this->createMock(QueryBuilder::class); + $queryBuilderMock->method('getRootAliases')->willReturn(['alias']); - $repositoryProphecy = $this->prophesize(EntityRepository::class); - $repositoryProphecy->createQueryBuilder('o')->willReturn($queryBuilder)->shouldBeCalled(); + $repositoryMock = $this->createMock(EntityRepository::class); + $repositoryMock->method('createQueryBuilder')->willReturn($queryBuilderMock); - $managerProphecy = $this->prophesize(ObjectManager::class); - $managerProphecy->getClassMetadata(OperationResource::class)->willReturn(new ClassMetadata(OperationResource::class)); - $managerProphecy->getRepository(OperationResource::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled(); + $managerMock = $this->createMock(ObjectManager::class); + $managerMock->method('getClassMetadata')->willReturn(new ClassMetadata(OperationResource::class)); + $managerMock->method('getRepository')->willReturn($repositoryMock); - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(OperationResource::class)->willReturn($managerProphecy->reveal())->shouldBeCalled(); + $managerRegistryMock = $this->createMock(ManagerRegistry::class); + $managerRegistryMock->method('getManagerForClass')->willReturn($managerMock); $operation = (new GetCollection())->withClass(OperationResource::class); - $extensionProphecy = $this->prophesize(QueryResultCollectionExtensionInterface::class); - $extensionProphecy->applyToCollection($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), OperationResource::class, $operation, [])->shouldBeCalled(); - $extensionProphecy->supportsResult(OperationResource::class, $operation, [])->willReturn(true)->shouldBeCalled(); - $extensionProphecy->getResult($queryBuilder, OperationResource::class, $operation, [])->willReturn([])->shouldBeCalled(); + $extensionMock = $this->createMock(QueryResultCollectionExtensionInterface::class); + $extensionMock->expects($this->once()) + ->method('applyToCollection') + ->with( + $queryBuilderMock, + $this->isInstanceOf(QueryNameGeneratorInterface::class), + OperationResource::class, + $operation, + [] + ); + $extensionMock->expects($this->once()) + ->method('supportsResult') + ->with(OperationResource::class, $operation, []) + ->willReturn(true); + $extensionMock->expects($this->once()) + ->method('getResult') + ->with($queryBuilderMock, OperationResource::class, $operation, []) + ->willReturn([]); + + $dataProvider = new CollectionProvider( + $this->createMock(ResourceMetadataCollectionFactoryInterface::class), + $managerRegistryMock, + [$extensionMock] + ); - $dataProvider = new CollectionProvider($this->prophesize(ResourceMetadataCollectionFactoryInterface::class)->reveal(), $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); $this->assertEquals([], $dataProvider->provide($operation)); } @@ -116,7 +139,7 @@ public function testHandleLinksCallable(): void { $class = 'foo'; $resourceMetadata = $this->createStub(ResourceMetadataCollectionFactoryInterface::class); - $query = $this->createStub(AbstractQuery::class); + $query = $this->createStub(Query::class); $query->method('getResult')->willReturn([]); $qb = $this->createStub(QueryBuilder::class); $qb->method('getQuery')->willReturn($query); diff --git a/tests/Doctrine/Orm/State/ItemProviderTest.php b/tests/Doctrine/Orm/State/ItemProviderTest.php index 7d3b7107308..a86a408d05b 100644 --- a/tests/Doctrine/Orm/State/ItemProviderTest.php +++ b/tests/Doctrine/Orm/State/ItemProviderTest.php @@ -20,7 +20,6 @@ use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; use ApiPlatform\Exception\RuntimeException; use ApiPlatform\Metadata\Get; -use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Company; @@ -29,15 +28,16 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Types; -use Doctrine\ORM\AbstractQuery; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\Mapping\AssociationMapping; use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\ManyToOneAssociationMapping; +use Doctrine\ORM\Query; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ObjectRepository; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; class ItemProviderTest extends TestCase @@ -49,35 +49,38 @@ public function testGetItemSingleIdentifier(): void $returnObject = new \stdClass(); $context = ['foo' => 'bar', 'fetch_data' => true]; - $queryProphecy = $this->prophesize(AbstractQuery::class); - $queryProphecy->getOneOrNullResult()->willReturn($returnObject)->shouldBeCalled(); - $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); - $queryBuilderProphecy->getQuery()->willReturn($queryProphecy->reveal())->shouldBeCalled(); - $queryBuilderProphecy->andWhere('o.identifier = :identifier_p1')->shouldBeCalled(); - $queryBuilderProphecy->getRootAliases()->shouldBeCalled()->willReturn(['o']); - $queryBuilderProphecy->setParameter('identifier_p1', 1, Types::INTEGER)->shouldBeCalled(); + $queryMock = $this->createMock(Query::class); + $queryMock->method('getOneOrNullResult')->willReturn($returnObject); - $queryBuilder = $queryBuilderProphecy->reveal(); + $queryBuilderMock = $this->createMock(QueryBuilder::class); + $queryBuilderMock->method('getQuery')->willReturn($queryMock); + $queryBuilderMock->method('andWhere')->with('o.identifier = :identifier_p1'); + $queryBuilderMock->method('getRootAliases')->willReturn(['o']); + $queryBuilderMock->method('setParameter')->with('identifier_p1', 1, Types::INTEGER); - $managerRegistryProphecy = $this->getManagerRegistry(OperationResource::class, [ + $managerMock = $this->getManagerRegistry(OperationResource::class, [ 'identifier' => [ 'type' => Types::INTEGER, ], - ], $queryBuilder); + ], $queryBuilderMock); + $managerRegistryMock = $this->createMock(ManagerRegistry::class); + $managerRegistryMock->method('getManagerForClass')->with(OperationResource::class)->willReturn($managerMock); - /** @var HttpOperation */ $operation = (new Get())->withUriVariables([ 'identifier' => (new Link())->withFromClass(OperationResource::class) - ->withIdentifiers([ - 0 => 'identifier', - ]), + ->withIdentifiers([0 => 'identifier']), ])->withClass(OperationResource::class)->withName('get'); - $extensionProphecy = $this->prophesize(QueryItemExtensionInterface::class); - $extensionProphecy->applyToItem($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), OperationResource::class, ['identifier' => 1], $operation, $context)->shouldBeCalled(); + $extensionMock = $this->createMock(QueryItemExtensionInterface::class); + $extensionMock->method('applyToItem') + ->with($queryBuilderMock, $this->isInstanceOf(QueryNameGeneratorInterface::class), OperationResource::class, ['identifier' => 1], $operation, $context); - $dataProvider = new ItemProvider($this->prophesize(ResourceMetadataCollectionFactoryInterface::class)->reveal(), $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); + $dataProvider = new ItemProvider( + $this->createStub(ResourceMetadataCollectionFactoryInterface::class), + $managerRegistryMock, + [$extensionMock] + ); $this->assertEquals($returnObject, $dataProvider->provide($operation, ['identifier' => 1], $context)); } @@ -86,20 +89,19 @@ public function testGetItemDoubleIdentifier(): void { $returnObject = new \stdClass(); - $queryProphecy = $this->prophesize(AbstractQuery::class); - $queryProphecy->getOneOrNullResult()->willReturn($returnObject)->shouldBeCalled(); - - // $exprProphecy = $this->prophesize(Expr::class); - // $exprProphecy->eq('o.ida', ':id_ida')->willReturn($comparisonProphecy)->shouldBeCalled(); - // $exprProphecy->eq('o.idb', ':id_idb')->willReturn($comparisonProphecy)->shouldBeCalled(); + $queryMock = $this->createMock(Query::class); + $queryMock->method('getOneOrNullResult')->willReturn($returnObject); - $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); - $queryBuilderProphecy->getQuery()->willReturn($queryProphecy->reveal())->shouldBeCalled(); - $queryBuilderProphecy->andWhere('o.idb = :idb_p1')->shouldBeCalled(); - $queryBuilderProphecy->andWhere('o.ida = :ida_p2')->shouldBeCalled(); - $queryBuilderProphecy->getRootAliases()->shouldBeCalled()->willReturn(['o']); + $queryBuilderMock = $this->createMock(QueryBuilder::class); + $queryBuilderMock->method('getQuery')->willReturn($queryMock); + $queryBuilderMock->expects($this->exactly(2)) + ->method('andWhere') + ->withConsecutive(['o.idb = :idb_p1'], ['o.ida = :ida_p2']); + $queryBuilderMock->method('getRootAliases')->willReturn(['o']); + $queryBuilderMock->expects($this->exactly(2)) + ->method('setParameter') + ->withConsecutive(['idb_p1', 2, Types::INTEGER], ['ida_p2', 1, Types::INTEGER]); - /** @var HttpOperation */ $operation = (new Get())->withUriVariables([ 'ida' => (new Link())->withFromClass(OperationResource::class) ->withIdentifiers([ @@ -111,25 +113,28 @@ public function testGetItemDoubleIdentifier(): void ]), ])->withName('get')->withClass(OperationResource::class); - $queryBuilderProphecy->setParameter('idb_p1', 2, Types::INTEGER)->shouldBeCalled(); - $queryBuilderProphecy->setParameter('ida_p2', 1, Types::INTEGER)->shouldBeCalled(); - - $queryBuilder = $queryBuilderProphecy->reveal(); - - $managerRegistryProphecy = $this->getManagerRegistry(OperationResource::class, [ + $managerMock = $this->getManagerRegistry(OperationResource::class, [ 'ida' => [ 'type' => Types::INTEGER, ], 'idb' => [ 'type' => Types::INTEGER, ], - ], $queryBuilder); + ], $queryBuilderMock); + $managerRegistryMock = $this->createMock(ManagerRegistry::class); + $managerRegistryMock->method('getManagerForClass')->with(OperationResource::class)->willReturn($managerMock); $context = []; - $extensionProphecy = $this->prophesize(QueryItemExtensionInterface::class); - $extensionProphecy->applyToItem($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), OperationResource::class, ['ida' => 1, 'idb' => 2], $operation, $context)->shouldBeCalled(); + $extensionMock = $this->createMock(QueryItemExtensionInterface::class); + $extensionMock->expects($this->once()) + ->method('applyToItem') + ->with($queryBuilderMock, $this->isInstanceOf(QueryNameGeneratorInterface::class), OperationResource::class, ['ida' => 1, 'idb' => 2], $operation, $context); - $dataProvider = new ItemProvider($this->prophesize(ResourceMetadataCollectionFactoryInterface::class)->reveal(), $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); + $dataProvider = new ItemProvider( + $this->createStub(ResourceMetadataCollectionFactoryInterface::class), + $managerRegistryMock, + [$extensionMock] + ); $this->assertEquals($returnObject, $dataProvider->provide($operation, ['ida' => 1, 'idb' => 2], $context)); } @@ -138,20 +143,20 @@ public function testQueryResultExtension(): void { $returnObject = new \stdClass(); - $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); - $queryBuilderProphecy->andWhere('o.identifier = :identifier_p1')->shouldBeCalled(); - $queryBuilderProphecy->getRootAliases()->shouldBeCalled()->willReturn(['o']); - $queryBuilderProphecy->setParameter('identifier_p1', 1, Types::INTEGER)->shouldBeCalled(); + $queryBuilderMock = $this->createMock(QueryBuilder::class); + $queryBuilderMock->expects($this->once())->method('andWhere')->with('o.identifier = :identifier_p1'); + $queryBuilderMock->expects($this->once())->method('getRootAliases')->willReturn(['o']); + $queryBuilderMock->expects($this->once())->method('setParameter')->with('identifier_p1', 1, Types::INTEGER); - $queryBuilder = $queryBuilderProphecy->reveal(); - - $managerRegistryProphecy = $this->getManagerRegistry(OperationResource::class, [ + $managerMock = $this->getManagerRegistry(OperationResource::class, [ 'identifier' => [ 'type' => Types::INTEGER, ], - ], $queryBuilder); + ], $queryBuilderMock); + + $managerRegistryMock = $this->createMock(ManagerRegistry::class); + $managerRegistryMock->method('getManagerForClass')->with(OperationResource::class)->willReturn($managerMock); - /** @var HttpOperation */ $operation = (new Get())->withUriVariables([ 'identifier' => (new Link())->withFromClass(OperationResource::class)->withIdentifiers([ 0 => 'identifier', @@ -159,18 +164,34 @@ public function testQueryResultExtension(): void ])->withClass(OperationResource::class)->withName('get'); $context = []; - $extensionProphecy = $this->prophesize(QueryResultItemExtensionInterface::class); - $extensionProphecy->applyToItem($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), OperationResource::class, ['identifier' => 1], $operation, $context)->shouldBeCalled(); - $extensionProphecy->supportsResult(OperationResource::class, $operation, $context)->willReturn(true)->shouldBeCalled(); - $extensionProphecy->getResult($queryBuilder, OperationResource::class, $operation, $context)->willReturn($returnObject)->shouldBeCalled(); - - $dataProvider = new ItemProvider($this->prophesize(ResourceMetadataCollectionFactoryInterface::class)->reveal(), $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); + $extensionMock = $this->createMock(QueryResultItemExtensionInterface::class); + $extensionMock->expects($this->once()) + ->method('applyToItem') + ->with($queryBuilderMock, $this->isInstanceOf(QueryNameGeneratorInterface::class), OperationResource::class, ['identifier' => 1], $operation, $context); + $extensionMock->expects($this->once()) + ->method('supportsResult') + ->with(OperationResource::class, $operation, $context) + ->willReturn(true); + $extensionMock->expects($this->once()) + ->method('getResult') + ->with($queryBuilderMock, OperationResource::class, $operation, $context) + ->willReturn($returnObject); + + $dataProvider = new ItemProvider( + $this->createStub(ResourceMetadataCollectionFactoryInterface::class), + $managerRegistryMock, + [$extensionMock] + ); $this->assertEquals($returnObject, $dataProvider->provide($operation, ['identifier' => 1], $context)); } public function testCannotCreateQueryBuilder(): void { + if (class_exists(AssociationMapping::class)) { + $this->markTestSkipped(); + } + $this->expectException(RuntimeException::class); $this->expectExceptionMessage('The repository class must have a "createQueryBuilder" method.'); @@ -206,71 +227,77 @@ public function testCannotCreateQueryBuilder(): void */ private function getManagerRegistry(string $resourceClass, array $identifierFields, QueryBuilder $queryBuilder, array $classMetadatas = []) { - $classMetadataProphecy = $this->prophesize(ClassMetadata::class); - $classMetadataProphecy->getIdentifierFieldNames()->willReturn(array_keys($identifierFields)); + $classMetadataMock = $this->createMock(ClassMetadata::class); + $classMetadataMock->method('getIdentifierFieldNames')->willReturn(array_keys($identifierFields)); + $getTypeOfFieldExpectations = []; foreach ($identifierFields as $name => $field) { - $classMetadataProphecy->getTypeOfField($name)->willReturn($field['type']); + $getTypeOfFieldExpectations[$name] = $field['type']; } - $platformProphecy = $this->prophesize(AbstractPlatform::class); + $classMetadataMock->method('getTypeOfField') + ->with($this->logicalOr(...array_keys($identifierFields))) + ->willReturnCallback(function ($name) use ($getTypeOfFieldExpectations) { + return $getTypeOfFieldExpectations[$name]; + }); - $connectionProphecy = $this->prophesize(Connection::class); - $connectionProphecy->getDatabasePlatform()->willReturn($platformProphecy); + $platformMock = $this->createMock(AbstractPlatform::class); - $repositoryProphecy = $this->prophesize(EntityRepository::class); - $repositoryProphecy->createQueryBuilder('o')->willReturn($queryBuilder); + $connectionMock = $this->createMock(Connection::class); + $connectionMock->method('getDatabasePlatform')->willReturn($platformMock); - $managerProphecy = $this->prophesize(EntityManagerInterface::class); - $managerProphecy->getClassMetadata($resourceClass)->willReturn($classMetadataProphecy->reveal()); - $managerProphecy->getConnection()->willReturn($connectionProphecy); - $managerProphecy->getRepository($resourceClass)->willReturn($repositoryProphecy->reveal()); + $repositoryMock = $this->createMock(EntityRepository::class); + $repositoryMock->method('createQueryBuilder')->with('o')->willReturn($queryBuilder); + + $managerMock = $this->createMock(EntityManagerInterface::class); + $managerMock->method('getConnection')->willReturn($connectionMock); + $managerMock->method('getRepository')->with($resourceClass)->willReturn($repositoryMock); + + $classMetadataExpectations = [$resourceClass => $classMetadataMock]; foreach ($classMetadatas as $class => $classMetadata) { - $managerProphecy->getClassMetadata($class)->willReturn($classMetadata); + $classMetadataExpectations[$class] = $classMetadata; } - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass($resourceClass)->willReturn($managerProphecy->reveal()); + $managerMock->method('getClassMetadata') + ->with($this->logicalOr(...array_keys($classMetadataExpectations))) + ->willReturnCallback(function ($name) use ($classMetadataExpectations) { + return $classMetadataExpectations[$name]; + }); - return $managerRegistryProphecy; + return $managerMock; } public function testGetSubresourceFromProperty(): void { $returnObject = new \stdClass(); - $queryProphecy = $this->prophesize(AbstractQuery::class); - $queryProphecy->getOneOrNullResult()->willReturn($returnObject)->shouldBeCalled(); - - $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); - $queryBuilderProphecy->join(Employee::class, 'm_a1', 'WITH', 'o.id = m_a1.company')->shouldBeCalled(); - $queryBuilderProphecy->getQuery()->willReturn($queryProphecy->reveal())->shouldBeCalled(); - $queryBuilderProphecy->getRootAliases()->shouldBeCalled()->willReturn(['o']); - $queryBuilderProphecy->andWhere('m_a1.id = :id_p1')->shouldBeCalled(); - $queryBuilderProphecy->setParameter('id_p1', 1, Types::INTEGER)->shouldBeCalled(); + $queryMock = $this->createMock(Query::class); + $queryMock->method('getOneOrNullResult')->willReturn($returnObject); - $queryBuilder = $queryBuilderProphecy->reveal(); + $queryBuilderMock = $this->createMock(QueryBuilder::class); + $queryBuilderMock->expects($this->once())->method('join')->with(Employee::class, 'm_a1', 'WITH', 'o.id = m_a1.company'); + $queryBuilderMock->expects($this->once())->method('getQuery')->willReturn($queryMock); + $queryBuilderMock->expects($this->once())->method('getRootAliases')->willReturn(['o']); + $queryBuilderMock->expects($this->once())->method('andWhere')->with('m_a1.id = :id_p1'); + $queryBuilderMock->expects($this->once())->method('setParameter')->with('id_p1', 1, Types::INTEGER); - $employeeClassMetadataProphecy = $this->prophesize(ClassMetadata::class); - $employeeClassMetadataProphecy->getAssociationMapping('company')->willReturn([ - 'type' => ClassMetadata::TO_ONE, - 'fieldName' => 'company', - ]); - $employeeClassMetadataProphecy->getTypeOfField('id')->willReturn(Types::INTEGER); + $employeeClassMetadataMock = $this->createMock(ClassMetadata::class); + $employeeClassMetadataMock->method('getAssociationMapping')->with('company')->willReturn(new ManyToOneAssociationMapping('company', Employee::class, Company::class)); + $employeeClassMetadataMock->method('getTypeOfField')->with('id')->willReturn(Types::INTEGER); - $managerRegistryProphecy = $this->getManagerRegistry(Company::class, [ + $managerMock = $this->getManagerRegistry(Company::class, [ 'id' => [ 'type' => Types::INTEGER, ], - ], $queryBuilder, [ - Employee::class => $employeeClassMetadataProphecy->reveal(), + ], $queryBuilderMock, [ + Employee::class => $employeeClassMetadataMock, ]); - $managerProphecy = $this->prophesize(EntityManagerInterface::class); - $managerRegistryProphecy->getManagerForClass(Employee::class)->willReturn($managerProphecy->reveal()); + $managerRegistryMock = $this->createMock(ManagerRegistry::class); + $managerRegistryMock->method('getManagerForClass')->with($this->logicalOr(Company::class, Employee::class)) + ->willReturn($managerMock); - /** @var HttpOperation */ $operation = (new Get())->withUriVariables([ 'employeeId' => (new Link())->withFromClass(Employee::class) ->withIdentifiers([ @@ -278,10 +305,16 @@ public function testGetSubresourceFromProperty(): void ])->withFromProperty('company'), ])->withName('getCompany')->withClass(Company::class); - $extensionProphecy = $this->prophesize(QueryItemExtensionInterface::class); - $extensionProphecy->applyToItem($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), Company::class, ['employeeId' => 1], $operation, [])->shouldBeCalled(); + $extensionMock = $this->createMock(QueryItemExtensionInterface::class); + $extensionMock->expects($this->once()) + ->method('applyToItem') + ->with($queryBuilderMock, $this->isInstanceOf(QueryNameGeneratorInterface::class), Company::class, ['employeeId' => 1], $operation, []); - $dataProvider = new ItemProvider($this->prophesize(ResourceMetadataCollectionFactoryInterface::class)->reveal(), $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); + $dataProvider = new ItemProvider( + $this->createStub(ResourceMetadataCollectionFactoryInterface::class), + $managerRegistryMock, + [$extensionMock] + ); $this->assertEquals($returnObject, $dataProvider->provide($operation, ['employeeId' => 1])); } @@ -290,7 +323,7 @@ public function testHandleLinksCallable(): void { $class = 'foo'; $resourceMetadata = $this->createStub(ResourceMetadataCollectionFactoryInterface::class); - $query = $this->createStub(AbstractQuery::class); + $query = $this->createStub(Query::class); $query->method('getOneOrNullResult')->willReturn(null); $qb = $this->createStub(QueryBuilder::class); $qb->method('getQuery')->willReturn($query); diff --git a/tests/Fixtures/TestBundle/Doctrine/Generator/UuidGenerator.php b/tests/Fixtures/TestBundle/Doctrine/Generator/UuidGenerator.php index ff483a28671..22e8a0d63bd 100644 --- a/tests/Fixtures/TestBundle/Doctrine/Generator/UuidGenerator.php +++ b/tests/Fixtures/TestBundle/Doctrine/Generator/UuidGenerator.php @@ -13,7 +13,6 @@ namespace ApiPlatform\Tests\Fixtures\TestBundle\Doctrine\Generator; -use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Id\AbstractIdGenerator; diff --git a/tests/Fixtures/TestBundle/Doctrine/Orm/EntityManager.php b/tests/Fixtures/TestBundle/Doctrine/Orm/EntityManager.php index 4b6b5cdf62e..a91c4b9b113 100644 --- a/tests/Fixtures/TestBundle/Doctrine/Orm/EntityManager.php +++ b/tests/Fixtures/TestBundle/Doctrine/Orm/EntityManager.php @@ -19,7 +19,6 @@ use Doctrine\ORM\Query; use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\Repository\RepositoryFactory; -use Doctrine\Persistence\ObjectRepository; final class EntityManager extends EntityManagerDecorator { diff --git a/tests/Fixtures/TestBundle/Entity/Issue6191/Book.php b/tests/Fixtures/TestBundle/Entity/Issue6191/Book.php new file mode 100644 index 00000000000..2c8f9a3ee89 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/Issue6191/Book.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6191; + +use ApiPlatform\Metadata\ApiResource; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\ORM\Mapping as ORM; + +#[ORM\Entity] +#[ApiResource(shortName: 'issue6191_book')] +class Book +{ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column] + private ?int $id = null; + + #[ORM\Column(length: 80, nullable: true)] + private ?string $title = null; + + #[ORM\OneToMany(targetEntity: Review::class, mappedBy: 'book', orphanRemoval: true)] + private Collection $reviews; + + public function __construct() + { + $this->reviews = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle(?string $title): static + { + $this->title = $title; + + return $this; + } + + /** + * @return Collection + */ + public function getReviews(): Collection + { + return $this->reviews; + } + + public function addReview(Review $review): static + { + if (!$this->reviews->contains($review)) { + $this->reviews->add($review); + $review->setBook($this); + } + + return $this; + } + + public function removeReview(Review $review): static + { + if ($this->reviews->removeElement($review)) { + // set the owning side to null (unless already changed) + if ($review->getBook() === $this) { + $review->setBook(null); + } + } + + return $this; + } +} diff --git a/tests/Fixtures/TestBundle/Entity/Issue6191/Review.php b/tests/Fixtures/TestBundle/Entity/Issue6191/Review.php new file mode 100644 index 00000000000..6b710067d5a --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/Issue6191/Review.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6191; + +use ApiPlatform\Metadata\ApiResource; +use Doctrine\ORM\Mapping as ORM; + +#[ORM\Entity] +#[ApiResource(shortName: 'issue6191_review')] +class Review +{ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column] + private ?int $id = null; + + #[ORM\ManyToOne(inversedBy: 'reviews')] + #[ORM\JoinColumn(nullable: false)] + private ?Book $book = null; + + public function getId(): ?int + { + return $this->id; + } + + public function getBook(): ?Book + { + return $this->book; + } + + public function setBook(?Book $book): static + { + $this->book = $book; + + return $this; + } +}