crud->create($request, News::class, ['news:write'], ['news:read']); */ final class CrudResponder { public function __construct( private EntityManagerInterface $em, private SerializerInterface $serializer, private ValidatorInterface $validator, ) { } /** * @param list $readGroups */ public function read(object $entity, array $readGroups): JsonResponse { return $this->json($entity, Response::HTTP_OK, $readGroups); } /** * @template T of object * * @param class-string $entityClass * @param list $writeGroups * @param list $readGroups */ public function create( Request $request, string $entityClass, array $writeGroups, array $readGroups, ): JsonResponse { $payload = $this->decodePayload($request); if ($payload === null) { return $this->jsonError('Ожидается JSON-объект в теле запроса', Response::HTTP_BAD_REQUEST); } unset($payload['id']); try { /** @var T $entity */ $entity = $this->serializer->deserialize( $this->encodePayload($payload), $entityClass, 'json', [ AbstractNormalizer::GROUPS => $writeGroups, ], ); } catch (JsonException|SerializerExceptionInterface $e) { return $this->jsonError('Ошибка десериализации: ' . $e->getMessage(), Response::HTTP_BAD_REQUEST); } if (($validationResponse = $this->validate($entity)) !== null) { return $validationResponse; } $this->em->persist($entity); $this->em->flush(); return $this->json($entity, Response::HTTP_CREATED, $readGroups); } /** * @param list $writeGroups * @param list $readGroups */ public function update( Request $request, object $entity, array $writeGroups, array $readGroups, ): JsonResponse { $payload = $this->decodePayload($request); if ($payload === null) { return $this->jsonError('Ожидается JSON-объект в теле запроса', Response::HTTP_BAD_REQUEST); } unset($payload['id']); try { $this->serializer->deserialize( $this->encodePayload($payload), $entity::class, 'json', [ AbstractNormalizer::GROUPS => $writeGroups, AbstractNormalizer::OBJECT_TO_POPULATE => $entity, ], ); } catch (JsonException|SerializerExceptionInterface $e) { return $this->jsonError('Ошибка десериализации: ' . $e->getMessage(), Response::HTTP_BAD_REQUEST); } if (($validationResponse = $this->validate($entity)) !== null) { return $validationResponse; } $this->em->flush(); return $this->json($entity, Response::HTTP_OK, $readGroups); } public function delete(object $entity): JsonResponse { $this->em->remove($entity); $this->em->flush(); return new JsonResponse(null, Response::HTTP_NO_CONTENT); } /** * @return array|null null если тело не является JSON-объектом */ private function decodePayload(Request $request): ?array { try { return $request->toArray(); } catch (JsonException) { return null; } } /** * @param array $payload * * @throws JsonException */ private function encodePayload(array $payload): string { return json_encode($payload, JSON_THROW_ON_ERROR); } private function validate(object $entity): ?JsonResponse { $errors = $this->validator->validate($entity); if (count($errors) === 0) { return null; } $formatted = []; foreach ($errors as $error) { $formatted[] = [ 'property' => $error->getPropertyPath(), 'message' => $error->getMessage(), ]; } return new JsonResponse(['errors' => $formatted], Response::HTTP_UNPROCESSABLE_ENTITY); } /** * @param list $groups */ private function json(mixed $data, int $status, array $groups): JsonResponse { $json = $this->serializer->serialize($data, 'json', [ AbstractNormalizer::GROUPS => $groups, ]); return new JsonResponse($json, $status, [], true); } private function jsonError(string $message, int $status): JsonResponse { return new JsonResponse(['error' => $message], $status); } }