Dzięki frameworkowi Api Platform możemy w szybki sposób stworzyć API REST’owe. Wystarczy klasa encji, kilka adnotacji i REST’owy CRUD gotowy. To jednak nie wszystko co powinno oferować porządne API. W tym artykule przedstawię filtry, które są niezwykle pomocne przy żądaniach typu GET.
Na wstępie wyjaśnię, że pisząc o filtrach mam na myśli wartości, które są wysyłane w żądaniach typu GET. Dodaje się je do adresu url po znaku ‘?’ (pytajnika).
Przykład zapytania z użyciem filtrów wyglądałby następująco:
https://127.0.100.1:8000/api/movies?createdAt[after]=1999-01-01
Na początku o standardowych filtrach
W następnym artykule opiszę jak utworzyć niestandardowe filtry. Zanim to jednak nastąpi – przedstawię jak działają te już zaimplementowane w Api Platform.
Filtry w api platform są domyślnie wyłączone. W momencie gdy je włączymy, automatycznie zostają dodane do dokumentacji OpenApi w sekcji `hydra:search`. Tym samym pojawiają się w widoku swaggera
Dodawanie standardowego filtra
Aby filtr został włączony należy go zarejestrować jako usługę w rozumieniu Symfony. W tym celu w pliku `config/services.yaml` stwórzmy konfigurację:
services: movie.date_filter: parent: 'api_platform.doctrine.orm.date_filter' arguments: [ { createdAt: ~ } ] tags: [ 'api_platform.filter' ] # opcjonalnie autowire: false autoconfigure: false public: false
Możesz również umieścić tę konfigurację w każdym pliku konfiguracyjnym, w którym definiujesz usługi Symfony. Oczywiście może być to również plik typy xml.
Usługa rozszerza klasę bazową filtrów z Api Platform. Dodając atrybut parent w definicję serwisu używasz tzw. parent services z komponentu Dependecy Injection Symfony. W tym parametrze dodajemy rodzaj filtra, którego chcemy użyć.
Filtr musi być również zarejestrowany z odpowiednim tagiem. W tym przypadku jest to api_platform.filter.
Jako pierwszy argument serwisu zostaje podana właściwość klasy, której będzie dotyczyć filtr. W naszym przykładzie jest to `createdAt`. Nazwa musi zostać podana jako klucz tablicy. Wartością tablicy będą właściwości samego filtra dla własności. O tym poniżej.
Ostatnie dwie opcje (autowire, autoconfigure) są dodawane opcjonalnie. Domyślnie w Symfony są one ustawione na wartość `true` co tutaj jest niewskazane, gdyż sami chcemy ustawić atrybuty usługi. Z tego powodu ich wartość została zmieniona.
Następnie należy zdefiniować klasę, do której odnosi się filtr. Tę konfigurację wpisujemy już w plikach zasobów Api Platform. Może to być plik “yaml” ; “xml” bądź w postaci adnotacji bezpośrednio w klasie. Poniżej przedstawiam przykład w postaci adnotacji w klasie.
<?php use ApiPlatform\Core\Annotation\ApiResource; /** * @ApiResource(attributes={"filters"={"movie.date_filter"}}) */ class Movie { /** * @ORM\Column(type="datetime") * @var DateTime */ private $createdAt; ... }
Anotacja ApiFilter
Jeżeli szukamy prostszego sposobu na dodanie filtrów do naszej klasy możemy posłużyć się adnotacją `ApiPlatform\Core\Annotation\ApiFilter`. Adnotacja sama zarejestruje filtr z odpowiednimi właściwościami oraz doda go do zasobu.
<?php use ApiPlatform\Core\Annotation\ApiFilter; use ApiPlatform\Core\Annotation\ApiResource; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter; /** * @ORM\Entity() * @ApiResource() * @ApiFilter(SearchFilter::class, properties={"firstName": "ipartial", "lastName": "ipartial"}) */ class User { /** * @ORM\Column() * @var string */ private $firstName; /** * @ORM\Column() * @var string */ private $lastName; ... }
W przykładzie powyżej dodałem filtr typu`SearchFilter`, który działa na właściwościach `firstName` oraz `lastName` szukając fragmentu tekstu niezależnie od wielkości liter.
Dzięki takiej konfiguracji możemy żądać zasobu `https://127.0.100.1:8000/api/users?firstName=ran`
Odpowiedzią serwera będzie tekst json w postaci:
{ "@context": "/api/contexts/User", "@id": "/api/users", "@type": "hydra:Collection", "hydra:member": [ { "@id": "/api/users/2", "@type": "User", "id": 2, "firstName": "Frank", "lastName": "Darabont", "movies": [] } ], "hydra:totalItems": 1, "hydra:view": { "@id": "/api/users?firstName=ran", "@type": "hydra:PartialCollectionView" }, "hydra:search": { "@type": "hydra:IriTemplate", "hydra:template": "/api/users{?firstName,lastName}", "hydra:variableRepresentation": "BasicRepresentation", "hydra:mapping": [ { "@type": "IriTemplateMapping", "variable": "firstName", "property": "firstName", "required": false }, { "@type": "IriTemplateMapping", "variable": "lastName", "property": "lastName", "required": false } ] } }
Warto zwrócić uwagę na sekcję hydra:search w odpowiedzi. Tutaj dostajemy informacje z jakich filtrów możemy skorzystać przy następnym zapytaniu. jest to przydatne przy tworzeniu klienta frontowego.
Standardowe filtry
Search Filter
To najczęściej używany filtr. Został już przedstawiony w przykładzie powyżej. Zasada jego działania polega na wyszukiwaniu tekstu w właściwościach typu string. Dopasowanie może odbywać się za pomocą pięciu metod:
- exact – szuka dokładnego dopasowania, cała szukana fraza musi być w wartości,
- partial – szuka tekstu, który składa się z szukanej frazy,
- start – szuka tekstu, które zaczynają się od szukanej frazy,
- end – szuka tekstu, który kończy się na szukanej frazie,
- word_start – szuka w tekście słów, które zaczynają się od frazy.
Dodatkowo można dodać literę `i` w sposób `iexact`, `istart` itd. co sprawi że szukanie będzie ignorować wielkość liter.
Date Filter
Ten typ filtru pozwala nam na filtrowanie atrybutów typu `DateTimeInterface`. Jeżeli dodasz go do zasobu w swagger pojawią się 4 nowe pola do filtrowania. Będą to after|before|strictly_after|strictly_before . Wartości jakie przyjmuje filtr muszą być zgodne z tym na co pozwala konstruktor klasy DateTime. Czyli na przykład format daty typu Y-M-D
https://127.0.100.1:8000/api/movies?createdAt[after]=1998-01-01
Powyższe żądanie zwraca wszystkie filmy, które powstały dokładnie lub po 1 stycznia 1998.
after – szuka dat występujących po szukanej dacie,
before – szuka dat przez szukaną datą.
Analogicznie wersja z “strictly_” DODAJ AFTER/ BEFOR – wyklucza daty, które są dokładnie dopasowane.
Do filtra możemy też dodać opcję, która wyklucza wartości null. Lub też wyklucza ją w pewnych typach. Wartości te są zdefiniowane za pomocą stałych w interfejsie: ApiPlatform\Core\Bridge\Doctrine\Common\Filter\DateFilterInterface
<?php use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\DateFilterInterface; /** * @ORM\Entity() * @ApiResource() * @ApiFilter( * DateFilter::class, * properties={"createdAt":DateFilterInterface::INCLUDE_NULL_BEFORE_AND_AFTER } * ) */ class Movie { /** * @ORM\Column(type="datetime") * @var DateTime */ private $createdAt; … }
Order Filter
Za pomocą filtrów można również sortować wyniki. W tym celu należy użyć filtru ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter
Dodatkowymi opcjami, które możesz zastosować w tym filtrze są:
- domyślna kolejność sortowania `default_direction`. Przyjmuje wartości typu `ASC` lub `DESC`
- strategia w sortowaniu wartości typu null `nulls_comparison`. Przyjmuje wartości zdefiniowane w ApiPlatform\Core\Bridge\Doctrine\Common\Filter\OrderFilterInterface
Inne filtry
Warto także wspomnieć o innych filtrach, które są dostępne, w tym:
Boolean Filter, Numeric Filter, Range Filter, Exists Filter
Jeżeli chcesz dowiedzieć się jak ich używać odsyłam do dokumentacji. Możesz również zostawić komentarz. W odpowiedzi postaram się rozwiązać Twój problem z filtrem.
Łączenie filtrów
W zapytaniach możemy połączyć kilka filtrów naraz.
https://127.0.100.1:8000/api/movies?createdAt[before]=1994-01-01&title=leo
Filtry zostaną zaaplikowany według strategii `AND` w kolejności, w której zostały podane.
Dla przykładu powyżej będzie to: “Znajdź filmy które powstały przed rokiem 1994 i których tytuł zawiera frazę leo”
Podsumowania
Mam nadzieje że udało mi się wyjaśnić ideę działania filtrów w Api Platform. Standardowe rozwiązania pozwolą nam zaspokoić większość naszych potrzeb. Jednakże, z doświadczenia wiem że to nie będzie wystarczające. W kolejnym artykule dowiecie się jak napisać własną klasę filtrów.