views:

117

answers:

4

I stumbled upon this question from two years ago.

Is there a way to get the raw SQL string executed when calling PDOStatement::execute() on a prepared statement? For debugging purposes this would be extremely useful.

The winning answer states that

[...] You can also get what you want if you set the PDO attribute PDO::ATTR_EMULATE_PREPARES. In this mode, PDO interpolate parameters into the SQL query and sends the whole query when you execute().

But it doesn't mention how to get the resulting query string. I know it's a bad idea performance wise but that doesn't bother me in debug mode. Does anybody know how to do this?

PS If there is some way I could have reopened / drawn attention to the original two year old topic instead of opening a new one, please let me know.

+2  A: 

Afaik, PDO doesn't really expose it to you. On development servers, you could enable the general query log for MySQL (if that's what you use), with possibly more control with sql_log_off, which does require the SUPER privilege.

Wrikken
+1  A: 

If you can't get it from PDO itself, consider using a wrapper class just for PDOStatement::execute() which will log the SQL query and values, and then call execute() on the statement. You will have to refactor your code to use the new class.

As a sidenote, I see that PDOStatement has a class variable $queryString that holds the query being used. The values will have to be retrieved from whatever's passed into execute() or bindParam().

First some utility functions for logging:

//removes newlines and extra spaces from print_r output
function str_squeeze($str) {

    if (is_array($str)) {
        $str = print_r($str, true);
    }

    $str = preg_replace('/[(\r)?\n|\t]/', ' ', $str);
    $str = trim(ereg_replace(' +', ' ', $str));
    return $str;
}

function logger($str) {
    //log it somewhere
}

Option 1: wrapper class around PDOStatement

class My_PDO_Utils {

    public static function execute(PDOStatement &$stm, $values = array()) {
        logger("QUERY: " . $stm->queryString . ", values = " . str_squeeze($values)) ;
        return $stm->execute($values) ;

    }

}

Then your code will have to be:

$stm = $db->prepare("SELECT * FROM table2 WHERE id = ?") ;

$res = My_PDO_Utils::execute($stm, array(79)) ;

instead of

$res = $stm->execute(array(79)) ;

Thinking some more about it, you could take it one further:

Option 2: Extend PDO and PDOStatement

If you want to be adventurous you can extend PDOStatement to do the logging for you, and PDO to return your extended PDOStatement class. This will require the least possible refactoring, ie just change new PDO() to new MY_PDO(), but could get tricky in its implementation as you would need to explicitely define any PDOStatement functionality you need in MY_PDOStatement so it gets called properly.

class My_PDO extends PDO {

    public function prepare($sql, $options = array()) {

        //do normal call
        $stm = parent::prepare($sql, $options) ;

        //encapsulate it in your pdostatement wrapper
        $myStm = new My_PDOStatement() ;
        $myStm->stm = $stm ;

        return $myStm ;

    }

}

class My_PDOStatement extends PDOStatement {

    /**
     *
     * @var PDOStatement
     */
    public $stm ;

    public function execute($values) {

        logger("QUERY: " . $this->stm->queryString . ", values = " . str_squeeze($values)) ;
        return $this->stm->execute($values) ;

    }

    public function fetchAll($fetch_style = PDO::FETCH_BOTH, $column_index = 0, $ctor_args = array()) {
        return $this->stm->fetchAll($fetch_style, $column_index, $ctor_args) ;
    }


}

But now your code can be:

$db = new My_PDO($dsn, $user, $pass) ;

$stm = $db->prepare("SELECT * FROM table2 WHERE id = ?") ;

$res = $stm->execute(array(79)) ;    
$row = $stm->fetchAll() ;
Fanis
Thanks for all the effort! I'm doing something very similar at the moment by extending PDO and PDOStatement (there's a command you can use to tell PDO to use your statement class). As far as I can see, there's two good arguments against this approach: 1. You'd have to update your classes every time the PDO API changed slightly. 2. By recreating the prepared statements (which is really pretty complicated if you consider the various syntaxes, handling references etc.) you'd be duplicating the functionality of PDO when using PDO::ATTR_EMULATE_PREPARES
Michael Clerx
For my own needs I'm wrapping around PEAR::DB and MDB2 with static functions at the moment to do proper logging, among other things, but am evaluating PDO so this was a good enough excuse to play with that :) (1) is my main concern, needing to maintain the extended classes for all required functionality across upgrades. For (2), it shouldn't need to recreate any statement as it's only wrapping around PDOStatement, unless I'm misunderstanding this point.
Fanis
Well you can write queries with '?' syntax and ':var' syntax, and you can add values with bindValue() or bindParam() - one of which uses references. Finally, you can add parameters at the last moment by adding them to the execute function. It'd be quite a bit of work to capture all of this in your query logging function, and apparently PDO can do this already (if you use the emulated prepares functionality) so you'd be rewriting something difficult that's already being done for you at a lower level...
Michael Clerx
Ah I see what you mean. Right, yes. My dummy code assumes only `execute($values)` would be used, as I'm doing in my own db wrapper. Gotcha.
Fanis
A: 

you maybe want to use a database profiler or even easier, see what queries arrive at your mysql server ;)

zolex
+2  A: 

I believe this is mentioned in the original question that was reference in this one. However there is actually supposed to be a method for retrieving this data.

PDOStatement::debugDumpParams

However it isn't currently working as documented. There is a bug report and patch submitted for it here http://bugs.php.net/bug.php?id=52384 in case anyone is interested in voting on it. Until it's fixed it seems like you are left to use query logging or setting a custom statement class using the PDO::ATTR_STATEMENT_CLASS attribute.

Chris Gutierrez
Thanks! I'd given up hope on this one :)
Michael Clerx