Skip to content

Commit 8d26122

Browse files
committed
code challenge 3 solution
1 parent 9016443 commit 8d26122

File tree

24 files changed

+659
-13
lines changed

24 files changed

+659
-13
lines changed

CODING-CHALLENGE-3.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@
1111
- introduce query params `page` and `size` in your ListControllers
1212
- use the Doctrine Paginator to paginate the workshop and attendee lists
1313
- implement a `PaginatedCollection` object and add the properties `items`, `total` and `count`
14-
- implement a `PaginationFactory` to encapsulate your pagination logic
14+
- implement a `PaginatedCollectionFactory` to encapsulate your pagination logic

CODING-CHALLENGE-4.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# RESTful Webservices in Symfony
2+
3+
## Coding Challenge 4 - HATEOAS
4+
5+
### Tasks
6+
7+
- introduce HATEOAS links for your read and list representations of workshops and attendees
8+
- use the JSON-HAL format
9+
10+
### Solution
11+
12+
- add a `links` property to the `PaginatedCollection` class (annotate the getter with `#[SerializedName('_links')]`)
13+
- add `UrlGeneratorInterface` as dependency of `PaginationFactory`
14+
- introduce a `addlink(string $rel, string $href)` method in the `PaginatedCollection` class
15+
- add links to the created `PaginatedCollection` (self, next, prev, first, last)
16+
- adjust the AttendeeNormalizer and WorkshopNormalizer and add
17+
- `$data['_link']['self']['href']` (remember to check for is_array($data))
18+
- `$data['_link']['collection']['href']` (remember to check for is_array($data))

src/Controller/Attendee/ListController.php

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

55
namespace App\Controller\Attendee;
66

7-
use App\Repository\AttendeeRepository;
7+
use App\Pagination\AttendeeCollectionFactory;
8+
use Symfony\Component\HttpFoundation\Request;
89
use Symfony\Component\HttpFoundation\Response;
910
use Symfony\Component\Routing\Annotation\Route;
1011
use Symfony\Component\Serializer\SerializerInterface;
@@ -13,18 +14,21 @@
1314
final class ListController
1415
{
1516
public function __construct(
16-
private AttendeeRepository $attendeeRepository,
17+
private AttendeeCollectionFactory $attendeeCollectionFactory,
1718
private SerializerInterface $serializer,
1819
) {
1920
}
2021

21-
public function __invoke(): Response
22+
public function __invoke(Request $request): Response
2223
{
23-
$allAttendees = $this->attendeeRepository->findAll();
24+
$attendeeCollection = $this->attendeeCollectionFactory->create(
25+
$request->query->getInt('page', 1),
26+
$request->query->getInt('size', 10)
27+
);
2428

25-
$serializedAttendees = $this->serializer->serialize($allAttendees, 'json');
29+
$serializedAttendeeCollection = $this->serializer->serialize($attendeeCollection, 'json');
2630

27-
return new Response($serializedAttendees, Response::HTTP_OK, [
31+
return new Response($serializedAttendeeCollection, Response::HTTP_OK, [
2832
'Content-Type' => 'application/json',
2933
]);
3034
}

src/Controller/Workshop/ListController.php

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

55
namespace App\Controller\Workshop;
66

7-
use App\Repository\WorkshopRepository;
7+
use App\Pagination\WorkshopCollectionFactory;
8+
use Symfony\Component\HttpFoundation\Request;
89
use Symfony\Component\HttpFoundation\Response;
910
use Symfony\Component\Routing\Annotation\Route;
1011
use Symfony\Component\Serializer\SerializerInterface;
@@ -13,18 +14,21 @@
1314
final class ListController
1415
{
1516
public function __construct(
16-
private WorkshopRepository $workshopRepository,
17+
private WorkshopCollectionFactory $workshopCollectionFactory,
1718
private SerializerInterface $serializer,
1819
) {
1920
}
2021

21-
public function __invoke(): Response
22+
public function __invoke(Request $request): Response
2223
{
23-
$allWorkshops = $this->workshopRepository->findAll();
24+
$workshopCollection = $this->workshopCollectionFactory->create(
25+
$request->query->getInt('page', 1),
26+
$request->query->getInt('size', 10)
27+
);
2428

25-
$serializedWorkshops = $this->serializer->serialize($allWorkshops, 'json');
29+
$serializedWorkshopCollection = $this->serializer->serialize($workshopCollection, 'json');
2630

27-
return new Response($serializedWorkshops, Response::HTTP_OK, [
31+
return new Response($serializedWorkshopCollection, Response::HTTP_OK, [
2832
'Content-Type' => 'application/json',
2933
]);
3034
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Pagination;
6+
7+
use App\Repository\AttendeeRepository;
8+
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface;
9+
10+
final class AttendeeCollectionFactory extends PaginatedCollectionFactory
11+
{
12+
public function __construct(
13+
private AttendeeRepository $attendeeRepository
14+
) {
15+
}
16+
17+
public function getRepository(): ServiceEntityRepositoryInterface
18+
{
19+
return $this->attendeeRepository;
20+
}
21+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Pagination;
6+
7+
final class PaginatedCollection
8+
{
9+
private array $items;
10+
private int $total;
11+
private int $count;
12+
13+
public function __construct(\Iterator $items, int $total)
14+
{
15+
$this->items = iterator_to_array($items);
16+
$this->total = $total;
17+
$this->count = \count($this->items);
18+
}
19+
20+
public function getItems(): array
21+
{
22+
return $this->items;
23+
}
24+
25+
public function getTotal(): int
26+
{
27+
return $this->total;
28+
}
29+
30+
public function getCount(): int
31+
{
32+
return $this->count;
33+
}
34+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Pagination;
6+
7+
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface;
8+
use Doctrine\ORM\Tools\Pagination\Paginator;
9+
10+
abstract class PaginatedCollectionFactory
11+
{
12+
abstract public function getRepository(): ServiceEntityRepositoryInterface;
13+
14+
public function create(int $page, int $size): PaginatedCollection
15+
{
16+
$query = $this->getRepository()->createQueryBuilder('u')->getQuery();
17+
18+
$paginator = new Paginator($query);
19+
$total = count($paginator);
20+
21+
$paginator
22+
->getQuery()
23+
->setFirstResult($size * ($page - 1))
24+
->setMaxResults($size);
25+
26+
return new PaginatedCollection($paginator->getIterator(), $total);
27+
}
28+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Pagination;
6+
7+
use App\Repository\WorkshopRepository;
8+
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface;
9+
10+
final class WorkshopCollectionFactory extends PaginatedCollectionFactory
11+
{
12+
public function __construct(
13+
private WorkshopRepository $workshopRepository
14+
) {
15+
}
16+
17+
public function getRepository(): ServiceEntityRepositoryInterface
18+
{
19+
return $this->workshopRepository;
20+
}
21+
}

tests/Controller/Attendee/ListControllerTest.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,30 @@ public function test_it_should_list_all_attendees(): void
2020

2121
$this->assertMatchesJsonSnapshot($this->browser->getResponse()->getContent());
2222
}
23+
24+
/**
25+
* @dataProvider paginationQueryParameterValues
26+
*/
27+
public function test_it_should_paginate_attendees(int $page, int $size): void
28+
{
29+
$this->loadFixtures([
30+
__DIR__.'/fixtures/paginate_attendee.yaml',
31+
]);
32+
33+
$this->browser->request('GET', sprintf('/attendees?page=%d&size=%d', $page, $size));
34+
35+
static::assertResponseIsSuccessful();
36+
37+
$this->assertMatchesJsonSnapshot($this->browser->getResponse()->getContent());
38+
}
39+
40+
public function paginationQueryParameterValues(): \Generator
41+
{
42+
yield 'show 1st page 10 items each' => [1, 10];
43+
yield 'show 2nd page 10 items each' => [2, 10];
44+
yield 'show 3rd page 10 items each' => [3, 10];
45+
yield 'show 1st page 5 items each' => [1, 5];
46+
yield 'show 4th page 5 items each' => [4, 5];
47+
yield 'show 5th page 5 items each' => [5, 5];
48+
}
2349
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
{
2+
"items": [
3+
{
4+
"identifier": "0b13e52d-b058-32fb-8507-10dec634a07c",
5+
"firstname": "Tianna",
6+
"lastname": "Ziemann",
7+
"email": "rubye76@hotmail.com",
8+
"workshops": []
9+
},
10+
{
11+
"identifier": "3167f744-197b-3c9b-9419-71e5daf2ea18",
12+
"firstname": "Maeve",
13+
"lastname": "Hartmann",
14+
"email": "wkohler@johnson.com",
15+
"workshops": []
16+
},
17+
{
18+
"identifier": "42610729-5cd9-35f3-86bc-ec9158e1797e",
19+
"firstname": "Justus",
20+
"lastname": "Thiel",
21+
"email": "ikoelpin@hotmail.com",
22+
"workshops": []
23+
},
24+
{
25+
"identifier": "77c5eb68-1ea8-3561-8cc8-aa7296a10f27",
26+
"firstname": "Dianna",
27+
"lastname": "Dach",
28+
"email": "osinski.heath@mccullough.com",
29+
"workshops": []
30+
},
31+
{
32+
"identifier": "1ef2b096-6263-3cd5-87c8-510a91cd114b",
33+
"firstname": "Kathleen",
34+
"lastname": "Bogan",
35+
"email": "mohammed.carter@gmail.com",
36+
"workshops": []
37+
},
38+
{
39+
"identifier": "54a4eed2-9608-3fca-ac16-bdb51b5bc348",
40+
"firstname": "Esteban",
41+
"lastname": "Von",
42+
"email": "sgislason@kuhn.com",
43+
"workshops": []
44+
},
45+
{
46+
"identifier": "68140326-e25d-3a9a-be91-54e736e31d69",
47+
"firstname": "Lambert",
48+
"lastname": "Steuber",
49+
"email": "elinore23@hotmail.com",
50+
"workshops": []
51+
},
52+
{
53+
"identifier": "75e73073-fb97-3730-a317-68b740376cf6",
54+
"firstname": "Marta",
55+
"lastname": "Kulas",
56+
"email": "oran53@hotmail.com",
57+
"workshops": []
58+
},
59+
{
60+
"identifier": "279458a4-0701-3cb9-bbb5-76f77c85d61f",
61+
"firstname": "Cornelius",
62+
"lastname": "Mosciski",
63+
"email": "ethelyn92@yahoo.com",
64+
"workshops": []
65+
},
66+
{
67+
"identifier": "0a3944ce-2c0d-3904-9199-ae41a9ef9980",
68+
"firstname": "Wilburn",
69+
"lastname": "Rempel",
70+
"email": "ogutmann@adams.org",
71+
"workshops": []
72+
}
73+
],
74+
"total": 20,
75+
"count": 10
76+
}

0 commit comments

Comments
 (0)