Debugging MongoDB Problems In PHP

UPDATE April 9, 2013 — Fork me on github

Coming from the MySQL world, MongoDB presented some challenges to my workflow. With MySQL I usually would create a class or at least a function to pass my queries to so that if I had SQL problems, I had a single place to go and put a print statement to see the exact query I was trying to run.

It also gave me a single place to put a try/catch block with a debug_print_backtrace to figure out where the offending query was coming from. In my admitedly brief experience with MongoDB so far, Mongo doesn’t lend itself to such practices quite as easily.

With this project I’ve been working on at work we’ve got 34,000 lines of code. We have a global $mongo object that we run queries against. When a Mongo query chokes up our site I need to know what exact call has been made, with what arguments and coming from where.

So I wrote this shim, MongoLogger that would pretends to be Mongo. During development I replace the $mongo object with my MongoLogger object, like so:

$mongodb = new Mongo("mongodb://un:pw@host/db"); // Connect to Mongo
$mongo = $mongodb->db;                   // pick your database
$mongo = new MongoLogger($mongo); // Replace the $mongo

MongoLogger then uses the PHP magic methods __call and __get and the almost magic method call_user_func_array to do the needed logging, and to pass the arguments on to the real $mongo object.

Note: Magic Methods, an call_user_func_array in particular, are slow. Not recommended for production use!

This is simpler than what I wrote for work, but the concept is the same and the important bits are here. It’s what I wish I could’ve found when I first started worrying about how to debug MongoDB calls.

<?php

class MongoLogger{
    /**
     * @class MongoLogger
     *
     * @brief A debugging shim between your code and MongoDB. Should be disbaled for production
     *
     * Usage:
     * $mongodb = new Mongo("mongodb://un:pw@host/db"); // Connect to Mongo
     * $mongo = $mongodb->db;                           // pick your database
     * $mongo = new MongoLogger($mongo);                // and all calls made to the original $mongo just keep working...
     *
     * YAY MAGIC METHODS! http://php.net/manual/en/language.oop5.magic.php
     * Makes use of magic methods __construct, __call and __get and the almost magic method call_user_func_array.
     */

    var $collections = Array();

    function __construct($mongo){
    global $real_mongo;
    $real_mongo = $mongo;
    }

    public function __call($name,$args){
    global $real_mongo;

    error_log("Mongo->$name called with " . print_r($args,TRUE));

    try {
        $res = call_user_func_array(Array($real_mongo,$name),$args);
        return $res;
    } catch (Exception $e){
        debug_print_backtrace();
        error_log(print_r($e,TRUE));
        throw $e;
    }
    }

    /**
     * @brief Return a MongoLoggerCollection for the requested collection
     * This is the main function that gets called in this class. We just pretend that
     * we've got whatever we're asked for (just like Mongo does)
     *
     */
    public function __get($name){
    if(!array_key_exists($name,$this->collections)){ $this->collections[$name] = new MongoLoggerCollection($name); }
    return $this->collections[$name];
    }
}

class MongoLoggerCollection {
    /**
     * @class MongoLoggerCollection
     *
     * @brief Represents a collection in Mongo. Most of your calls will come through here.
     */
    var $collection;

    function __construct($name){
    $this->collection = $name;
    }

    public function __call($name,$args){
    global $real_mongo;

    // Logging!
    $debug = debug_backtrace();
    $caller = $debug[1];
    error_log($caller['file'] . ':' . $caller['line']);
    error_log("\$mongo->{$this->collection}->$name(");
    foreach($args as $arg){ error_log(print_r($arg,TRUE)); }
    error_log(")");

    try {
        $res = call_user_func_array(Array($real_mongo->{$this->collection},$name),$args);
        return $res;
    } catch (Exception $e){
        debug_print_backtrace();
        error_log(print_r($e,TRUE));
        throw $e;
    }
    }
}

This entry was posted in Computers, Programming and tagged , , , , , , , . Bookmark the permalink.

2 Responses to Debugging MongoDB Problems In PHP

  1. Additional tip for invoking Mongo collections/methods programatically.

    $conn = new Mongo(‘127.0.0.1’);
    $db = $conn->{DB_NAME}; // references the database from a previously defined constant
    $collection = ‘wp_options’;
    $method = ‘insert’;
    // some data
    $data = array(‘user’ => ‘myuser’,’pass’ => md5(‘568fh’));
    $ref = &$data; // note the pointer reference
    // this is equivalent to $db->wp_options->insert($ref);
    call_user_func_array(array($db->{$collection},$method), array($ref));

  2. Tip for taking a snapshot and replicate an existing MySQL database to MongoDB:

    $dbname = “mydbname”;
    $mongoconn = new Mongo(WP_MONGODB_IP);
    $db = $conn->{$dbname};

    $conn = mysql_connect(“localhost”, “mysql_user”, “mysql_password”);
    mysql_select_db($dbname);
    $result = mysql_query(“SHOW TABLES”);
    while ($row = mysql_fetch_assoc($result)) {
    $table = $row[‘Tables_in_’.$dbname];
    $res2 = mysql_query(“SELECT * FROM $table”);
    while ($row2 = mysql_fetch_assoc($res2)) {
    $ref = &$row2;
    $collection = $table; // only for clarity
    call_user_func_array(array($db->{$collection},’insert’), array($ref));
    }
    }
    mysql_close($conn);

Leave a Reply

Your email address will not be published. Required fields are marked *