Skip to content

Commit 1d34f49

Browse files
author
Aymeric Ratinaud
committed
filter and sort on data's worldcities.csv
1 parent f40232e commit 1d34f49

File tree

7 files changed

+213
-5
lines changed

7 files changed

+213
-5
lines changed

README.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,33 @@
1-
# Api Platform Pagination
1+
# Api Platform Sort, Filter and Pagination on raw data
22

3+
Inspired from https://github.com/api-platform/demo
4+
5+
This is an example using a custom `CityFilter` filtering on raw data from the file `Repository/City/data/worldcities.csv`
6+
7+
CityFilter pass the `sort` and `order` params to the context and this context it's processed in the `CityCollectionDataProvider`
8+
9+
## Install
10+
11+
composer install
12+
13+
## Launch
14+
15+
symfony serve --no-tls
16+
17+
## Usage
18+
19+
`/api/cities?search[key]=tokyo&order[key]=desc&page=1`
20+
21+
### Keys available
22+
23+
- id
24+
- city
25+
- city_ascii
26+
- lat
27+
- lng
28+
- country
29+
- iso2
30+
- iso3
31+
- admin_name
32+
- capital
33+
- population

config/services.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ services:
2424
# add more service definitions when explicit configuration is needed
2525
# please note that last definitions always *replace* previous ones
2626

27-
App\Repository\City\CityCachedDataRepository:
28-
decorates: App\Repository\City\CityDataRepository
27+
#commented to use filter
28+
#App\Repository\City\CityCachedDataRepository:
29+
#decorates: App\Repository\City\CityDataRepository
2930

3031
App\Repository\City\CityDataInterface: '@App\Repository\City\CityDataRepository'

src/DataProvider/CityCollectionDataProvider.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
99
use App\DataProvider\Extension\CityCollectionExtensionInterface;
1010
use App\Entity\City;
11+
use App\Filter\CityFilter;
1112
use App\Repository\City\CityDataInterface;
13+
use App\Repository\City\CityDataRepository;
1214

1315
final class CityCollectionDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface
1416
{
@@ -40,8 +42,21 @@ public function supports(string $resourceClass, string $operationName = null, ar
4042
*/
4143
public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable
4244
{
45+
$cityFilterSearch = $context[CityFilter::CITY_FILTER_SEARCH_CONTEXT] ?? null;
46+
$cityFilterOrder = $context[CityFilter::CITY_FILTER_ORDER_CONTEXT] ?? null;
47+
4348
try {
44-
$collection = $this->repository->getCities();
49+
/** @var CityDataRepository $repository */
50+
$repository = $this->repository;
51+
52+
if ($cityFilterSearch) {
53+
$repository->setFilter($cityFilterSearch);
54+
}
55+
if ($cityFilterOrder) {
56+
$repository->setOrder($cityFilterOrder);
57+
}
58+
59+
$collection = $repository->getCities();
4560
} catch (\Exception $e) {
4661
throw new \RuntimeException(sprintf('Unable to retrieve cities from external source: %s', $e->getMessage()));
4762
}

src/Entity/City.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66

77
use ApiPlatform\Core\Annotation\ApiProperty;
88
use ApiPlatform\Core\Annotation\ApiResource;
9+
use ApiPlatform\Core\Annotation\ApiFilter;
10+
use App\Filter\CityFilter;
911

1012
/**
1113
* @ApiResource(
1214
* collectionOperations={
1315
* "get"
1416
* }
1517
* )
18+
* @ApiFilter(CityFilter::class, arguments={"throwOnInvalid"=false})
1619
*/
1720
class City
1821
{

src/Filter/CityFilter.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace App\Filter;
4+
5+
use ApiPlatform\Core\Serializer\Filter\FilterInterface;
6+
use Symfony\Component\HttpFoundation\Request;
7+
8+
class CityFilter implements FilterInterface
9+
{
10+
public const CITY_FILTER_SEARCH_CONTEXT = 'city_search_filter';
11+
public const CITY_FILTER_ORDER_CONTEXT = 'city_order_filter';
12+
private $throwOnInvalid;
13+
14+
public function __construct(bool $throwOnInvalid = false)
15+
{
16+
$this->throwOnInvalid = $throwOnInvalid;
17+
}
18+
19+
public function apply(Request $request, bool $normalization, array $attributes, array &$context)
20+
{
21+
$search = $request->query->get('search');
22+
$order = $request->query->get('order');
23+
24+
if (!$search && $this->throwOnInvalid) {
25+
return;
26+
}
27+
28+
if ($search) {
29+
$context[self::CITY_FILTER_SEARCH_CONTEXT] = $search;
30+
}
31+
32+
if ($order) {
33+
$context[self::CITY_FILTER_ORDER_CONTEXT] = $order;
34+
}
35+
}
36+
37+
public function getDescription(string $resourceClass): array
38+
{
39+
return [
40+
'search' => [
41+
'property' => null,
42+
'type' => 'string',
43+
'required' => false,
44+
'openapi' => [
45+
'description' => 'Search and Order by key from array',
46+
],
47+
]
48+
];
49+
}
50+
51+
}

src/Repository/City/CityDataRepository.php

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@
66

77
use App\Entity\City;
88

9+
/**
10+
* Class CityDataRepository
11+
* @package App\Repository\City
12+
*/
913
final class CityDataRepository implements CityDataInterface
1014
{
1115
private const DATA_SOURCE = 'worldcities.csv';
1216
private const FIELDS_COUNT = 11;
17+
private $filter;
18+
private $order;
1319

1420
/**
1521
* @return array<int, City>
@@ -37,6 +43,47 @@ public function getFromCsv(): array
3743
throw new \RuntimeException(sprintf('Invalid data at row: %d', count($row)));
3844
}
3945

46+
if ($this->filter) {
47+
foreach ($this->filter as $key => $filter) {
48+
switch ($key) {
49+
case "city":
50+
$rowId = 0;
51+
break;
52+
case "city_ascii":
53+
$rowId = 1;
54+
break;
55+
case "lat":
56+
$rowId = 2;
57+
break;
58+
case "lng":
59+
$rowId = 3;
60+
break;
61+
case "country":
62+
$rowId = 4;
63+
break;
64+
case "iso2":
65+
$rowId = 5;
66+
break;
67+
case "iso3":
68+
$rowId = 6;
69+
break;
70+
case "admin_name":
71+
$rowId = 7;
72+
break;
73+
case "capital":
74+
$rowId = 8;
75+
break;
76+
case "population":
77+
$rowId = 9;
78+
break;
79+
}
80+
}
81+
$city = strtolower($row[$rowId]);
82+
if (strpos($city, $filter) === false) {
83+
continue;
84+
}
85+
}
86+
4087
$city = new City(
4188
$cpt - 1,
4289
$this->sanitize($row[0] ?? ''),
@@ -54,6 +101,23 @@ public function getFromCsv(): array
54101
$cities[$cpt - 1] = $city;
55102
}
56103

104+
if ($this->order) {
105+
foreach ($this->order as $key => $order)
106+
{
107+
usort($cities, function ($a, $b) use($key, $order) {
108+
$getMethod = 'get' . ucfirst($key);
109+
if ($a->{$getMethod}() === $b->{$getMethod}()) {
110+
return 0;
111+
}
112+
if ($order === 'desc') {
113+
return ($a->{$getMethod}() < $b->{$getMethod}());
114+
} else {
115+
return ($a->{$getMethod}() > $b->{$getMethod}());
116+
}
117+
});
118+
}
119+
}
120+
57121
return $cities ?? [];
58122
}
59123

@@ -74,8 +138,50 @@ private function getFileAsArray(): array
74138
return $file;
75139
}
76140

141+
/**
142+
* @param string|null $str
143+
* @return string
144+
*/
77145
private function sanitize(?string $str): string
78146
{
79147
return trim(utf8_encode((string) $str));
80148
}
149+
150+
/**
151+
* @return string|null
152+
*/
153+
public function getFilter(): ?string
154+
{
155+
return $this->filter;
156+
}
157+
158+
/**
159+
* @param array $filter
160+
* @return $this
161+
*/
162+
public function setFilter(array $filter): CityDataRepository
163+
{
164+
$this->filter = $filter;
165+
166+
return $this;
167+
}
168+
169+
/**
170+
* @return string|null
171+
*/
172+
public function getOrder(): ?string
173+
{
174+
return $this->order;
175+
}
176+
177+
/**
178+
* @param array $order
179+
* @return $this
180+
*/
181+
public function setOrder(array $order): CityDataRepository
182+
{
183+
$this->order = $order;
184+
185+
return $this;
186+
}
81187
}

src/Repository/City/data/worldcities.csv

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
"Tokyo","Tokyo","35.6897","139.6922","Japan","JP","JPN","Tōkyō","primary","37977000","1392685764"
1+
"city","city_ascii","lat","lng","country","iso2","iso3","admin_name","capital","population","id"
2+
"Tokyo","Tokyo","35.6897","139.6922","Japan","JP","JPN","Tōkyō","primary","37977000","1392685764"
23
"Jakarta","Jakarta","-6.2146","106.8451","Indonesia","ID","IDN","Jakarta","primary","34540000","1360771077"
34
"Delhi","Delhi","28.6600","77.2300","India","IN","IND","Delhi","admin","29617000","1356872604"
45
"Mumbai","Mumbai","18.9667","72.8333","India","IN","IND","Mahārāshtra","admin","23355000","1356226629"

0 commit comments

Comments
 (0)