Fitry w Api Platform

Filtr Api Platform

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.  

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *