Extending Firebug Database Profiler

Well, as of late I’m a bit obsessed with logging, profiling and other utility tools which help me to see what I’m doing needlessly and how I can improve performance. In my previous posts, I had mentioned in keeping logs with FirePHP and database profiling with FirePHP. Now I got an idea that wouldn’t be great to see what the sql queries had returned just looking on the profiler log. By this way I would have checked whether or not the query was working right or wrong and other kind of cool stuff.


You may argue that this is not the job of a profiler. You should use something else to debug your sql queries but not your profiler. It can affect the execution time and other kind of things. But don’t worry that’s not the case. It won’t affect your profiling info at all. And by this way, you will able to add other kind of information on your profiling log. Also you could want to alter the default view of the profiling info. So we can also call this: How to alter the profiling table sent to the firebug console from firebug db profiler.

So First of all, we got to extend Zend_Db_Profiler_Firebug. We do not create everything from scratch but just change some of the functionality of the current class. We shall obtain a code like this:

class Profiler extends Zend_Db_Profiler_Firebug {
    /**
     *
     * @param string $label
     */
    public function  __construct($label) {
        parent::__construct($label);
    }

    /**
     *
     * @param boolean $enable
     */
    public function setEnabled($enable) {
        parent::setEnabled($enable);
        if ($enable) {
            $this->_message->setHeader(array('Time','Event','Parameters','Results'));
        }
    }

    /**
     *
     * @param ArrayObject $results
     */
    public function addResults($results = null) {
        $lastRowIndex = $this->_message->getRowCount()-1;
        $row = $this->_message->getLastRow();
        if (!is_null($results)) {
            $row[] = ($params=$results->getArrayCopy()) ? $params : null;
        } else {
            $row[] = null;
        }
        $this->_message->setRow($lastRowIndex, $row);
    }
}

As you see I’m not doing a lot of different things. I just changed the table header for the table to be created and I manipulated the row added by the queryEnd() function of my profiler. That doesn’t look that hard. The part you will get confused is why I’m changing the ArrayObject to an array. That’s because of the firephp table behavior. If the variable is sent as an array, it will be seen as a variable by firephp but if you send it as an ArrayObject, it will be seen as a simple text and you will miss the good syntax highlighting and an expanded “variable view” option.

So when we come to the query execution part, you should do something like this:

$profiler = new Profiler('My Database Query Logs');
$profiler->setEnabled(true);

function query($str, $type = Zend_Db_Profiler::SELECT) {
	if ($dbProfileEnabled) { //Check if the profiling enabled or not...
	    $queryId = $profiler->queryStart($str, $type);
	    $qp = $profiler->getQueryProfile($queryId);
	    if ($qp->hasEnded()) {
		$queryId = $profiler->queryClone($qp);
		$qp = $profiler->getQueryProfile($queryId);
	    }
	    $qp->start($queryId);
	    $q = mysql_query($str);
	    $profiler->queryEnd($queryId);
	} else {
	    $q = mysql_query($str);
	}
        //Do some error handling
        $results = $this->fetchResultsToArray($q);
        $profiler->addResults($results);
	//Return or do whatever you want
}

As you see I did not changed the profiling part. I haven’t done anything to that info and by this the timing will not be delayed by fetching operations. Once you fetched or decided what to add to that col you should just call the method we have created above. And will work like a charm.

But here comes the bad part of the great things. Some of the methods you use in your profiler class does not exist. Sadly, at the time where the Zend_Wildfire_Plugin_FirePhp_TableMessage was written no one had thought of to write set/get methods for it properties. Maybe they thought but decided that these values should not be manipulated once they are created. But I don’t think that’s the case, and it’s just lazyness that I can understand :) (If anyone who has knowledge about it, I appreciate if you inform me the reason too)

So the worst thing you have to patch Zend_Wildfire_Plugin_FirePhp_TableMessage. Just add these lines to the end of the file:

/**
     * Returns the row at the given index
     *
     * @param integer $i
     * @return array
     */
    public function getRow($i)
    {
        return $this->_rows[$i];
    }

    /**
     * Returns the last row of the table
     *
     * @return array
     */
    public function getLastRow()
    {
        return $this->_rows[count($this->_rows)-1];
    }

    /**
     * Sets the row on the given index to a new row
     *
     * @param integer $i
     * @param array $row
     */
    public function setRow($i, $row)
    {
        $this->_rows[$i] = $row;
    }

    /**
     * Returns the row count
     *
     * @return integer
     */
    public function getRowCount()
    {
        return count($this->_rows);
    }

You may ask why not to extend it and than use it, this way you will not need to enter a patch to the Zend Framework. Well that’s a case too. But I didn’t wanted it because I would like to see this kind of get/set methods on the table and I didn’t want to extend the base class just to write set/get. But you may prefer an approach like that too and than you will have to modify our previous profiler class and create a new class like tablemessage. So here what our tablemessage class will look like:

class TableMessage extends Zend_Wildfire_Plugin_FirePhp_TableMessage {
    public function  __construct($label) {
        parent::__construct($label);
    }
    /**
     * Returns the row at the given index
     *
     * @param integer $i
     * @return array
     */
    public function getRow($i)
    {
        return $this->_rows[$i];
    }

    /**
     * Returns the last row of the table
     *
     * @return array
     */
    public function getLastRow()
    {
        return $this->_rows[count($this->_rows)-1];
    }

    /**
     * Sets the row on the given index to a new row
     *
     * @param integer $i
     * @param array $row
     */
    public function setRow($i, $row)
    {
        $this->_rows[$i] = $row;
    }

    /**
     * Returns the row count
     *
     * @return integer
     */
    public function getRowCount()
    {
        return count($this->_rows);
    }
}

You see I just added the set/get methods I had added to the core Zend class. Nothing more. Now I have to edit my profiler class’ setEnabled() and that’s how it will look like:

public function setEnabled($enable) {
        parent::setEnabled($enable);
        if ($enable) {
            $this->_message = new TableMessage($this->label);
            $this->_message->setBuffered(true);
            $this->_message->setHeader(array('Time','Event','Parameters','Results'));
            $this->_message->setDestroy(true);
            Zend_Wildfire_Plugin_FirePhp::getInstance()->send($this->_message);
        }
    }

You see that instead of using Wildfire’s tableMessage I use my own. If you open up the Firebug Profiler you will notice a similar code there too (Actually I copy pasted this from there). The only difference is that I use my own tablemessage instead of Zend’s tablemessage.

I recommend you using the second option as you don’t have to patch Zend Framework. You could use this information on manipulating this table as many ways you want. You could add a query type column on the table or some other information like last_insert_id or maybe you want to add which class called this method. And of course not just for db profiling but you can also use the Zend_Wildfire_Plugin_FirePhp_TableMessage to create a table like log which you may need to modify the rows.

Update: I had opened an issue on ZF tracker about these methods, and thanks to Christoph Dorn, they are added to the class with some minor changes and better error handling (like array index out of bounds controls etc.).