Skip to content

Commit f6ccfff

Browse files
vincentchalamondunglas
authored andcommitted
Fix 1246: configure default order on ApiResource annotation (#1321)
1 parent a774eab commit f6ccfff

File tree

7 files changed

+201
-2
lines changed

7 files changed

+201
-2
lines changed

features/bootstrap/FeatureContext.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\EmbeddableDummy;
2929
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\EmbeddedDummy;
3030
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy;
31+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Foo;
3132
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Node;
3233
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Question;
3334
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
@@ -132,6 +133,23 @@ public function thereIsDummyObjects($nb)
132133
$this->manager->flush();
133134
}
134135

136+
/**
137+
* @Given there are :nb foo objects with fake names
138+
*/
139+
public function thereAreFooObjectsWithFakeNames($nb)
140+
{
141+
$names = ['Hawsepipe', 'Sthenelus', 'Ephesian', 'Separativeness', 'Balbo'];
142+
143+
for ($i = 0; $i < $nb; ++$i) {
144+
$foo = new Foo();
145+
$foo->setName($names[$i]);
146+
147+
$this->manager->persist($foo);
148+
}
149+
150+
$this->manager->flush();
151+
}
152+
135153
/**
136154
* @Given there is :nb dummy group objects
137155
*/

features/main/default_order.feature

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
Feature: Default order
2+
In order to get a list in a specific order,
3+
As a client software developer,
4+
I need to be able to specify default order.
5+
6+
@createSchema @dropSchema
7+
Scenario: Override custom order
8+
Given there are 5 foo objects with fake names
9+
When I send a "GET" request to "/foos?itemsPerPage=10"
10+
Then the response status code should be 200
11+
And the response should be in JSON
12+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
13+
And the JSON should be equal to:
14+
"""
15+
{
16+
"@context": "/contexts/Foo",
17+
"@id": "/foos",
18+
"@type": "hydra:Collection",
19+
"hydra:member": [
20+
{
21+
"@id": "/foos/2",
22+
"@type": "Foo",
23+
"id": 2,
24+
"name": "Sthenelus"
25+
},
26+
{
27+
"@id": "/foos/4",
28+
"@type": "Foo",
29+
"id": 4,
30+
"name": "Separativeness"
31+
},
32+
{
33+
"@id": "/foos/1",
34+
"@type": "Foo",
35+
"id": 1,
36+
"name": "Hawsepipe"
37+
},
38+
{
39+
"@id": "/foos/3",
40+
"@type": "Foo",
41+
"id": 3,
42+
"name": "Ephesian"
43+
},
44+
{
45+
"@id": "/foos/5",
46+
"@type": "Foo",
47+
"id": 5,
48+
"name": "Balbo"
49+
}
50+
],
51+
"hydra:totalItems": 5,
52+
"hydra:view": {
53+
"@id": "/foos?itemsPerPage=10",
54+
"@type": "hydra:PartialCollectionView"
55+
}
56+
}
57+
"""

src/Bridge/Doctrine/Orm/Extension/OrderExtension.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,24 @@
1414
namespace ApiPlatform\Core\Bridge\Doctrine\Orm\Extension;
1515

1616
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
17+
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
1718
use Doctrine\ORM\QueryBuilder;
1819

1920
/**
2021
* Applies selected ordering while querying resource collection.
2122
*
2223
* @author Kévin Dunglas <[email protected]>
2324
* @author Samuel ROZE <[email protected]>
25+
* @author Vincent Chalamon <[email protected]>
2426
*/
2527
final class OrderExtension implements QueryCollectionExtensionInterface
2628
{
2729
private $order;
30+
private $resourceMetadataFactory;
2831

29-
public function __construct(string $order = null)
32+
public function __construct(string $order = null, ResourceMetadataFactoryInterface $resourceMetadataFactory = null)
3033
{
34+
$this->resourceMetadataFactory = $resourceMetadataFactory;
3135
$this->order = $order;
3236
}
3337

@@ -38,6 +42,14 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator
3842
{
3943
$classMetaData = $queryBuilder->getEntityManager()->getClassMetadata($resourceClass);
4044
$identifiers = $classMetaData->getIdentifier();
45+
if (null !== $this->resourceMetadataFactory) {
46+
$defaultOrder = $this->resourceMetadataFactory->create($resourceClass)->getAttribute('order');
47+
if (null !== $defaultOrder) {
48+
$queryBuilder->addOrderBy('o.'.$defaultOrder[0], $defaultOrder[1] ?? 'ASC');
49+
50+
return;
51+
}
52+
}
4153

4254
if (null !== $this->order) {
4355
foreach ($identifiers as $identifier) {

src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@
154154

155155
<service id="api_platform.doctrine.orm.query_extension.order" class="ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\OrderExtension" public="false">
156156
<argument>%api_platform.collection.order%</argument>
157+
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
157158

158159
<tag name="api_platform.doctrine.orm.query_extension.collection" priority="16" />
159160
</service>

tests/Bridge/Doctrine/Orm/Extension/OrderExtensionTest.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\OrderExtension;
1717
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGenerator;
18+
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
19+
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
1820
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
1921
use Doctrine\ORM\EntityManager;
2022
use Doctrine\ORM\Mapping\ClassMetadata;
@@ -62,4 +64,48 @@ public function testApplyToCollectionWithWrongOrder()
6264
$orderExtensionTest = new OrderExtension();
6365
$orderExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class);
6466
}
67+
68+
public function testApplyToCollectionWithOrderOverriden()
69+
{
70+
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
71+
$queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
72+
73+
$queryBuilderProphecy->addOrderBy('o.foo', 'DESC')->shouldBeCalled();
74+
75+
$classMetadataProphecy = $this->prophesize(ClassMetadata::class);
76+
$classMetadataProphecy->getIdentifier()->shouldBeCalled()->willReturn(['name']);
77+
78+
$resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn(new ResourceMetadata(null, null, null, null, null, ['order' => ['foo', 'DESC']]));
79+
80+
$emProphecy = $this->prophesize(EntityManager::class);
81+
$emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
82+
83+
$queryBuilderProphecy->getEntityManager()->shouldBeCalled()->willReturn($emProphecy->reveal());
84+
85+
$queryBuilder = $queryBuilderProphecy->reveal();
86+
$orderExtensionTest = new OrderExtension('asc', $resourceMetadataFactoryProphecy->reveal());
87+
$orderExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class);
88+
}
89+
90+
public function testApplyToCollectionWithOrderOverridenWithNoDirection()
91+
{
92+
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
93+
$queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
94+
95+
$queryBuilderProphecy->addOrderBy('o.foo', 'ASC')->shouldBeCalled();
96+
97+
$classMetadataProphecy = $this->prophesize(ClassMetadata::class);
98+
$classMetadataProphecy->getIdentifier()->shouldBeCalled()->willReturn(['name']);
99+
100+
$resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn(new ResourceMetadata(null, null, null, null, null, ['order' => ['foo']]));
101+
102+
$emProphecy = $this->prophesize(EntityManager::class);
103+
$emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
104+
105+
$queryBuilderProphecy->getEntityManager()->shouldBeCalled()->willReturn($emProphecy->reveal());
106+
107+
$queryBuilder = $queryBuilderProphecy->reveal();
108+
$orderExtensionTest = new OrderExtension('asc', $resourceMetadataFactoryProphecy->reveal());
109+
$orderExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class);
110+
}
65111
}

tests/Fixtures/TestBundle/Entity/Dummy.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
* "my_dummy.order",
3535
* "my_dummy.range",
3636
* "my_dummy.search",
37-
* },
37+
* }
3838
* })
3939
* @ORM\Entity
4040
*/
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity;
15+
16+
use ApiPlatform\Core\Annotation\ApiProperty;
17+
use ApiPlatform\Core\Annotation\ApiResource;
18+
use Doctrine\ORM\Mapping as ORM;
19+
use Symfony\Component\Validator\Constraints as Assert;
20+
21+
/**
22+
* Foo.
23+
*
24+
* @author Vincent Chalamon <[email protected]>
25+
*
26+
* @ApiResource(attributes={
27+
* "order"={"name", "DESC"}
28+
* })
29+
* @ORM\Entity
30+
*/
31+
class Foo
32+
{
33+
/**
34+
* @var int The id
35+
*
36+
* @ORM\Column(type="integer")
37+
* @ORM\Id
38+
* @ORM\GeneratedValue(strategy="AUTO")
39+
*/
40+
private $id;
41+
42+
/**
43+
* @var string The dummy name
44+
*
45+
* @ORM\Column
46+
* @Assert\NotBlank
47+
* @ApiProperty(iri="http://schema.org/name")
48+
*/
49+
private $name;
50+
51+
public function getId()
52+
{
53+
return $this->id;
54+
}
55+
56+
public function setName($name)
57+
{
58+
$this->name = $name;
59+
}
60+
61+
public function getName()
62+
{
63+
return $this->name;
64+
}
65+
}

0 commit comments

Comments
 (0)