FOSRestBundle icon indicating copy to clipboard operation
FOSRestBundle copied to clipboard

Update one/few fields with patch action ?

Open Jerome1337 opened this issue 9 years ago • 4 comments

I have made a PatchAction without using forms and I am trying to ignore null/default value

Currently my action look like this

/*
 * @ParamConverter("updatedServer", converter="fos_rest.request_body")
 *
 * @return View
 */
public function patchAction(Server $server, Server $updatedServer, ConstraintViolationListInterface $validationErrors)
{
    if ($validationErrors->count() > 0) {
        return $this->handleBodyValidationErrorsView($validationErrors);
    }

    $server->setAlias($updatedServer->getAlias())
        ->setMac($updatedServer->getMac())
        ->setSshUser($updatedServer->getSshUser())
        ->setSshPort($updatedServer->getSshPort())
        ->setIpmiAddress($updatedServer->getIpmiAddress())
        ->setIpmiLogin($updatedServer->getIpmiLogin())
        ->setIpmiPassword($updatedServer->getIpmiPassword())
        ->setMysqlHost($updatedServer->getMysqlHost())
        ->setMysqlRoot($updatedServer->getMysqlRoot())
        ->setWebServer($updatedServer->getWebServer())
        ->setWebServerSslListen($updatedServer->getWebServerSslListen())
        ->setWebServerSslPort($updatedServer->getWebServerSslPort())
        ->setMysqlServer($updatedServer->getMysqlServer())
        ->setSuphp($updatedServer->getSuphp())
        ->setFastcgi($updatedServer->getFastcgi())
        ->setNadminCompliant($updatedServer->getNadminCompliant())
        ->setEmailCompliant($updatedServer->getEmailCompliant())
        ->setAvailable($updatedServer->getAvailable())
        ->setEnvironment($updatedServer->getEnvironment())
        ->setInstalledAt($updatedServer->getInstalledAt());

    $em = $this->getDoctrine()->getManager();

    $em->persist($server);
    $em->flush();

    return $this->view($server);
}

I set manually all fields I can update. But this method don't working because if I send a request to update only one or few fields with a JSON body like this

{
    "mac": "ff:ff:ff:ff:ff:ff"
}

The JSON body will look like this after I send the request

// This is what $updatedServer get in my controller
{
    "id": null,
    "name": null,
    "alias": null,
    "notes": null,
    "hosted_domain": null,
    "mac": "ff:ff:ff:ff:ff:ff",
    // ...
}

As you can see above in my controller I have set every updatable fields

$server->setAlias($updatedServer->getAlias())
    ->setMac($updatedServer->getMac())
    ->setSshUser($updatedServer->getSshUser())
    // ...

So if a value is null inside the body request the controller will set it to null, it will do the same with the default values set inside the entity

I don't know if this really a FOSRestBundle

How can I update only one or few field with a patch action without this problem ? Is this possible / the right way ?

I searched on the Internet few hours + asking a question on Stackoverflow without success...

Am I obligated to use forms ?

Thanks

Jerome1337 avatar Apr 06 '16 15:04 Jerome1337

Imo if you handle the patch request manually, you will need to check on your own if any of the methods of the $updatedServer object returns null before calling the setter of the $server instance.

xabbuh avatar Apr 06 '16 16:04 xabbuh

@xabbuh Yes I had this idea too, but I must write >15 condition. This is not really what I want... This can be handled automatiquely ?

Jerome1337 avatar Apr 07 '16 08:04 Jerome1337

Using the Form component is one way to do that. Otherwise you will have to write some custom code that does that in a more elegant way than writing a lot of if statements.

xabbuh avatar Apr 07 '16 10:04 xabbuh

Old issue, I have the same problem. Maybe there is a better solution after some years...

This is using the ObjectNormalizer and PropertyAccessorInterface services from Symfony:

    public function update(Zone $oldZone, Zone $newZone, array $context = [])
    {
        $data = $this->objectNormalizer->normalize($newZone, null, $context);
        foreach ($data as $property => $value) {
            $this->propertyAccessor->setValue($oldZone, $property, $value);
        }
    }

Context could be retrieved from the controller using:

$context = [];
$config = $request->attributes->get('_template');
if ($config instanceof ViewAnnotation) {
  $context['groups'] = $config ->getSerializerGroups();
}

gremo avatar Oct 17 '22 09:10 gremo