You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在API Platform中实现带过滤的自定义统计获取端点

解决Symfony/API Platform自定义用户统计端点的过滤问题

我来帮你搞定这个自定义统计端点的过滤需求,结合API Platform的特性,有两个比较靠谱的方案,我给你一步步拆解:

方案一:用自定义控制器(最直接易上手)

既然是单个用户的统计接口,自定义控制器可以直接拿到请求参数,逻辑实现起来更灵活。

1. 先定义统计结果的DTO(你的Statistic实体)

这个DTO不需要ORM映射,只用来定义返回的结构,记得加上API Platform的序列化配置:

// src/Dto/UserStatistics.php
namespace App\Dto;

use ApiPlatform\Metadata\ApiProperty;

class UserStatistics
{
    /**
     * @ApiProperty(openapiContext={"type"="string"})
     */
    public string $sport;

    /**
     * @ApiProperty(openapiContext={"type"="string", "nullable": true})
     */
    public ?string $position;

    /**
     * @ApiProperty(openapiContext={"type"="integer"})
     */
    public int $totalMatches;

    /**
     * @ApiProperty(openapiContext={"type"="integer"})
     */
    public int $winCount;

    /**
     * @ApiProperty(openapiContext={"type"="integer"})
     */
    public int $drawCount;

    /**
     * @ApiProperty(openapiContext={"type"="integer"})
     */
    public int $lossCount;
}

2. 在User实体上配置自定义操作

给User添加一个statistics自定义操作,指定控制器并声明可接收的过滤参数:

// src/Entity/User.php
use ApiPlatform\Metadata\ApiOperation;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\Get;
use App\Controller\UserStatisticsController;

// ...
class User implements UserInterface
{
    // ...

    #[Get(
        uriTemplate: '/users/{id}/statistics',
        controller: UserStatisticsController::class,
        openapiContext: [
            'parameters' => [
                [
                    'name' => 'sport[]',
                    'in' => 'query',
                    'required' => false,
                    'schema' => ['type' => 'array', 'items' => ['type' => 'string']],
                    'style' => 'form'
                ],
                [
                    'name' => 'outcome',
                    'in' => 'query',
                    'required' => false,
                    'schema' => ['type' => 'string', 'enum' => ['win', 'draw', 'loss']]
                ]
            ]
        ]
    )]
    #[ApiProperty(readable: false)] // 避免这个属性被序列化到User的响应里
    public function getStatistics(): array
    {
        // 这个方法只是占位,实际逻辑在控制器里
        return [];
    }
}

3. 编写自定义控制器逻辑

控制器里直接注入Request和EntityManager,拿到用户ID、过滤参数,然后用Doctrine QueryBuilder做统计查询:

// src/Controller/UserStatisticsController.php
namespace App\Controller;

use App\Entity\User;
use App\Dto\UserStatistics;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

class UserStatisticsController
{
    public function __invoke(User $user, Request $request, EntityManagerInterface $em): array
    {
        // 可选:权限控制,比如只允许用户查看自己的统计
        // $currentUser = $this->getUser();
        // if ($currentUser !== $user) {
        //     throw new AccessDeniedException();
        // }

        // 获取过滤参数
        $sports = $request->query->all('sport');
        $outcomeFilter = $request->query->get('outcome');

        // 构建统计查询(这里假设Match的outcome字段:1=胜,2=平,3=负,你要改成自己的常量值)
        $qb = $em->createQueryBuilder()
            ->select([
                's.name as sport',
                'p.name as position',
                'COUNT(mp.id) as totalMatches',
                'SUM(CASE WHEN m.outcome = 1 THEN 1 ELSE 0 END) as winCount',
                'SUM(CASE WHEN m.outcome = 2 THEN 1 ELSE 0 END) as drawCount',
                'SUM(CASE WHEN m.outcome = 3 THEN 1 ELSE 0 END) as lossCount'
            ])
            ->from('App\Entity\MatchPlayer', 'mp')
            ->join('mp.user', 'u')
            ->join('mp.playedMatch', 'm')
            ->join('m.sport', 's')
            ->leftJoin('mp.position', 'p')
            ->where('u.id = :userId')
            ->setParameter('userId', $user->getId())
            ->groupBy('s.id, p.id');

        // 应用sport过滤
        if (!empty($sports)) {
            $qb->andWhere('s.name IN (:sports)')
                ->setParameter('sports', $sports);
        }

        // 应用outcome过滤
        if ($outcomeFilter) {
            $outcomeMap = [
                'win' => 1,
                'draw' => 2,
                'loss' => 3
            ];
            if (isset($outcomeMap[$outcomeFilter])) {
                $qb->andWhere('m.outcome = :outcome')
                    ->setParameter('outcome', $outcomeMap[$outcomeFilter]);
            }
        }

        $results = $qb->getQuery()->getResult();

        // 映射查询结果到DTO
        $statistics = [];
        foreach ($results as $row) {
            $stat = new UserStatistics();
            $stat->sport = $row['sport'];
            $stat->position = $row['position'];
            $stat->totalMatches = (int)$row['totalMatches'];
            $stat->winCount = (int)$row['winCount'];
            $stat->drawCount = (int)$row['drawCount'];
            $stat->lossCount = (int)$row['lossCount'];
            $statistics[] = $stat;
        }

        return $statistics;
    }
}

方案二:自定义数据提供者(更贴合API Platform规范)

如果你想复用API Platform的过滤系统,可以自定义数据提供者,从请求上下文里获取过滤参数:

1. 实现自定义数据提供者

// src/DataProvider/UserStatisticsDataProvider.php
namespace App\DataProvider;

use ApiPlatform\Core\DataProvider\ContextAwareDataProviderInterface;
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
use App\Entity\User;
use App\Dto\UserStatistics;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RequestStack;

class UserStatisticsDataProvider implements ContextAwareDataProviderInterface, RestrictedDataProviderInterface, ItemDataProviderInterface
{
    public function __construct(
        private EntityManagerInterface $em,
        private RequestStack $requestStack
    ) {}

    public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
    {
        // 只支持User的statistics操作
        return $resourceClass === User::class && $operationName === 'get_statistics';
    }

    public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): array
    {
        $user = $this->em->getRepository(User::class)->find($id);
        if (!$user) {
            return [];
        }

        $request = $this->requestStack->getCurrentRequest();
        $sports = $request->query->all('sport');
        $outcomeFilter = $request->query->get('outcome');

        // 这里的查询逻辑和控制器里的完全一致,省略重复代码
        $qb = $this->em->createQueryBuilder()
            // 同上的select、from、join、where、groupBy
            // 应用过滤条件
            // ...

        $results = $qb->getQuery()->getResult();

        // 映射到DTO,同上逻辑
        $statistics = [];
        foreach ($results as $row) {
            $stat = new UserStatistics();
            // 赋值属性...
            $statistics[] = $stat;
        }

        return $statistics;
    }

    public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable
    {
        // 不需要实现,因为是单个用户的统计
        throw new \LogicException('Not implemented');
    }
}

2. 在User的自定义操作里指定数据提供者

修改User实体的Get注解,去掉controller,加上provider:

#[Get(
    uriTemplate: '/users/{id}/statistics',
    provider: UserStatisticsDataProvider::class,
    openapiContext: [
        // 同上的parameters配置
    ]
)]

关键注意点

  • 参数映射:确保你的outcome参数和Match实体里的$outcome字段值对应(比如示例中的1=胜,你要改成自己项目里的常量)
  • 权限控制:一定要加上权限校验,避免用户查看其他用户的统计数据
  • 缓存优化:统计数据可以用Symfony Cache组件缓存,减少数据库查询压力
  • 序列化配置:给DTO添加正确的序列化组,确保返回的JSON结构符合预期

内容的提问来源于stack exchange,提问作者tobias ingold

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 12:27:52