You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
What I expect from this method is to be able to update one of multiple fields of an entity and the validation should only check these fields.
The problem being that we deserialize the entity, so we actually lose a lot of information.
The way I solved this is with this method:
protected function put(Request $request, $deserializedEntity)
{
$id = $request->get('id');
if (!isset($id)) {
throw $this->createNotFoundException('Parameter required: id');
}
$em = $this->getDoctrine()->getManager();
$entityClassName = $em->getClassMetadata(ClassUtils::getRealClass(get_class($deserializedEntity)))->getName();
$entity = $em->getRepository($entityClassName)->find($id);
if (!$entity) {
throw $this->createNotFoundException('No such entity '.$entityClassName.' with id: '.$id);
}
$this->partialUpdate($request, $deserializedEntity, $entity);
$this->validateEntityField($entity, $request->request->all());
$em->persist($entity);
$em->flush();
return $entity;
}
But for this to work, I needed to override RequestBodyParamConverter with my own implementation, just to add two lines and two methods:
/**
* {@inheritdoc}
*
* @param Request $request
* @param ParamConverter $configuration
*/
public function apply(Request $request, ParamConverter $configuration)
{
$options = (array) $configuration->getOptions();
if (isset($options['deserializationContext']) && is_array($options['deserializationContext'])) {
$arrayContext = array_merge($this->context, $options['deserializationContext']);
} else {
$arrayContext = $this->context;
}
$this->configureContext($context = new Context(), $arrayContext);
try {
$object = $this->serializer->deserialize(
$request->getContent(),
$configuration->getClass(),
$request->getContentType(),
$context
);
} catch (UnsupportedFormatException $e) {
return $this->throwException(new UnsupportedMediaTypeHttpException($e->getMessage(), $e), $configuration);
} catch (JMSSerializerException $e) {
return $this->throwException(new BadRequestHttpException($e->getMessage(), $e), $configuration);
} catch (SymfonySerializerException $e) {
return $this->throwException(new BadRequestHttpException($e->getMessage(), $e), $configuration);
}
// Added
$this->addAssociations($request, $object);
$this->addDefaultValues($object);
// End
$request->attributes->set($configuration->getName(), $object);
if (null !== $this->validator) {
$validatorOptions = $this->getValidatorOptions($options);
$errors = $this->validator->validate($object, null, $validatorOptions['groups']);
$request->attributes->set(
$this->validationErrorsArgument,
$errors
);
}
return true;
}
/**
* Initializes an entity by calling its constructor after unserialization.
*
* @param object $object The object to update
*/
protected function addDefaultValues(&$object)
{
$accessor = PropertyAccess::createPropertyAccessor();
$entityClass = ClassUtils::getRealClass(get_class($object));
$newEntity = new $entityClass();
// Add author if not set
if ($accessor->isReadable($object, 'author')) {
$author = $accessor->getValue($object, 'author');
$user = $this->tokenStorage->getToken()->getUser();
if (is_null($author) && !is_null($user)) {
$accessor->setValue($object, 'author', $user);
}
}
$this->duplicateFields($object, $newEntity);
}
/**
* Initializes an entity by calling its constructor after unserialization.
*
* @param object $dest The object to update
* @param object $src The object from which to duplicate all fields
*/
protected function duplicateFields(&$dest, $src)
{
$accessor = PropertyAccess::createPropertyAccessor();
$destClass = ClassUtils::getRealClass(get_class($dest));
$srcClass = ClassUtils::getRealClass(get_class($src));
if ($destClass != $srcClass) {
throw new \Exception('Cannot duplicate fields. Source and destination are not the same class.');
}
$metadataFactory = $this->em->getMetadataFactory();
$class = $metadataFactory->getMetadataFor($destClass);
foreach ($class->getFieldNames() as $fieldName) {
try {
$destValue = $accessor->getValue($dest, $fieldName);
$srcValue = $accessor->getValue($src, $fieldName);
if (is_null($destValue) && !is_null($srcValue)) {
$accessor->setValue($dest, $fieldName, $srcValue);
}
} catch (\Exception $e) {
// Ignore
}
}
}
Do you see a better and cleaner way to handle partial update ?
Thank you for your feedback.
The text was updated successfully, but these errors were encountered:
Hello,
I've implemented a few years ago a partial update version of RequestBodyParamConverter.
What I expect from this method is to be able to update one of multiple fields of an entity and the validation should only check these fields.
The problem being that we deserialize the entity, so we actually lose a lot of information.
The way I solved this is with this method:
But for this to work, I needed to override RequestBodyParamConverter with my own implementation, just to add two lines and two methods:
Do you see a better and cleaner way to handle partial update ?
Thank you for your feedback.
The text was updated successfully, but these errors were encountered: