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
相关产品推荐
相关产品推荐

