简体   繁体   中英

Atomic operations with Doctrine MongoDB ODM

I can't seem to get the following query to work. Basically, I am trying to add a message document to a conversation document as illustrated below:

public function reply($conversationId, Message $message, $flush = true)
{            
    $this->dm->createQueryBuilder($this->class)
        ->field('archivers')->unsetField()
        ->field('repliedBy')->set($message->getUserId())
        ->field('repliedBody')->set($message->getBody())
        ->field('repliedAt')->set(new \DateTime())
        ->field('modifiedAt')->set(new \DateTime())
        ->field('messages')->push($message)
        ->field('id')->equals(new \MongoId($conversationId))
        ->getQuery()
        ->execute();

    if ($flush) {
        $this->dm->flush();
    }
}

This reply method gets called in two ways. First by a user posting a message via a html form, and second by a REST call made by an Android application. The form works but the REST call fails (the rest implementation uses the JMSSerializerBundle with FOSRestBundle btw)...

I have verified that the code gets called and the parameters passed to the method are valid in both cases, but for some reason the commit() call inside UnitOfWork.php ignores the changes to the document. See line 413 to see what I mean.

Does anybody have an idea why this might be happening?

Below are the other approaches that I have tried:

First, I added an update() call which fails with "Catchable Fatal Error: Object of class ... could not be converted to string in /vendor/bundles/Symfony/Bundle/DoctrineMongoDBBundle/Logger/DoctrineMongoDBLogger.php line 280".

public function reply($conversationId, Message $message, $flush = true)
{            
    $this->dm->createQueryBuilder($this->class)
        ->update()
        ->field('archivers')->unsetField()
        ->field('repliedBy')->set($message->getUserId())
        ->field('repliedBody')->set($message->getBody())
        ->field('repliedAt')->set(new \DateTime())
        ->field('modifiedAt')->set(new \DateTime())
        ->field('messages')->push($message)
        ->field('id')->equals(new \MongoId($conversationId))
        ->getQuery()
        ->execute();

    if ($flush) {
        $this->dm->flush();
    }
}

Second approach I tried was pushing an array instead of an object:

public function reply($conversationId, Message $message)
{            
    $this->dm->createQueryBuilder($this->class)
        ->update()
        ->field('archivers')->unsetField()
        ->field('repliedBy')->set($message->getUserId())
        ->field('repliedBody')->set($message->getBody())
        ->field('repliedAt')->set(new \DateTime())
        ->field('modifiedAt')->set(new \DateTime())
        ->field('messages')->push(array(
            '_id' => new \MongoId(),
            'userId' => $message->getuserId(),
            'body' => $message->getBody(),
            'createdAt' => new \DateTime(),
            'modifiedAt' => new \DateTime(),
        ))
        ->field('id')->equals(new \MongoId($conversationId))
        ->getQuery() 
        ->execute();

    $this->dm->flush();
}

Which works fine until the flush() method gets called. The flush() causes duplicate objects to be pushed. So I get two copies of the same message in the conversation (commenting the flush() solves the issue but the application has multiple flush() calls in other classes).

Another push query that fails with an object:

public function archive($conversationId, $userId)
{

    $userStamp = new UserStamp();
    $userStamp->setUserId($userId);

    $this->dm->createQueryBuilder($this->class)
        ->update()
        ->field('archivers')->push($userStamp)
        ->field('modifiedAt')->set(new \DateTime())
        ->field('id')->equals(new \MongoId($conversationId))
        ->getQuery()
        ->execute();
}

If the push() call is removed, everything works fine.

Still stuck at this point.

The query builder is typically used to execute multi-document or command queries against MongoDB, which bypass document management. The only exception is if you are performing a hydrated find query, which is described in the query builder documentation . Your example above is equivalent to:

$collection->update(
    ['_id' => new \MongoId($conversationId)],
    [
        '$set' => [
            'repliedBy' => $message->getUserId(),
            'repliedBody' => $message->getbody(),
            'repliedAt' => new \MongoDate(),
            'modifiedAt' => new \MongoDate(),
        ],
        '$push' => ['messages' => $message],
    ],
    ['multiple' => false]
);

Note that your field mappings will be utilized (eg Datetime will become a MongoDate), but there is no document to manage with this query.

If pushing an object durring an update use \\stdClass in php. quick example:

public function reply($conversationId, Message $message)
{      
       $object = new \stdClass();
       $object->_id = new \MongoId();
       $object->userId = $message->getuserId();
       $object->body = $message->getBody();
       $object->createdAt = new \MongoDate();
       $object->modifiedAt = new \MongoDate();

       $this->dm->createQueryBuilder($this->class)
        ->update()
        ->field('archivers')->unsetField()
        ->field('repliedBy')->set($message->getUserId())
        ->field('repliedBody')->set($message->getBody())
        ->field('repliedAt')->set(new \DateTime())
        ->field('modifiedAt')->set(new \DateTime())
        ->field('messages')->push()
        ->field('id')->equals(new \MongoId($conversationId))
        ->getQuery() 
        ->execute();

        $this->dm->flush();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM