Skip to content

Having one and only one Collection Data provider for each resource is a must? #550

@mappedinn

Description

@mappedinn

Hi,

I do need custom operation in my API which returns an entity for given slug. Consequently, the total number of operations for the resource Product are 6 (5 are default operations + 1 custom operation).

  api_products_get_collection             GET        ANY      ANY    /api/products.{_format}                
  api_products_post_collection            POST       ANY      ANY    /api/products.{_format}                
  api_products_get_item                   GET        ANY      ANY    /api/products/{id}.{_format}           
  api_products_put_item                   PUT        ANY      ANY    /api/products/{id}.{_format}           
  api_products_delete_item                DELETE     ANY      ANY    /api/products/{id}.{_format}           
  api_products_slug                       GET        ANY      ANY    /api/products/by-slug/{slug} 

I followed the official documentation to define the custom operation. But surprisingly, I noticed that the route api_products_get_collection is using the collection data provider of the route api_products_slug.

I thought at first it is problem of routes order. But, it was not the case since as it is shown in the command below:

$ bin/console debug:router | grep products 
  app_product_products                    ANY        ANY      ANY    /                                      
  api_products_get_collection             GET        ANY      ANY    /api/products.{_format}                
  api_products_post_collection            POST       ANY      ANY    /api/products.{_format}                
  api_products_get_item                   GET        ANY      ANY    /api/products/{id}.{_format}           
  api_products_put_item                   PUT        ANY      ANY    /api/products/{id}.{_format}           
  api_products_delete_item                DELETE     ANY      ANY    /api/products/{id}.{_format}           
  api_products_slug                       GET        ANY      ANY    /api/products/by-slug/{slug}

In addition, here is how I declared the routes:

api_platform:
    resource: .
    type: api_platform
    prefix: /api

# the routes is declared after the routes of the api_platfom to make sure having to the custom operation route after api_platfom routes
actions:
    resource: ../../src/Action/
    type: annotation

So, for sure, it is not a problem related to the order of routes.

The question is: should I have one and only one Collection Data Provider for each resource?


Below is how I declared the custom operation.

(1) Declaration of a provider implementing CollectionDataProviderInterface

<?php

namespace App\Provider;

use App\Entity\Product;
use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface;
use ApiPlatform\Core\Exception\ResourceClassNotSupportedException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

use App\Repository\ProductRepository;
use Symfony\Component\HttpFoundation\RequestStack;
use Monolog\Logger;

final class ProductCollectionDataProvider implements CollectionDataProviderInterface
{
    /**
     * @var ProductRepository
     */
    private $repository;

    /**
     * @var RequestStack
     */
    private $requestStack;

    /**
     * @var Logger
     */
    private $logger;

    /**
     * ProductCollectionDataProviderconstructor.
     *
     * @param ProductRepository $repository
     * @param RequestStack $requestStack
     * @param Logger $logger
     */
    public function __construct(ProductRepository $repository, RequestStack $requestStack, Logger $logger)
    {
        $this->repository = $repository;
        $this->requestStack = $requestStack;
        $this->logger = $logger;
    }

    /**
     * Retrieves a collection.
     *
     * @param string      $resourceClass
     * @param string|null $operationName
     *
     * @throws ResourceClassNotSupportedException
     *
     * @return array|\Traversable
     */
    public function getCollection(string $resourceClass, string $operationName = null){

        $this->logger->info('getCollection() is called');
        $slug =  $this->requestStack->getCurrentRequest()->attributes->get('slug');
        $this->logger->info($slug);

        if (Product::class !== $resourceClass) {
            throw new ResourceClassNotSupportedException();
        }
        // if($operationName !== 'product_slug') return null;

        $product = $this->repository->findProductBySlug($this->requestStack->getCurrentRequest()->attributes->get('slug'));
        if($product===null){
            throw new NotFoundHttpException(sprintf('The Product resource \'%s\' was not found.',$slug));

        }
        return $product;

    }

}

(2) Definition of an action

<?php

namespace App\Action;

use App\Entity\Product;
use App\Provider\ProductCollectionDataProvider;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\Routing\Annotation\Route;

class ProductSlug
{
    private $productCollectionDataProvider;

    public function __construct(ProductCollectionDataProvider $productCollectionDataProvider)
    {
        $this->productCollectionDataProvider = $productCollectionDataProvider;
    }

    /**
     * @Route(
     *     name="api_products_slug",
     *     path="/api/products/by-slug/{slug}",
     *     defaults={"_api_resource_class"=Product::class, "_api_collection_operation_name"="product_slug"}
     * )
     * @Method("GET")
     */
    public function __invoke()
    {
        return  $this->productCollectionDataProvider->getCollection(Product::class);
    }
}

(3) declaration of the custom action as an service

services:
    # ...
    App\Action\:
        resource: '../src/Action'
        tags: ['controller.service_arguments']

(4) declaration of the route of the custom action

api_platform:
    resource: .
    type: api_platform
    prefix: /api

# the routes is declared after the routes of the api_platfom to make sure having to the custom operation route after api_platfom routes
actions:
    resource: ../../src/Action/
    type: annotation

environment

$ composer info | grep api
api-platform/api-pack             v1.0.1             A pack for API Platform
api-platform/core                 v2.1.5             The ultimate solution to create web APIs.
$ composer info | grep symfony/framework
symfony/framework-bundle          v4.0.4             Symfony FrameworkBundle

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions