George Irimiciuc
George Irimiciuc

Reputation: 4633

Update certain fields based on condition in Doctrine

I have created a Symfony 2 Bundle that supports private messages between users. I gave them the ability to send messages from their inbox or sent folder to the trash one. Messages will be marked as trash through the isRTrash and isSTrash fields, marked by receiver and by sender, respectively. That is because, being the same message in my database, if I had one single field here, one user marking it as trash, would mark it for the other one, as well.

Now, I want to give them the possibility to delete them, too, from their trash folder. Messages won't be deleted, but marked similarly to trash ones, just that they are forever gone from standard user view. I'm having problems with marking them like this, because I have to mark both messages that are sent and received by the user.

I've made the following query in the entity's repository:

    public function delete($user, $msg)
    {
        $qb = $this->createQueryBuilder('a')
            ->update('PrivateMessageBundle:Message', 'a')
            ->where('a IN(:msg)')
            ->andwhere('a.receiver = :user AND a.isRTrash IS NOT null AND a.isRDeleted = false')->set('a.isRDeleted', true)
            ->orWhere('a.sender = :user AND a.isSTrash IS NOT null AND a.isSDeleted = false')->set('a.isSDeleted', true)
            ->setParameters(
                array('user' => $user, 'msg' => $msg)
            );
echo '<pre>';
\Doctrine\Common\Util\Debug::dump($qb->getQuery()->getSQL()); exit;
echo '</pre>';
        return $qb->getQuery();
    }

And the output query is string(196) "UPDATE message SET isRDeleted = 1, isSDeleted = 1 WHERE (id IN (?) AND (receiver_id = ? AND isRTrash IS NOT NULL AND isRDeleted = 0)) OR (sender_id = ? AND isSTrash IS NOT NULL AND isSDeleted = 0)"

I give as input the curent logged in user and an array of message id's. Then, I check messages that are in trash, are not marked as deleted and have the curent user as receiver or sender and want to mark them as deleted.

The problem is that both conditions are met, and both SET are being called, marking a message's isRDeleted and isSDeleted to true, regardless.

I am very close, but don't know how to make it so that the fields are marked separately, only if their condition is met.

Meanwhile, I'm using a foreach loop, but I think it can be done faster with a query

   $em = $this->getDoctrine()->getManager();
    foreach ($msgs as $msgid) {
        $msg = $messages->findOneBy(array('id' => $msgid));


        if ($msg->getSender() == $this->getUser() && $msg->getIsSTrash() && $msg->getIsSDeleted() == false) {
            $msg->setIsSDeleted(true);
            $changedno++;
        } else if ($msg->getReceiver() == $this->getUser() && $msg->getIsRTrash() && $msg->getIsRDeleted() == false) {
            $msg->setIsRDeleted(true);
            $changedno++;
        }

        $em->flush();
    }

Upvotes: 2

Views: 2123

Answers (1)

tchap
tchap

Reputation: 3432

I think you need a CASE .. WHEN construction but Doctrine doesn't have that in DQL (See the Grammar). So you either must use a raw query, something along these lines (it's pseudo MySQL) :

UPDATE PrivateMessageBundle:Message a
SET a.isRDeleted = CASE
    WHEN a.receiver = :user AND a.isRTrash IS NOT null THEN TRUE
    ELSE a.isRDeleted = FALSE
END, 
SET a.isSSDeleted = CASE
    WHEN a.receiver = :user AND a.isRTrash IS NOT null THEN TRUE
    ELSE a.isSDeleted = FALSE
END

... or use two standard queries, one for isRDeleted and one for isSDeleted, like the one you already did. To be honest I think that's a pretty simple solution in your case, and it looks more maintenance-friendly if you ever need to read your code again in six months.

NB : on a side note, the ->set() or ->when() functions in Doctrine (and all the others, in fact) do not follow a specific order; they are just adding properties to the Doctrine query object, and when you call getQuery(), a SQL query is made. That means that the following construction :

 ->when()->set()
 ->orwhen()->set()

is equivalent to :

 ->set()->set()
 ->when()->orWhen() 

which is why your solution cannot work. There is no condition to be met before set() is called (if I'm not clear, tell me)

Upvotes: 2

Related Questions