Skip to content

Commit

Permalink
filter and sort on data's worldcities.csv
Browse files Browse the repository at this point in the history
  • Loading branch information
aratinau committed Jul 2, 2021
1 parent f40232e commit 1d34f49
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 5 deletions.
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,33 @@
# Api Platform Pagination
# Api Platform Sort, Filter and Pagination on raw data

Inspired from https://github.com/api-platform/demo

This is an example using a custom `CityFilter` filtering on raw data from the file `Repository/City/data/worldcities.csv`

CityFilter pass the `sort` and `order` params to the context and this context it's processed in the `CityCollectionDataProvider`

## Install

composer install

## Launch

symfony serve --no-tls

## Usage

`/api/cities?search[key]=tokyo&order[key]=desc&page=1`

### Keys available

- id
- city
- city_ascii
- lat
- lng
- country
- iso2
- iso3
- admin_name
- capital
- population
5 changes: 3 additions & 2 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ services:
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones

App\Repository\City\CityCachedDataRepository:
decorates: App\Repository\City\CityDataRepository
#commented to use filter
#App\Repository\City\CityCachedDataRepository:
#decorates: App\Repository\City\CityDataRepository

App\Repository\City\CityDataInterface: '@App\Repository\City\CityDataRepository'
17 changes: 16 additions & 1 deletion src/DataProvider/CityCollectionDataProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
use App\DataProvider\Extension\CityCollectionExtensionInterface;
use App\Entity\City;
use App\Filter\CityFilter;
use App\Repository\City\CityDataInterface;
use App\Repository\City\CityDataRepository;

final class CityCollectionDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface
{
Expand Down Expand Up @@ -40,8 +42,21 @@ public function supports(string $resourceClass, string $operationName = null, ar
*/
public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable
{
$cityFilterSearch = $context[CityFilter::CITY_FILTER_SEARCH_CONTEXT] ?? null;
$cityFilterOrder = $context[CityFilter::CITY_FILTER_ORDER_CONTEXT] ?? null;

try {
$collection = $this->repository->getCities();
/** @var CityDataRepository $repository */
$repository = $this->repository;

if ($cityFilterSearch) {
$repository->setFilter($cityFilterSearch);
}
if ($cityFilterOrder) {
$repository->setOrder($cityFilterOrder);
}

$collection = $repository->getCities();
} catch (\Exception $e) {
throw new \RuntimeException(sprintf('Unable to retrieve cities from external source: %s', $e->getMessage()));
}
Expand Down
3 changes: 3 additions & 0 deletions src/Entity/City.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@

use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiFilter;
use App\Filter\CityFilter;

/**
* @ApiResource(
* collectionOperations={
* "get"
* }
* )
* @ApiFilter(CityFilter::class, arguments={"throwOnInvalid"=false})
*/
class City
{
Expand Down
51 changes: 51 additions & 0 deletions src/Filter/CityFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace App\Filter;

use ApiPlatform\Core\Serializer\Filter\FilterInterface;
use Symfony\Component\HttpFoundation\Request;

class CityFilter implements FilterInterface
{
public const CITY_FILTER_SEARCH_CONTEXT = 'city_search_filter';
public const CITY_FILTER_ORDER_CONTEXT = 'city_order_filter';
private $throwOnInvalid;

public function __construct(bool $throwOnInvalid = false)
{
$this->throwOnInvalid = $throwOnInvalid;
}

public function apply(Request $request, bool $normalization, array $attributes, array &$context)
{
$search = $request->query->get('search');
$order = $request->query->get('order');

if (!$search && $this->throwOnInvalid) {
return;
}

if ($search) {
$context[self::CITY_FILTER_SEARCH_CONTEXT] = $search;
}

if ($order) {
$context[self::CITY_FILTER_ORDER_CONTEXT] = $order;
}
}

public function getDescription(string $resourceClass): array
{
return [
'search' => [
'property' => null,
'type' => 'string',
'required' => false,
'openapi' => [
'description' => 'Search and Order by key from array',
],
]
];
}

}
106 changes: 106 additions & 0 deletions src/Repository/City/CityDataRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@

use App\Entity\City;

/**
* Class CityDataRepository
* @package App\Repository\City
*/
final class CityDataRepository implements CityDataInterface
{
private const DATA_SOURCE = 'worldcities.csv';
private const FIELDS_COUNT = 11;
private $filter;
private $order;

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

if ($this->filter) {
foreach ($this->filter as $key => $filter) {
switch ($key) {
case "city":
$rowId = 0;
break;
case "city_ascii":
$rowId = 1;
break;
case "lat":
$rowId = 2;
break;
case "lng":
$rowId = 3;
break;
case "country":
$rowId = 4;
break;
case "iso2":
$rowId = 5;
break;
case "iso3":
$rowId = 6;
break;
case "admin_name":
$rowId = 7;
break;
case "capital":
$rowId = 8;
break;
case "population":
$rowId = 9;
break;
}
}
$city = strtolower($row[$rowId]);
if (strpos($city, $filter) === false) {
continue;
}
}

$city = new City(
$cpt - 1,
$this->sanitize($row[0] ?? ''),
Expand All @@ -54,6 +101,23 @@ public function getFromCsv(): array
$cities[$cpt - 1] = $city;
}

if ($this->order) {
foreach ($this->order as $key => $order)
{
usort($cities, function ($a, $b) use($key, $order) {
$getMethod = 'get' . ucfirst($key);
if ($a->{$getMethod}() === $b->{$getMethod}()) {
return 0;
}
if ($order === 'desc') {
return ($a->{$getMethod}() < $b->{$getMethod}());
} else {
return ($a->{$getMethod}() > $b->{$getMethod}());
}
});
}
}

return $cities ?? [];
}

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

/**
* @param string|null $str
* @return string
*/
private function sanitize(?string $str): string
{
return trim(utf8_encode((string) $str));
}

/**
* @return string|null
*/
public function getFilter(): ?string
{
return $this->filter;
}

/**
* @param array $filter
* @return $this
*/
public function setFilter(array $filter): CityDataRepository
{
$this->filter = $filter;

return $this;
}

/**
* @return string|null
*/
public function getOrder(): ?string
{
return $this->order;
}

/**
* @param array $order
* @return $this
*/
public function setOrder(array $order): CityDataRepository
{
$this->order = $order;

return $this;
}
}
3 changes: 2 additions & 1 deletion src/Repository/City/data/worldcities.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"Tokyo","Tokyo","35.6897","139.6922","Japan","JP","JPN","Tōkyō","primary","37977000","1392685764"
"city","city_ascii","lat","lng","country","iso2","iso3","admin_name","capital","population","id"
"Tokyo","Tokyo","35.6897","139.6922","Japan","JP","JPN","Tōkyō","primary","37977000","1392685764"
"Jakarta","Jakarta","-6.2146","106.8451","Indonesia","ID","IDN","Jakarta","primary","34540000","1360771077"
"Delhi","Delhi","28.6600","77.2300","India","IN","IND","Delhi","admin","29617000","1356872604"
"Mumbai","Mumbai","18.9667","72.8333","India","IN","IND","Mahārāshtra","admin","23355000","1356226629"
Expand Down

0 comments on commit 1d34f49

Please sign in to comment.