I'm fairly new to Symfony 5.4 and recently created my first API using that version
For my specific API endpoint, one of the parameters is an array of IDs.
I need to validate the array via:
I implemented it in a simple way, checking the array before persisting the entity using typecasting and the existing Repository
:
$parentPropertyIds = (array)$request->request->get('parent_property_ids'); if ($parentPropertyIds) { $parentCount = $doctrine->getRepository(Property::class)->countByIds($parentPropertyIds); if ($parentCount !== count($parentPropertyIds)) { return $this->json([ 'status' => 'error', 'message' => 'parent_property_id_invalid' ], 422); } foreach ($parentPropertyIds as $parentPropertyId) { $parentProperty = $doctrine->getRepository(Property::class)->find($parentPropertyId); $property->addParent($parentProperty); } }
However, this makes my controller actions too "body positive" and feels like something that could be implemented in a more elegant way.
I can't find anything in the Symfony 5.4 documentation.
Currently I want to know if:
Full endpoint code:
/** * @Route("/property", name="property_new", methods={"POST"}) */ public function create(ManagerRegistry $doctrine, Request $request, ValidatorInterface $validator): Response { $entityManager = $doctrine->getManager(); $property = new Property(); $property->setName($request->request->get('name')); $property->setCanBeShared((bool)$request->request->get('can_be_shared')); $parentPropertyIds = (array)$request->request->get('parent_property_ids'); if ($parentPropertyIds) { $parentCount = $doctrine ->getRepository(Property::class) ->countByIds($parentPropertyIds); if ($parentCount !== count($parentPropertyIds)) { return $this->json([ 'status' => 'error', 'message' => 'parent_property_id_invalid' ], 422); } foreach ($parentPropertyIds as $parentPropertyId) { $parentProperty = $doctrine->getRepository(Property::class)->find($parentPropertyId); $property->addParent($parentProperty); } } $errors = $validator->validate($property); if (count($errors) > 0) { $messages = []; foreach ($errors as $violation) { $messages[$violation->getPropertyPath()][] = $violation->getMessage(); } return $this->json([ 'status' => 'error', 'messages' => $messages ], 422); } $entityManager->persist($property); $entityManager->flush(); return $this->json([ 'status' => 'ok', 'id' => $property->getId() ]); }
P粉6355097192023-12-19 16:22:56
You can use Data Transfer Objects (DTOs) with Authentication services. There are many predefined constraints , or you can create a custom constraint. < /p>
For example, how to use simple constraints as annotations:
class PropertyDTO { /** * @Assert\NotBlank */ public string $name = ""; public bool $shared = false; }
Then assign the data to the DTO:
$propertyData = new PropertyDTO(); $propertyData->name = $request->request->get('name'); ...
In some cases, it is better to define the constructor in the DTO and then request from and pass it to the DTO immediately:
$data = $request->getContent(); // or $request->getArray(); depends on your content type $propertyData = new PropertyDTO($data);
Then verify it:
$errors = $validator->validate($propertyData); if (count($errors) > 0) { /* * Uses a __toString method on the $errors variable which is a * ConstraintViolationList object. This gives us a nice string * for debugging. */ $errorsString = (string) $errors; return $this->json([ 'status' => 'error', 'message' => 'parent_property_id_invalid' ], 422); } //...