Skip to main content

Atomicly update an embedded document with Doctrine MongoDB ODM

I had to deal with an unexpected problem recently. In my setup, I have a Conversation document which contains multiple Message documents embedded inside - a one-to-many mapping basically - and I need to atomically push a new Message when a user replies to a conversation. Below is the initial code that I implemented:

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();
}

Unfortunately, this approach failed miserably. Because if you use the push() call with a document instance, you get a Catchable Fatal Error: Object of class ... could not be converted to string in /vendor/bundles/Symfony/Bundle/DoctrineMongoDBBundle/Logger/DoctrineMongoDBLogger.php line 280 error.

After losing some hair over this, I decided to seek help. Luckily, jmikola (one of the developers of Doctrine MongoDB ODM) replied to my question at stackoverflow.com.

Turns out, the query builder is not really intended to work with document instances. As an alternative approach, I used an array representation of the Message document as illustrated below:

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();
}

According to jmikola, you can also use the document manager to achieve the same result (see his last comment under his answer), but I have not verified this yet.

Comments

Popular posts from this blog

Securing Symfony2 REST services with FOSOAuthServerBundle

Overview In my previous article, I wrote about setting up a Symfony2 REST service using FOSRestBundle. However, this REST service was behind a firewall protected by a generic form_login provider. Not really ideal if you wish to open your REST API to other applications. So in this article, I will try to explain how to set up FOSOAuthServerBundle to protect your REST API methods using OAuth2. Before we start getting into the gritty details, it is a good idea to have a look at the official OAuth2 documentation . Let's begin... FOSOAuthServerBundle Installation You have to install v1.1.0 of FOSOAuthServerBundle if you are using Symfony 2.0.x. If not, see the docs . First, add the following entries to your deps file: [FOSOAuthServerBundle] git=git://github.com/FriendsOfSymfony/FOSOAuthServerBundle.git target=bundles/FOS/OAuthServerBundle version=origin/1.1.x [oauth2-php] git=git://github.com/FriendsOfSymfony/oauth2-php.git Run the vendors script to install these...

Adding post-login logic to FOSUserBundle

Having finally figured out how to use FOSUserBundle in my project, I decided to keep track of all logins next. The implementation turned out to be a breeze thanks to Symfony2's security listener mechanism. As usual, the first step is to create a MongoDB document for this purpose. This is a very simple document that contains a user's id, session id, IP address, and login date. src/Acme/UserBundle/Document/LoginHistory.php namespace Acme\UserBundle\Document; class LoginHistory { protected $id; protected $userId; protected $sessionId; protected $ip; protected $createdAt; /** * Get id * * @return custom_id $id */ public function getId() { return $this->id; } /** * Set userId * * @param int $userId */ public function setUserId($userId) { $this->userId = $userId; } /** * Get userId * * @return int $userId */ public function getUserId...

Setting up a Symfony2 REST service with FOSRestBundle

Installation First thing is to download and setup the FOSRestBundle. If you are running Symfony 2.0.x, get the FOSRestBundle version 0.6, otherwise, download the code in the master branch. The FOSRestBundle depends on the JMSSerializerBundle so the following instructions are going to include some extra information for setting up this bundle to complete our REST service. First thing is to install our dependencies. Add the following to your deps file: [metadata] git=http://github.com/schmittjoh/metadata.git version=1.1.0 [JMSSerializerBundle] git=git://github.com/schmittjoh/JMSSerializerBundle.git target=bundles/JMS/SerializerBundle [FOSRest] git=git://github.com/FriendsOfSymfony/FOSRest.git target=fos/FOS/Rest [FOSRestBundle] git=git://github.com/FriendsOfSymfony/FOSRestBundle.git target=bundles/FOS/RestBundle version=origin/0.6 Run the vendors script to install these bundles: php bin/vendors install Next, update your app/autoload.php file...