3

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.

tester
  • 3,977
  • 5
  • 39
  • 59
  • Well, you should [avoid DBREF at all costs](http://www.transmachina.com/en/knowledge-resources/mongo-tip-avoid-dbref-all-costs), and even if you wanna use, `structure it well`. The Product collection should have the Categories ref, `not vice-versa`. And i've never practically used DBREF. Anyways have a look at [How to DBREF](http://stackoverflow.com/questions/6195286/how-to-query-mongodb-with-dbref) and/or [Another Help](http://stackoverflow.com/questions/8713304/mongodb-dbref) – Ravi Sep 07 '14 at 15:12
  • Thanks for the comment. I now use simple ObjectIds, as this is totally sufficient for me. But the same error still appears as mentioned in my problem statement. But you said one interesting thing: `not vice-versa`. Do you have some argumentation for that? I can't follow you, why not vice-versa? – tester Sep 07 '14 at 18:52
  • `vice-versa`: If you have to query the Product in your case, then you have to apply queries onto Category collection to fetch `Product && Categories` data, as that is having the Id of Product fields. – Ravi Sep 08 '14 at 05:47
  • and show your new/modified Collections sample for both collections. – Ravi Sep 08 '14 at 05:49
  • But if you click on a category you want to load all the products belonging to this category. Therefore the category holds products? – tester Sep 08 '14 at 08:43
  • Yeah, that's true.... I've figured this other way around. Apologies! – Ravi Sep 08 '14 at 09:39
  • I concur with @Ravi that you're better served with simple references in this case (since you're always referring to documents in the same collection); however, DBRefs do serve a purpose in Doctrine, especially when working with references to discriminated classes. See my comment [here](http://stackoverflow.com/questions/8713304/mongodb-dbref#comment40342524_8713820) (in the question that was linked above) for context. – jmikola Sep 11 '14 at 17:08

1 Answers1

3

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.

jmikola
  • 6,892
  • 1
  • 31
  • 61