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

Api-Platform无标识Patch请求实现单字段更新问题

解决无标识符ApiResource Patch请求的部分字段更新问题

问题场景

我需要创建不带标识符的ApiResource Patch请求,实现独立更新UserProfileApi的firstName或lastName字段。当前代码配置如下:

#[ApiResource(
    uriTemplate: '/user/profile.{_format}',
//  uriTemplate: '/user/profile/{id}.{_format}',
    operations: [
        new Patch(
            provider: CustomItemStateProvider::class,
            processor: CustomEntityStateProcessor::class,
        ),
    ],
    stateOptions: new Options(entityClass: User::class),
)]
class UserProfileApi implements ApiResourceInterface
{
    #[ApiProperty(readable: false, writable: false, identifier: true)]
    public ?int $id = null;
    public ?string $firstName = null;
    public ?string $lastName = null;
}

Patch请求体:

{"firstName":"John"}

当uriTemplate包含{id}时,CustomItemStateProvider会被调用,能获取到Doctrine中的User实体,CustomEntityStateProcessor拿到的UserProfileApi已更新firstName,lastName保留数据库值,一切正常。但移除{id}后,CustomItemStateProvider不会被调用,CustomEntityStateProcessor拿到的UserProfileApi仅包含"firstName":"John",lastName为null,且处理器会尝试创建新用户。

需要实现:将UserProfileApi的请求数据与实体数据传递给CustomEntityStateProcessor,仅更新指定字段,其余实体数据保持不变。


解决方案

1. 强制触发Provider加载现有实体

在Patch操作配置中添加fetchPartial: false,强制系统调用Provider加载现有资源,即使URL中没有标识符:

new Patch(
    provider: CustomItemStateProvider::class,
    processor: CustomEntityStateProcessor::class,
    // 强制触发Provider获取现有实体
    fetchPartial: false,
),

2. 修改CustomItemStateProvider获取当前用户实体

因为URL无标识符,需要从请求上下文获取当前登录用户,以此加载对应的User实体:

class CustomItemStateProvider implements ItemStateProviderInterface
{
    public function __construct(
        private TokenStorageInterface $tokenStorage,
        private EntityManagerInterface $em
    ) {}

    public function provide(StateProviderInterface $provider, string $uriTemplate, array $uriVariables, array $context): object
    {
        // 获取当前登录用户
        $user = $this->tokenStorage->getToken()?->getUser();
        if (!$user instanceof User) {
            throw new AccessDeniedException();
        }

        // 返回数据库中已存在的User实体
        return $this->em->getRepository(User::class)->find($user->getId());
    }
}

3. 调整CustomEntityStateProcessor实现部分字段更新

确保处理器将请求数据合并到现有实体,而非创建新实体:

class CustomEntityStateProcessor implements StateProcessorInterface
{
    public function __construct(private EntityManagerInterface $em) {}

    public function process(mixed $data, Operation $operation, array $context): void
    {
        // 从上下文获取Provider加载的现有实体
        $existingUser = $context['data'] ?? null;
        if (!$existingUser instanceof User) {
            throw new InvalidArgumentException('无法找到目标用户实体');
        }

        // 仅更新请求中存在的非空字段
        if ($data->firstName !== null) {
            $existingUser->setFirstName($data->firstName);
        }
        if ($data->lastName !== null) {
            $existingUser->setLastName($data->lastName);
        }

        $this->em->persist($existingUser);
        $this->em->flush();
    }
}

4. 优化UserProfileApi配置

移除不必要的标识符标记,添加序列化组控制字段处理:

#[ApiResource(
    uriTemplate: '/user/profile.{_format}',
    operations: [
        new Patch(
            provider: CustomItemStateProvider::class,
            processor: CustomEntityStateProcessor::class,
            fetchPartial: false,
        ),
    ],
    stateOptions: new Options(entityClass: User::class),
    normalizationContext: ['groups' => ['user:profile']],
    denormalizationContext: ['groups' => ['user:profile:write']],
)]
class UserProfileApi implements ApiResourceInterface
{
    public ?int $id = null;

    #[ApiProperty(groups: ['user:profile', 'user:profile:write'])]
    public ?string $firstName = null;

    #[ApiProperty(groups: ['user:profile', 'user:profile:write'])]
    public ?string $lastName = null;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 08:24:59