2

Summary

I'm trying to find a way to change a referenced variable two levels up whilst avoiding Deprecated: Call-time pass-by-reference has been deprecated

Research I've done

I've looked through this and this and it seems like call_user_func_array can silence the warning however I think I'm missing something.

The Problem

I'm using MongoDB with PHP, the following method belongs to a model and simply checks the schema of the input that is passed into it by reference before saving it.

// $this->collection is the MongoCollection object
public function save(&$entry) {

    if( empty($entry) ) return false;
    if( !$this->checkSchema($entry) ) $this->throwDbError('Schema violation in `' . get_class($this) . '`');

    try { return $this->collection->save(&$entry); } // <---- want to avoid using &
    catch (Exception $e) { return $this->throwDbError($e); }

}

MongoCollection::save ($this->collection->save) will append the _id field onto $entry with the new document id. However this change isn't being reflected on the $entry that is passed into the method above unless I pass it call time by reference. (Essentially I want MongoCollection::save to be able to modify $entry two levels up)

All right, that's my best shot at explaining the problem, let me know if you need clarification.

Community
  • 1
  • 1
Nathan Kot
  • 2,392
  • 1
  • 21
  • 31
  • 3
    "// <---- want to avoid using &" --- so delete it. Place the cursor right before the `&` sign and press "delete". Seriously, in php5 it makes no sense. You can only specify that variable should be passed as a reference **only** in a function declaration. – zerkms Oct 12 '12 at 11:17
  • i know i can, but then the $entry that gets passed into the method above doesn't get [_id] appended to it (which i need) – Nathan Kot Oct 12 '12 at 11:19
  • @NathanKot Seems, that `$entry` should be an object. Either that, or remembe, that methods can have a return value ;) – KingCrunch Oct 12 '12 at 11:21
  • @Nathan Kot: well, it's just a mongodb driver bug then – zerkms Oct 12 '12 at 11:21
  • @KingCrunch: yep, that's what I also thought of, but no - http://www.php.net/manual/en/mongocollection.save.php – zerkms Oct 12 '12 at 11:21
  • @Nathan Kot: how about this: http://www.php.net/manual/en/mongocollection.insert.php see the example #1 – zerkms Oct 12 '12 at 11:24
  • @KingCrunch thanks, I considered returning the array but it may cause mistakes down the line as MongoCollection::save simply modifies the _id so I want to mirror its functionality – Nathan Kot Oct 12 '12 at 11:25
  • @zerkms save() allows creation of new documents which is what I need, insert also amends the _id but I'm guessing that it'd have the same problem? – Nathan Kot Oct 12 '12 at 11:26
  • @Nathan Kot: have you checked the Example #1? Oh, just checked your location - hi ;-) – zerkms Oct 12 '12 at 11:27
  • @zerkms ahhh sorry missed that hmmmmm so passing by reference means _id isn't changed, so I get why not passing reference isnt working, but why does passing the reference work? – Nathan Kot Oct 12 '12 at 11:31
  • @Nathan Kot: it does work for `save()` not `insert()` – zerkms Oct 12 '12 at 11:32
  • @zerkms oh hey! haha i'm from auckland – Nathan Kot Oct 12 '12 at 11:32
  • @Nathan Kot: well, anyway, just don't use passing by reference in the function call, it's just wrong. And `insert()` should work. – zerkms Oct 12 '12 at 11:33
  • @zerkms yep avoiding pass by ref, but still not sure how to solve with insert() anyways its late so i'll be looking at it tomorrow, thanks for the help! – Nathan Kot Oct 12 '12 at 11:36
  • @Nathan Kot: oh, come on! It's in the example #1 `$collection->insert($a); var_dump($a['_id']);` – zerkms Oct 12 '12 at 11:37
  • @zerkms hmmm okay now I think we're on a different page, save() actually also amends the _id (although no documented?) you can find this [here](http://stackoverflow.com/questions/4434009/get-mongodb-id-object-after-upsert-with-php) in the second answer, example#1 shows me that it wont amend references, so its no surprise that passing the referenced $entry wont work, but I'm not sure why passing as reference does amend the _id – Nathan Kot Oct 12 '12 at 11:51
  • @Nathan Kot: if you really keen - take the sources and see why's that. If I were you - I would be good with just working code (as soon as it's 1am there) ;-) – zerkms Oct 12 '12 at 11:54

1 Answers1

0

MongoCollection::save() and MongoCollection::insert() can both modify their argument by setting an _id key, although it doesn't seem to be documented for save() (I'll fix that soon). Internally, both methods are modifying the raw zval passed to the C functions. If I had to guess, this is because specifying the first argument as a reference would make it impossible to pass array literals. So, the extension cheats and modifies the argument anyway, with the side effect of being unable to modify something passed by reference.

I tested the following code, which seems to work around this at the expense of copying the array argument in your save method:

public function save(&$entry)
{
    if (empty($entry)) {
        return false;
    }

    if (!$this->checkSchema($entry)) {
        $this->throwDbError('Schema violation in `' . get_class($this) . '`');
    }

    try {
        $entryCopy = $entry;
        $saveResult = $this->collection->save($entryCopy);

        if (!isset($entry['_id']) && isset($entryCopy['_id']) {
            $entry['_id'] = $entryCopy['_id'];
        }

        return $saveResult; 
    } catch (Exception $e) {
        return $this->throwDbError($e);
    }
}

I suppose you could always copy the _id property back to $entry if you like. Alternatively, you could do with the array copying and simply initialize $entry[_id] to a new MongoId instance if it's not already set. That's essentially what the driver does for you when inserting a document without an _id.

jmikola
  • 6,892
  • 1
  • 31
  • 61
  • "this is because specifying the first argument as a reference would make it impossible to pass array literals" This seems like a crappy reason to forgo correctness – newacct Oct 12 '12 at 20:22
  • @jmikola thanks for your answer, this is the approach (although inelegant) that i ended up taking - but think i'll change it to your second suggestion where I initialise the MongoID manually – Nathan Kot Oct 12 '12 at 23:41