How to apply an ApiFilter(SearchFilter: class) to a dynamic getter of an entity? [doctrine-orm, api-platform, graphql]

Viewed 174

I'm new to php, doctrine and symfony. Please feel free to give me suggestions for improvement. Sry if my English isn't accurate.

What do I want to achieve?

I want to:

  • filter a collection (Protocols) by a dynamic getter (getTimeTrackerState) of my (Protocol) Entity model.
  • see the filter in the graphql playground within the collection protocols.
  • be able to execute a query with a filter value.

My initial situation

The Entity Code: Protocol.php

use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\DateFilter;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ApiResource()
 * @Vich\Uploadable()
 * @ORM\Entity(repositoryClass=ProtocolRepository::class)
 * 
 * @ApiFilter(DateFilter::class, properties={"dueDate"})
 * @ApiFilter(SearchFilter::class, properties={"timeTrackerState": "exact", "state": "exact"})
 *
 */
class Protocol
{
    /**
     * @var string
     *
     * @ORM\Id()
     * @ORM\GeneratedValue(strategy="UUID")
     * @ORM\Column(type="guid")
     *
     * @Assert\Uuid()
     */
    private $id;
    
    // some other fields
    
    // /**
    //  * @var string
    //  * 
    //  * @ORM\Column(type="string")
    //  */
    // public $timeTrackerState;
    
    public function getTimeTrackerState(): string
    {
        if ($this->getDaysUntilExeeded() <= 0) {
            return Protocol::TIME_TRACKER_EXEEDED;
        } elseif ($this->getDaysUntilUpcoming() <= 0) {
            return Protocol::TIME_TRACKER_UPCOMING;
        } else {
            return Protocol::TIME_TRACKER_NOT_UPCOMING;
        }
    }
    
    // not setter!
}

graphql query:

query GetProtocols {
  protocols(timeTrackerState: "UPCOMING") {
    pageInfo {
      endCursor
      hasNextPage
    }
    totalCount
    edges {
      cursor
      node {
        ...ProtocolCore
        state
        timeTrackerState
        
        ... other stuff
        }
      }
    }
  }

loaded graphql schema docs (playground):

endpoint with timeTrackerState ApiFilter

What is the problem?

The filter is not available in graphql (see loaded graphql schema docs), as long as there is no field declared, which is annotated with (@ORM\Column(type="string")). If I declare this field named timeTrackerState , then this field appears in the loaded graphql schema docs, but I also need to store it in the database, what isn't what I want. Queries with a applied filter value (i.e. timeTrackerState: "UPCOMING") will verify against the database and not against the dynamic getter getTimeTrackerState() of my entity class. I think this behaviour is intended of doctrine.

Maybe I'm missing some basic functionality, or I'm completely off with my plan and should reconsider the structure.

I also thought about filtering it in the frontend, but then I need to fetch many unnecessary entries (protocol) with states I'm not interested in.

What I tried so far

  • As described above, I used a @ORM\Column(type="string") field to write the field into the database and query it from there. The problem here is that I don't know how to keep it up to date.

  • I wrote a custom-resolver which nearly does what I want, BUT then I end up having two queries for the same resource and they only differ in their arguments, which I want to have together. I don't know how to merge my custom Resolver with the already provided Resolver used by protocols.

    • ProtocolCollecctionResolver.php:

      use ApiPlatform\Core\GraphQl\Resolver\QueryCollectionResolverInterface;
      use App\Entity\Protocol;
      
      final class ProtocolCollectionResolver implements QueryCollectionResolverInterface
      {
          /**
           * @param iterable<Protocol> $collection
           *
           * @return iterable<Protocol>
           */
          public function __invoke(iterable $collection, array $context): iterable
          {
              ...
              return $collection    
          }
      
    • usage of Resolver in Entity Protocol.php:

      /**
      * @ApiResource(
      *     graphql={
      *         "item_query",
      *         "collection_query",
      *         "delete",
      *         "update",
      *         "create",
      * 
      *         "collectionQuery"={
      *             "collection_query"=ProtocolCollectionResolver::class,
      *             "args"={
      *                 "timeTrackerState"={
      *                     "type"="String!",
      *                     "description"="The time state of the protocol"
      *                 }
      *             }
      *         },
      *     }
      * )
      * 
      * @Vich\Uploadable()
      * @ORM\Entity(repositoryClass=ProtocolRepository::class)
      * 
      * @ApiFilter(DateFilter::class, properties={"dueDate"})
      * @ApiFilter(SearchFilter::class, properties={"timeTrackerState": "exact", "state": "exact"})
      */
      class Protocol
      {}
      
    • loaded graphql schema docs (playground):

      endpoint of extra resolver

Thank you in advice!

0 Answers
Related