我确实需要在我的 API 中进行自定义操作,以返回给定 slug 的实体。因此,资源的操作总数为Product
6(5 个是默认操作 + 1 个自定义操作)。
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}
我按照官方文档来定义自定义操作。但令人惊讶的是,我注意到该路由api_products_get_collection
正在使用该路由的集合数据提供程序api_products_slug
。
起初我认为这是路线顺序的问题。但是,情况并非如此,因为如下命令所示:
$ 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}
此外,这是我声明路线的方式:
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
因此,可以肯定的是,这不是与路线顺序有关的问题。
问题是:每个资源是否应该只有一个集合数据提供者?
以下是我声明自定义操作的方式。
(1) 实现 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) 动作的定义
<?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) 将自定义动作声明为服务
services:
# ...
App\Action\:
resource: '../src/Action'
tags: ['controller.service_arguments']
(4) 自定义动作的路由声明
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
环境
$ 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