Reputation: 4633
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
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 callgetQuery()
, 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