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 e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *