Własny filtr w Api Platform

własny filtr Api Platform

W poprzednim artykule opisałem jak działają standardowe filtry dostarczane z Api Platform. Jednak, często zdarza się, że to co domyślnie jest dostarczane pozostaje niewystarczające. Dlatego Api platform daje nam możliwość tworzenia własnych filtrów – zgodnie z naszymi potrzebami.

Implementacja interfejsu filtrów

Pierwszym, a zarazem ładniejszym sposobem jest implementacja interfejsu dostarczonego przez twórców Api Platform: ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractFilter.

Interfejs ten posiada dwie metody, których ciało musimy zaimplementować:

1. filterProperty – odpowiada za działanie filtra, a dokładnie za modyfikację queryBuildera w sposób, który odpowiada naszym oczekiwaniom. 

2. getDescription – ta metoda zwraca dokumentacje dla naszego filtra. Zwraca tablice, która następnie jest serializowana do standardu OpenApi. Dzięki temu nasz filtr będzie widoczny w swager.

Przykładowa implementacja

W poniższym przykładzie założyłem posiadanie dwóch zasobów. Film i Użytkownik. Użytkownik posiada relacje ManyToMany do filmów. Oznacza ona filmy, w których grał użytkownik. Film posiada właściwość `type` która określa typ filmu. Kod jest dostępny na moim githubie

Diagram encji filtry api platform
Diagram encji

Stwórzmy filtr, który dla użytkowników będzie znajdować tylko tych, którzy występowali w filmach odpowiedniego typu.

Zanim przejdziemy do implementacji interfejsu wspomnę o abstrakcyjnej klasie \ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractFilter,  która jest udostępniana przez ApiPlatform. Implementuje ona nasz interfejs, a dodatkowo ma wstrzyknięte serwisy, które mogą okazać się użyteczne przy tworzeniu filtrów. 

Implementacja metody `getDescription`

W zasadzie moglibyśmy zwracać w tej metodzie pustą tablicę. Filtr by i tak zadziałał. Jednak, jeżeli chcemy mieć porządek w dokumentacji naszego api, zalecam dodać implementacje.

 W naszym przypadku wygląda to tak:

<?php
public function getDescription(string $resourceClass): array
{
   $description = [];
   $description["typesOfFilms"] = [
       'property' => null,
       'required' => false,
       'type' => 'string',
       'is_collection' => false,
   ];
   return $description;
}

Klucz zwracanej tablicy oznacza nazwę filtra.

Parametr tablicy`property` określa, dla której właściwości zasobu odnosi się filtr. Może się tak zdarzyć, że skonfigurujemy filtr w taki sposób, aby działał dla konkretnej właściwości. W naszym przypadku filtr będzie działał w kontekście zasobu. Dlatego ustawiona jest wartość null.

Kolejno, pole`require`- mówi nam czy filtr jest wymagany. Dla niektórych zapytań możemy wymagać podania wartości w filtrach. U nas zasób będzie działał zarówno z zaaplikowanym filtrem jak i bez. 

Następnie, właściwość ‘type’ określa typ wartości, które możemy wpisać w filtrze. Typy są zgodne ze standardem OpenApi ‘string’, ‘bool’, ‘array’ itp. W sytuacji, gdy podamy niepoprawną wartość lub w ogóle jej nie podamy, domyślnie zostanie wybrany typ ‘string’.

Parametr `is_collection` określa czy dany filtr może być aplikowany wielokrotnie. Zobrazuję to przykładem:

Jeżeli tę wartość ustawilibyśmy na true to moglibyśmy wysyłać takie żądania: 

“https://example.com/api/users?typesOfFilms[]=action&typesOfFilms[]=drama”

Dla przypomnienia dodam, że filtry są dodawana za pomocą operatora logicznego AND, dlatego przykład powyżej powinien zwrócić wszystkich aktorów, którzy grali w dramatach i filmach akcji.

Implementacja filtru. Metoda “filterProperty”

<?php
protected function filterProperty(
    string $property,
    $value,
    QueryBuilder $queryBuilder,
    QueryNameGeneratorInterface $queryNameGenerator, 
    string $resourceClass, 
    string $operationName = null
) {
   $rootAlias = $queryBuilder->getRootAliases()[0];
   $joinAlias = $queryNameGenerator->generateJoinAlias('movies');

   $queryBuilder
       ->leftJoin($rootAlias.'.movies', $joinAlias)
       ->andWhere($joinAlias . ".type = :type")
       ->setParameter('type', $value);
}

W parametrach tej metody dostajemy kolejno:

  • $property zasobu, jeżeli ustaliliśmy filtr dla konkretnej property
  • $value wartość przekazywaną przez filtr
  • $queryBuilder z częściowo utworzonym zapytaniem do bazy danych
  • $queryNameGenerator obiekt, który pomoże nam stworzyć potrzebne aliasy dla naszego filtra
  • $resourceClass to klasa/zasób, do której zapięty jest filtr
  • opcjonalnie $operationName czyli metoda żądania z protokołu HTTP

Ciało metody jest bardzo proste. Po pierwsze – odczytuję główny alias naszego zasobu. Następnie, tworzę alias dla relacji z `movie`. 

W tym momencie mamy już komplet danych, dzięki którym możemy zmodyfikować zapytanie w queryBuilderze. Należy pamiętać, aby dodać ‘join’ gdyż Api Platform może domyślnie nie dodawać relacji do każdego zasobu. 

Rejestracja filtra

Ostatnią rzeczą, którą należy zrobić, aby nasz filtr był widoczny w systemie jest jego rejestracja. W tym celu klasę należy zarejestrować jako filtr z tagiem `api_platform.filter`.

Jeżeli mamy włączoną autokonfigurację wystarczy sama rejestracja serwisu. Symfony po interfejsie sam rozpozna jaki tag musi nadać.

U mnie wygląda to tak, że rejestruje wszystkie klasy znajdujące się w namespace App/Filter jako serwisy.

# config/services.yaml
App\Filter\:
   resource: '../src/Filter'

Podsumowanie

Dzięki temu, że możemy modyfikować bezpośrednio zapytanie do bazy danych mechanizm filtrów jest bardzo elastyczny. Implementacja dostarczonego interfejsu sprawia, że kod jest czysty i spójny. 

Jeżeli jednak i to by nam nie wystarczyło Api Platform w swojej dokumentacji opisuje metodę tworzenia filtrów z wykorzystaniem doctrine bezpośrednio. Ale o tym w innym artykule.
Pokazałem tutaj tylko jeden ze sposobów na stworzenie własnego filtra. Po więcej informacje odsyłam do dokumentacji

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *