Skip to content

Commit 55cda8a

Browse files
committed
Merge branch '2.1'
2 parents a8f3d97 + 1e0104c commit 55cda8a

File tree

7 files changed

+149
-54
lines changed

7 files changed

+149
-54
lines changed

features/hydra/collection.feature

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,3 +395,45 @@ Feature: Collections support
395395
"additionalProperties": false
396396
}
397397
"""
398+
399+
@dropSchema
400+
@createSchema
401+
Scenario: Allow passing "0" to `itemsPerPage`
402+
When I send a "GET" request to "/dummies?itemsPerPage=0"
403+
Then the response status code should be 200
404+
And the response should be in JSON
405+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
406+
And the JSON should be valid according to this schema:
407+
"""
408+
{
409+
"type": "object",
410+
"properties": {
411+
"@context": {"pattern": "^/contexts/Dummy$"},
412+
"@id": {"pattern": "^/dummies$"},
413+
"@type": {"pattern": "^hydra:Collection$"},
414+
"hydra:totalItems": {"type":"number", "maximum": 30},
415+
"hydra:member": {
416+
"type": "array",
417+
"minItems": 0,
418+
"maxItems": 0
419+
},
420+
"hydra:view": {
421+
"type": "object",
422+
"properties": {
423+
"@id": {"pattern": "^/dummies\\?itemsPerPage=0$"},
424+
"@type": {"pattern": "^hydra:PartialCollectionView$"},
425+
"hydra:first": {"pattern": "^/dummies\\?itemsPerPage=0&page=1$"},
426+
"hydra:last": {"pattern": "^/dummies\\?itemsPerPage=0&page=1$"},
427+
"hydra:previous": {"pattern": "^/dummies\\?itemsPerPage=0&page=1$"},
428+
"hydra:next": {"pattern": "^/dummies\\?itemsPerPage=0&page=1$"}
429+
}
430+
},
431+
"hydra:search": {}
432+
},
433+
"additionalProperties": false
434+
}
435+
"""
436+
437+
When I send a "GET" request to "/dummies?itemsPerPage=0&page=2"
438+
Then the response status code should be 400
439+
And the JSON node "hydra:description" should be equal to "Page should not be greater than 1 if itemsPegPage is equal to 0"

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,17 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator
9393
$itemsPerPage = (null !== $this->maximumItemPerPage && $itemsPerPage >= $this->maximumItemPerPage ? $this->maximumItemPerPage : $itemsPerPage);
9494
}
9595

96-
if (0 >= $itemsPerPage) {
97-
throw new InvalidArgumentException('Item per page parameter should not be less than or equal to 0');
96+
if (0 > $itemsPerPage) {
97+
throw new InvalidArgumentException('Item per page parameter should not be less than 0');
9898
}
9999

100-
$firstResult = ($this->getPaginationParameter($request, $this->pageParameterName, 1) - 1) * $itemsPerPage;
100+
$page = $this->getPaginationParameter($request, $this->pageParameterName, 1);
101+
102+
if (0 === $itemsPerPage && 1 < $page) {
103+
throw new InvalidArgumentException('Page should not be greater than 1 if itemsPegPage is equal to 0');
104+
}
105+
106+
$firstResult = ($page - 1) * $itemsPerPage;
101107
if ($request->attributes->get('_graphql')) {
102108
$collectionArgs = $request->attributes->get('_graphql_collections_args', []);
103109
if (isset($collectionArgs[$resourceClass]['after'])) {

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

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,43 @@ public function testApplyToCollection()
6767
$extension->applyToCollection($queryBuilder, new QueryNameGenerator(), 'Foo', 'op');
6868
}
6969

70+
public function testApplyToCollectionWithItemPerPageZero()
71+
{
72+
$requestStack = new RequestStack();
73+
$requestStack->push(new Request(['pagination' => true, 'itemsPerPage' => 0, '_page' => 1]));
74+
75+
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
76+
$attributes = [
77+
'pagination_enabled' => true,
78+
'pagination_client_enabled' => true,
79+
'pagination_items_per_page' => 0,
80+
];
81+
$resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata(null, null, null, [], [], $attributes))->shouldBeCalled();
82+
$resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal();
83+
84+
$queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
85+
$queryBuilderProphecy->setFirstResult(0)->willReturn($queryBuilderProphecy)->shouldBeCalled();
86+
$queryBuilderProphecy->setMaxResults(0)->shouldBeCalled();
87+
$queryBuilder = $queryBuilderProphecy->reveal();
88+
89+
$extension = new PaginationExtension(
90+
$this->prophesize(ManagerRegistry::class)->reveal(),
91+
$requestStack,
92+
$resourceMetadataFactory,
93+
true,
94+
false,
95+
false,
96+
0,
97+
'_page'
98+
);
99+
$extension->applyToCollection($queryBuilder, new QueryNameGenerator(), 'Foo', 'op');
100+
}
101+
70102
/**
71103
* @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
72-
* @expectedExceptionMessage Item per page parameter should not be less than or equal to 0
104+
* @expectedExceptionMessage Page should not be greater than 1 if itemsPegPage is equal to 0
73105
*/
74-
public function testApplyToCollectionWithItemPerPageZero()
106+
public function testApplyToCollectionWithItemPerPageZeroAndPage2()
75107
{
76108
$requestStack = new RequestStack();
77109
$requestStack->push(new Request(['pagination' => true, 'itemsPerPage' => 0, '_page' => 2]));
@@ -86,8 +118,8 @@ public function testApplyToCollectionWithItemPerPageZero()
86118
$resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal();
87119

88120
$queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
89-
$queryBuilderProphecy->setFirstResult(40)->willReturn($queryBuilderProphecy)->shouldNotBeCalled();
90-
$queryBuilderProphecy->setMaxResults(40)->shouldNotBeCalled();
121+
$queryBuilderProphecy->setFirstResult(0)->willReturn($queryBuilderProphecy)->shouldNotBeCalled();
122+
$queryBuilderProphecy->setMaxResults(0)->shouldNotBeCalled();
91123
$queryBuilder = $queryBuilderProphecy->reveal();
92124

93125
$extension = new PaginationExtension(
@@ -105,7 +137,7 @@ public function testApplyToCollectionWithItemPerPageZero()
105137

106138
/**
107139
* @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
108-
* @expectedExceptionMessage Item per page parameter should not be less than or equal to 0
140+
* @expectedExceptionMessage Item per page parameter should not be less than 0
109141
*/
110142
public function testApplyToCollectionWithItemPerPageLessThen0()
111143
{
@@ -116,7 +148,7 @@ public function testApplyToCollectionWithItemPerPageLessThen0()
116148
$attributes = [
117149
'pagination_enabled' => true,
118150
'pagination_client_enabled' => true,
119-
'pagination_items_per_page' => 0,
151+
'pagination_items_per_page' => -20,
120152
];
121153
$resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata(null, null, null, [], [], $attributes))->shouldBeCalled();
122154
$resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal();

tests/Fixtures/app/AppKernel.php

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,22 @@
1212
declare(strict_types=1);
1313

1414
use ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle;
15+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\User;
1516
use ApiPlatform\Core\Tests\Fixtures\TestBundle\TestBundle;
1617
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
1718
use FOS\UserBundle\FOSUserBundle;
1819
use Nelmio\ApiDocBundle\NelmioApiDocBundle;
1920
use Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle;
2021
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
22+
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
2123
use Symfony\Bundle\SecurityBundle\SecurityBundle;
2224
use Symfony\Bundle\TwigBundle\TwigBundle;
2325
use Symfony\Component\Config\Loader\LoaderInterface;
26+
use Symfony\Component\DependencyInjection\ContainerBuilder;
2427
use Symfony\Component\HttpKernel\Kernel;
28+
use Symfony\Component\Routing\RouteCollectionBuilder;
29+
use Symfony\Component\Security\Core\User\UserInterface;
30+
use Symfony\Component\Security\Http\Firewall\ContextListener;
2531

2632
/**
2733
* AppKernel for tests.
@@ -30,7 +36,9 @@
3036
*/
3137
class AppKernel extends Kernel
3238
{
33-
public function registerBundles()
39+
use MicroKernelTrait;
40+
41+
public function registerBundles(): array
3442
{
3543
return [
3644
new FrameworkBundle(),
@@ -45,7 +53,12 @@ public function registerBundles()
4553
];
4654
}
4755

48-
public function registerContainerConfiguration(LoaderInterface $loader)
56+
protected function configureRoutes(RouteCollectionBuilder $routes)
57+
{
58+
$routes->import('config/routing.yml');
59+
}
60+
61+
protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader)
4962
{
5063
$environment = $this->getEnvironment();
5164

@@ -55,5 +68,49 @@ public function registerContainerConfiguration(LoaderInterface $loader)
5568
}
5669

5770
$loader->load("{$this->getRootDir()}/config/config_{$environment}.yml");
71+
72+
$securityConfig = [
73+
'encoders' => [
74+
User::class => 'bcrypt',
75+
// Don't use plaintext in production!
76+
UserInterface::class => 'plaintext',
77+
],
78+
'providers' => [
79+
'chain_provider' => [
80+
'chain' => [
81+
'providers' => ['in_memory', 'fos_userbundle'],
82+
],
83+
],
84+
'in_memory' => [
85+
'memory' => [
86+
'users' => [
87+
'dunglas' => ['password' => 'kevin', 'roles' => 'ROLE_USER'],
88+
'admin' => ['password' => 'kitten', 'roles' => 'ROLE_ADMIN'],
89+
],
90+
],
91+
],
92+
'fos_userbundle' => ['id' => 'fos_user.user_provider.username_email'],
93+
],
94+
'firewalls' => [
95+
'dev' => [
96+
'pattern' => '^/(_(profiler|wdt|error)|css|images|js)/',
97+
'security' => false,
98+
],
99+
'default' => [
100+
'provider' => 'chain_provider',
101+
'http_basic' => null,
102+
'anonymous' => null,
103+
],
104+
],
105+
'access_control' => [
106+
['path' => '^/', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
107+
],
108+
];
109+
110+
if (method_exists(ContextListener::class, 'setLogoutOnUserChange')) {
111+
$securityConfig['firewalls']['default']['logout_on_user_change'] = true;
112+
}
113+
114+
$c->loadFromExtension('security', $securityConfig);
58115
}
59116
}

tests/Fixtures/app/config/config_test.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
1-
imports:
2-
- { resource: security.yml }
3-
41
framework:
52
secret: 'dunglas.fr'
6-
router:
7-
resource: '%kernel.root_dir%/config/routing.yml'
8-
strict_requirements: '%kernel.debug%'
93
validation:
104
enable_annotations: true
115
serializer:

tests/Fixtures/app/config/routing.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ relation_embedded.custom_get:
1313
_controller: 'TestBundle:Custom:custom'
1414

1515
controller:
16-
resource: "@TestBundle/Controller"
16+
resource: '@TestBundle/Controller'
1717
type: annotation

tests/Fixtures/app/config/security.yml

Lines changed: 0 additions & 36 deletions
This file was deleted.

0 commit comments

Comments
 (0)