Wide banner showcasing playful tech illustrations, including a quirky spider-like character representing API Platform, over a gentle blue and white background.

API Platform: How to Implement Custom Collection Data Providers with Filters?

03/03/2023

In most cases, when you are trying to filter data from your endpoint, default ApiPlatform filters are more than enough to cover all your needs. Sometimes though, additional actions are needed before your collection can be returned to the world.

How to Implement Custom Collection Data Providers with Filters?

This article explains how to implement a custom collection data provider with filters in API Platform version 2.7.4 and is a part of our series of articles on API Platform which you can find on our blog.

Imagine the following scenario: Let's say we have a collection named transactions. We already created endpoints to fetch data GET /api/transactions:

collectionOperations={
    "get"={
        "normalization_context"={"groups"={"transaction:read"}}
    },
}

We also have some filters; for example, employee exact.

>@ApiFilter(SearchFilter::class, properties={"employee"="exact"})

By default when you will call your endpoint with an additional param in the query string project=1, your list will be filtered by only these transactions that are related to a specific project - in this example id 1. The problem starts when you want to do something extra in the DataProvider in this collection. For example, let's filter collection in a data provider based on logged users. If a user has a specific role, then they see all transactions, if they’re a "normal" user then they can only see transactions related to his user.

public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable
{
    $queryBuilder = $this->managerRegistry->getRepository(Transaction::class)->createQueryBuilder('t');

    if (!$this->security->isGranted('ROLE_MANAGER')) {
        $queryBuilder
            ->andWhere('ewh.employee = :employee')
            ->setParameter('employee', $this->security->getUser())
        ;
    }

    return $queryBuilder->getQuery()->getResult();
}

However, what’s important to know is that this will remove default filters. Hence, you will not be able to filter by project anymore. What’s the solution?

To force DataProvider to use default filters. In order to do that, we need to inject extensions collection from the api platform.

public function __construct(
    private object $collectionExtensions,
    private ManagerRegistry $managerRegistry,
    private Security $security,
)
{}

Next, we need to inject a specific collection into our service.

App\DataProvider\TransactionsDataProvider:
    tags:
        - { name: 'api_platform.collection_data_provider', priority: 3 }
    autoconfigure: false
    arguments:
        $collectionExtensions: !tagged api_platform.doctrine.orm.query_extension.collection

From this moment forward, we will have all extensions available in our service. Some of them are filters, other ones could be paginations. Now, we can use them to filter our Query by additional criteria - by project in our example. To do that let's use the following code where we are applying each extension.

$queryNameGenerator = new QueryNameGenerator();

/** @var QueryCollectionExtensionInterface $extension */
foreach ($this->collectionExtensions as $extension) {
    $extension->applyToCollection($queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context);

    if ($extension instanceof QueryResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $operationName)) {
        return $extension->getResult($queryBuilder);
    }
}

Now, we can finally call our endpoint to check if everything works well. All class including all previous code snippets is available here:

<?php

declare(strict_types=1);

namespace App\DataProvider;

use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGenerator;
use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface;
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
use App\Entity\Transaction;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Security\Core\Security;

final class TransactionsDataProvider implements CollectionDataProviderInterface, RestrictedDataProviderInterface
{
    private ManagerRegistry $managerRegistry;

    private Security $security;

    private object $collectionExtensions;

    public function __construct(
        private object $collectionExtensions,
        private ManagerRegistry $managerRegistry,
        private Security $security,
    )
    {}

    public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
    {
        return EmployeeWorkingHour::class === $resourceClass;
    }

    public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable
    {
        $queryBuilder = $this->managerRegistry->getRepository(Transaction::class)->createQueryBuilder('t');

        if (!$this->security->isGranted('ROLE_MANAGER')) {
            $queryBuilder
                ->andWhere('t.employee = :employee')
                ->setParameter('employee', $this->security->getUser())
            ;
        }

        $queryNameGenerator = new QueryNameGenerator();

        /** @var QueryCollectionExtensionInterface $extension */
        foreach ($this->collectionExtensions as $extension) {
            $extension->applyToCollection($queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context);

            if ($extension instanceof QueryResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $operationName)) {
                return $extension->getResult($queryBuilder);
            }
        }

        return $queryBuilder->getQuery()->getResult();
    }
}

Hope this helps!

Ma
Wizerunek Marka Krokwy, CEO Primotly, pewnie pozującego ze skrzyżowanymi rękami. Marek ma na sobie ciemną koszulę z kołnierzykiem i okulary, co prezentuje elegancki i przystępny wygląd na czystym białym tle
Marek Krokwa
CEO

Najnowsze artykuły

Obraz koncepcyjny ukazujący kontrast między technologią AI a emisjami środowiskowymi, z symbolem mikroprocesora dla AI i chmurami symbolizującymi emisję CO2 nad budynkami przemysłowymi.

Innovations | 22/11/2024

Jak sztuczna inteligencja pomaga firmom śledzić emisje CO2?

Bernhard Huber

Rozliczanie emisji dwutlenku węgla, praktyka śledzenia, pomiaru i raportowania emisji gazów cieplarnianych (GHG) to tematy, które coraz częściej pojawiają się nie tylko w kontekście dbania o środowisko, ale też odpowiedzialności biznesu. Ponieważ 90% firm z listy Fortune 500 zobowiązało się do realizacji celów zrównoważonego rozwoju, zapotrzebowanie na skuteczne rozwiązania w zakresie rozliczania emisji dwutlenku węgla rośnie. Jednak firmy często zmagają się ze złożonymi łańcuchami dostaw i brakiem danych w czasie rzeczywistym, co sprawia, że dokładne rozliczanie emisji dwutlenku węgla jest trudnym zadaniem. Wygodne rozwiązanie podsuwa sztuczna inteligencja. Oferuje ona potężny zestaw narzędzi do automatyzacji i optymalizacji śledzenia emisji oraz identyfikowania nieefektywności.

Szczegóły
Grafika symbolizująca ład korporacyjny w kontekście ESG

Business | 15/11/2024

Czym jest ład korporacyjny w kontekście ESG?

Łukasz Kopaczewski

Podczas gdy wszystkie trzy filary - środowiskowy, społeczny i ład korporacyjny - są niezbędne, ład korporacyjny często odgrywa najbardziej fundamentalną rolę. Zarządzanie, które obejmuje etyczne przywództwo, przejrzystość i odpowiedzialność, zapewnia, że wysiłki ESG nie są tylko deklaracjami na papierze, ale są zintegrowane z codziennymi decyzjami firmy. Warto zauważyć, że niedawne badanie wykazało, że 39% firm uważa, że osiąga odpowiednie wyniki w zakresie zarządzania, co wskazuje na znaczne możliwości poprawy.

Szczegóły
Ilustracja do artykułu o wykorzystaniu sztucznej inteligencji (AI) w biznesowych procesach decyzyjnych w przedsiębiorstwie

Business | 08/11/2024

Wykorzystanie sztucznej inteligencji (AI) w biznesowych procesach decyzyjnych w przedsiębiorstwie

Agata Pater

Poleganie wyłącznie na intuicji w biznesie może często oznaczać utratę dużych możliwości. Najlepszym przykładem jest Netflix. Analizując ponad 30 milionów dziennych „odtworzeń” oraz niezliczone oceny i wyszukiwania subskrybentów, Netflix dokładnie określił, czego chcą widzowie, co doprowadziło do stworzenia hitowych seriali, takich jak House of Cards. To podejście nie tylko zwiększyło zaangażowanie widzów; zrewolucjonizowało podejście branży rozrywkowej do tworzenia treści.

Szczegóły