Search code examples
mongodbdoctrinefatal-errordoctrine-odmodm

MongoDB Doctrine: $in needs an array - Many to Many Relation


I have some categories which contain products, whereas you can add products to a category. This is a many to many relation, whereas products do not know of their relation to a category (as it is only saved inside a category). I will first post my setting and then the query which makes problems.

/** @ODM\Document */
class Category
{
    /** @ODM\Id */
    private $id;

    /** @ODM\String */
    private $name;

    /** @ODM\ReferenceMany(targetDocument="Product", inversedBy="category") */
    private $products;

    public function setProducts($products)
    {
        $this->products = $products;
    }
}

/** @ODM\Document */
class Product
{
    /** @ODM\Id */
    private $id;

    /** @ODM\String */
    private $name;

    /** @ODM\Float */
    private $price;

    /** @ODM\ReferenceMany(targetDocument="Category", mappedBy="products") */
    private $category;
}

In order to have some data, I create it like this:

$p1 = new Documents\Product();
$p1->setName('p1');
$p1->setPrice(1.99);

$p2 = new Documents\Product();
$p2->setName('p2');
$p2->setPrice(3.99);

$c1 = new Documents\Category();
$c1->setName('category1');
$c1->setProducts(array($p1, $p2));

$c2 = new Documents\Category();
$c2->setName('category2');
$c2->setProducts(array($p1, $p2));

$dm->persist($p1);
$dm->persist($p2);
$dm->persist($c1);
$dm->persist($c2);
$dm->flush();

Looking into MongoDB, I now have the following data:

db.Category.find()
{ "_id" : ObjectId("53e3d8d3e2afec2303d63afa"), "name" : "category1", "products" : [ DBRef("Product", ObjectId("53e3d8d3e2afec2303d63af8")), DBRef("Product", ObjectId("53e3d8d3e2afec2303d63af9")) ] }
{ "_id" : ObjectId("53e3d8d3e2afec2303d63afb"), "name" : "category2", "products" : [ DBRef("Product", ObjectId("53e3d8d3e2afec2303d63af8")), DBRef("Product", ObjectId("53e3d8d3e2afec2303d63af9")) ] }

db.Product.find()
{ "_id" : ObjectId("53e3d8d3e2afec2303d63af8"), "name" : "p1", "price" : 1.99 }
{ "_id" : ObjectId("53e3d8d3e2afec2303d63af9"), "name" : "p2", "price" : 3.99 }

I now want to query this data: Getting a product by id and getting all its categories, it belongs to:

$product = $dm->find('Documents\Product', '53e3d8d3e2afec2303d63af8');
var_export($product->getName());
$category = $product->getCategory();
var_export(sizeof($category));
foreach($category as $c){
    var_export($c->getName());
}

// Outputs: 'p1' 2 'category1' 'category2'

But if I want to query the data the other way round: Getting a category and getting all its products, the category has:

$category = $dm->find('Documents\Category', '53e3d8d3e2afec2303d63afa');
var_export($category->getName());
$products = $category->getProducts();
var_export(sizeof($products));
foreach($products as $p){
    var_export($p->getName());
}

// Outputs: 'category1' 2

No products are displayed and I get a fatal error:

Fatal error: Uncaught exception 'MongoCursorException' with message 'localhost:27017: Can't canonicalize query: BadValue $in needs an array' in /private/var/www/mongo/vendor/doctrine/mongodb/lib/Doctrine/MongoDB/Cursor.php on line 288

If I look into the MongoDB log, the query is as follows:

assertion 17287 Can't canonicalize query: BadValue $in needs an array ns:shop.Product query:{ $query: { _id: { $in: { 53e3d8d3e2afec2303d63af8: ObjectId('53e3d8d3e2afec2303d63af8'), 53e3d8d3e2afec2303d63af9: ObjectId('53e3d8d3e2afec2303d63af9') } } }, $orderby: [] }

I mean, the problem seems clear: $in needs an array, but there is no JSON array given. But I don't know how to repair it. What am I doing wrong? If you can't answer this particular problem, can you provide me a working example for MongoDB with Doctrine, where you have a many to many relation like this and can query the data by both sides?

UPDATE: I maybe should have mentioned, which version of doctrine I use. I used this setting:

{
    "require":{
        "doctrine/common":"2.3.*",
        "doctrine/dbal":"2.3.*",
        "doctrine/orm":"*",
        "doctrine/mongodb-odm": "1.0.0-BETA9"
    }
}

Then I thought of updating to newest version available and used this setting:

{
     "require":{
        "doctrine/common":"2.4.*",
        "doctrine/dbal":"2.3.*",
        "doctrine/orm":"*",
        "doctrine/mongodb-odm": "dev-master"
    }
}

And voilà: Everything works fine.... OMG. But any explanations for this are still welcome, I want to know what happened here. Thanks a lot.


Solution

  • The bug you were experiencing was fixed in PR #743, which was included in 1.0.0-BETA10. It was one of several fixes necessary for compatibility with MongoDB 2.6 (see issue #741), as the server became more strict about validating query criteria.

    Server versions before 2.4 accepted objects for $in, and simply ignored the keys. If you take a look at the BSON specification, you'll notice that arrays and objects have a very similar structure apart from the type code. Each is a sequence of key/value pairs, and the array payloads look very much like objects with sequential numeric strings as their keys. That should explain why it was so easy for older server versions to accept either type.