Search code examples
phpsymfonyfosrestbundle

FosRestBundle PATCH action prevent update entity with null/default values


I have made a working patchAction on my serverController to update one or some field in a existent server.

Actually my patchAction 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());

    if (null !== $updatedServer->getOs()) {
        $os = $this->getDoctrine()->getRepository('AppBundle:Os')->findBy(['id' => $updatedServer->getOs()->getId()]);
        $server->setOs($os[0]);
    }

    if (null !== $updatedServer->getPuppetClasses()) {
        $puppetClass = $this->getDoctrine()->getRepository('AppBundle:PuppetClass')->findBy(['id' => $updatedServer->getPuppetClasses()[0]->getId()]);
        $server->setPuppetClasses($puppetClass);
    }

    if (null !== $updatedServer->getPuppetTemplates()) {
        $puppetTemplate = $this->getDoctrine()->getRepository('AppBundle:PuppetTemplate')->findBy(['id' => $updatedServer->getPuppetTemplates()[0]->getId()]);
        $server->setPuppetTemplates($puppetTemplate);
    }

    if (null !== $updatedServer->getBackupModel()) {
        $backupModel = $this->getDoctrine()->getRepository('AppBundle:BackupModel')->findBy(['id' => $updatedServer->getBackupModel()->getId()]);
        $server->setBackupModel($backupModel[0]);
    }

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

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

    return $this->view([$updatedServer, $server]);
}

The problem is when trying to update only one or few fields. I set a JSON body to change datas.

{
    "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 had the idea to make a if condition for every updatable fields but I will have >20 conditions inside...

How can I prevent this to append with a generic and reusable system ?

Can these values can be ignored if they are not set before the execution of the request ?

Maybe create callback inside my entity class ?

Thanks

EDIT

My Server $server is the current server object I want to update. It's coming with the request. Exemple when I send this request /api/servers/2 I get the body of the server with the ID 2.

The Server $updatedServer is the body with the updated datas.

I tried your second edit but I get a 500 error

"Unable to guess how to get a Doctrine instance from the request information."

Because I can't get the server I want to patch and the body (with ParamConverter) in the same time.


Solution

  • I just found the answer to that question

    Use a set parameter function in your entity which will update the field that you will give in your request body

    App\Entity\Server

    use Doctrine\Common\Inflector\Inflector;
    
    public class Server {
    
        public function setParameters($params) {
            foreach ($params as $k => $p) {
                if (!is_null($p)) { // here is the if statement
                    $key = Inflector::camelize($k);
                    if (property_exists($this, $key)) {
                        $this->{'set' . ucfirst($key)}($p);
                    }
                }
            }
            return $this;
        }
    }
    

    App\Controller\ServerApiController

    public function patchAction(Server $server, Request $request, ConstraintViolationListInterface $validationErrors)
    {
        if ($validationErrors->count() > 0) {
            return $this->handleBodyValidationErrorsView($validationErrors);
        }
    
        $data = json_decode($request->getContent());
        $em->persist($server->setParameters($data););
        $em->flush();
    
        return $this->view([$server]);
    }
    

    It should return the content of server + the field update by the body of your request which will be change in the $server->setParameter($data)