Comment déboguer les requêtes de la base de données AOP?

avant de passer à PDO, j'ai créé des requêtes SQL en PHP en concaténant des chaînes. Si j'ai une erreur de syntaxe de base de données, je pourrais juste faire écho à la chaîne de requête SQL finale, l'essayer moi-même sur la base de données, et la modifier jusqu'à ce que je fixe l'erreur, puis remettre cela dans le code.

les déclarations PDO préparées sont plus rapides, meilleures et plus sûres, mais une chose me dérange: Je ne vois jamais la requête finale telle qu'elle est envoyée à la base de données. Quand je reçois des erreurs sur la syntaxe dans mon journal Apache ou mon fichier log personnalisé (j'enregistre les erreurs dans un bloc catch ), Je ne vois pas la requête qui les a causées.

est-il possible de capturer la requête SQL complète envoyée par AOP à la base de données et de l'enregistrer dans un fichier?

127
demandé sur Nathan Long 2010-03-09 20:43:23

17 réponses

vous dites ceci:

Je ne vois jamais la requête finale envoyé à la base de données

en fait, lorsqu'on utilise des déclarations préparées, il n'y a pas de requête finale " :

  • tout D'abord, une déclaration est envoyée à la DB, et préparée là
    • la base de données analyse la requête, et construit un représentation interne des TI
  • Et, lorsque vous liez des variables et exécutez l'instruction, seules les variables sont envoyées à la base de données
    • et la base de données "injecte" les valeurs dans sa représentation interne de la déclaration



Donc, pour répondre à votre question:

Est-il un moyen de capture l'complet Requête SQL envoyée par AOP à la base de données et l'enregistrer dans un fichier?

Pas : comme il n'y a pas de " requête SQL complète " n'importe où, il n'y a aucun moyen de le capturer.



La meilleure chose que vous pouvez faire, à des fins de débogage, est de "reconstruire" une requête SQL "réelle", en injectant les valeurs dans la chaîne SQL de la déclaration.

Ce Que Je habituellement, dans ce genre de situations, est:

  • echo le code SQL qui correspond à la déclaration, avec des espaces réservés
  • et utiliser var_dump (ou un équivalent) juste après, pour afficher les valeurs des paramètres
  • c'est généralement suffisant pour voir une erreur possible, même si vous n'avez pas de requête" réelle " que vous pouvez exécuter.

ce n'est pas super, quand il s'agit de déboguer -- mais c'est le prix des déclarations préparées et les avantages qu'elles apportent.

94
répondu Pascal MARTIN 2010-03-09 17:46:22

recherche dans le journal de la base de données

bien que Pascal MARTIN soit correct que PDO n'envoie pas la requête complète à la base de données tout d'un coup, ryeguy la suggestion d'utiliser la fonction de journalisation de la DB m'a en fait permis de voir la requête complète assemblée et exécutée par la base de données.

Voici comment: (Ces instructions sont pour MySQL sur une machine Windows - votre kilométrage peut varier)

  • dans my.ini , sous la rubrique [mysqld] , ajouter une commande log , comme log="C:\Program Files\MySQL\MySQL Server 5.1\data\mysql.log"
  • Redémarrez MySQL.
  • il va commencer à journaliser chaque requête dans ce fichier.

ce fichier va se développer rapidement, alors assurez-vous de le supprimer et désactiver la journalisation lorsque vous avez terminé les tests.

80
répondu Nathan Long 2010-03-09 23:06:44

sûr que vous pouvez déboguer en utilisant ce mode {{ PDO::ATTR_ERRMODE }} Juste ajouter une ligne avant votre requête puis vous montrera les lignes de débogage.

$db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
$db->query('SELECT *******');  
14
répondu Saud AlFadhli 2012-07-03 16:33:16

Un vieux post, mais peut-être que quelqu'un va trouver cela utile;

function pdo_sql_debug($sql,$placeholders){
    foreach($placeholders as $k => $v){
        $sql = preg_replace('/:'.$k.'/',"'".$v."'",$sql);
    }
    return $sql;
}
12
répondu dontaskagain 2012-04-10 10:59:08

probablement ce que vous voulez faire est d'utiliser debugDumParams () Il ne construit pas la déclaration préparée pour vous, mais il montrera vos paramètres.

12
répondu fijiaaron 2018-05-02 07:08:53

Pas de. Les requêtes AOP ne sont pas préparées du côté du client. PDO envoie simplement la requête SQL et les paramètres au serveur de base de données. La base de données est ce que la substitution (de la ? 's). Vous avez deux options:

  • utilisez la fonction de journalisation de votre DB (mais même dans ce cas, elle est normalement présentée sous la forme de deux énoncés distincts (c'est-à-dire "pas final") au moins avec Postgres)
  • produit la requête SQL et le les paramètres et le reconstituer vous-même
8
répondu ryeguy 2010-03-09 17:45:51

Voici une fonction pour voir ce que le SQL efficace sera, adpated d'un commentaire par "Mark" À php.net :

function sql_debug($sql_string, array $params = null) {
    if (!empty($params)) {
        $indexed = $params == array_values($params);
        foreach($params as $k=>$v) {
            if (is_object($v)) {
                if ($v instanceof \DateTime) $v = $v->format('Y-m-d H:i:s');
                else continue;
            }
            elseif (is_string($v)) $v="'$v'";
            elseif ($v === null) $v='NULL';
            elseif (is_array($v)) $v = implode(',', $v);

            if ($indexed) {
                $sql_string = preg_replace('/\?/', $v, $sql_string, 1);
            }
            else {
                if ($k[0] != ':') $k = ':'.$k; //add leading colon if it was left out
                $sql_string = str_replace($k,$v,$sql_string);
            }
        }
    }
    return $sql_string;
}
8
répondu Matt Browne 2015-03-30 13:23:51

presque rien n'a été dit à propos de l'affichage des erreurs, sauf vérifier les journaux d'erreurs, mais il y a une fonctionnalité plutôt utile:

<?php
/* Provoke an error -- bogus SQL syntax */
$stmt = $dbh->prepare('bogus sql');
if (!$stmt) {
    echo "\PDO::errorInfo():\n";
    print_r($dbh->errorInfo());
}
?>

( source du lien de )

il est clair que ce code peut être modifié pour être utilisé comme message d'exception ou tout autre type de traitement des erreurs

5
répondu Zippp 2013-03-28 14:53:19

par exemple, vous avez cette déclaration AOP:

$query="insert into tblTest (field1, field2, field3)
values (:val1, :val2, :val3)";
$res=$db->prepare($query);
$res->execute(array(
  ':val1'=>$val1,
  ':val2'=>$val2,
  ':val3'=>$val3,
));

maintenant vous pouvez obtenir la requête exécutée en définissant un tableau comme ceci:

$assoc=array(
  ':val1'=>$val1,
  ':val2'=>$val2,
  ':val3'=>$val3,
);
$exQuery=str_replace(array_keys($assoc), array_values($assoc), $query);
echo $exQuery;
3
répondu Alireza 2015-10-27 16:01:41

recherche sur internet j'ai trouvé cette solution acceptable. Une classe différente est utilisée à la place de PDO et les fonctions PDO sont appelées par des appels de fonctions magiques. Je ne suis pas sûr que cela crée de graves problèmes de performances. Mais il peut être utilisé jusqu'à ce qu'une fonction de journalisation sensible soit ajoutée à AOP.

ainsi , selon ce fil , vous pouvez écrire un wrapper pour votre connexion AOP qui peut se connecter et lancer une exception lorsque vous obtenez une erreur.

voici un exemple simple:

class LoggedPDOSTatement extends PDOStatement    {

function execute ($array)    {
    parent::execute ($array);
    $errors = parent::errorInfo();
    if ($errors[0] != '00000'):
        throw new Exception ($errors[2]);
    endif;
  }

}

ainsi vous pouvez utiliser cette classe au lieu de PDOStatement:

$this->db->setAttribute (PDO::ATTR_STATEMENT_CLASS, array ('LoggedPDOStatement', array()));

Voici une mention AOP décorator mise en œuvre:

class LoggedPDOStatement    {

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

function execute ($params = null)    {
    $result = $this->stmt->execute ($params); 
    if ($this->stmt->errorCode() != PDO::ERR_NONE):
        $errors = $this->stmt->errorInfo();
        $this->paint ($errors[2]);
    endif;
    return $result;
}

function bindValue ($key, $value)    {
    $this->values[$key] = $value;    
    return $this->stmt->bindValue ($key, $value);
}

function paint ($message = false)    {
    echo '<pre>';
    echo '<table cellpadding="5px">';
    echo '<tr><td colspan="2">Message: ' . $message . '</td></tr>';
    echo '<tr><td colspan="2">Query: ' . $this->stmt->queryString . '</td></tr>';
    if (count ($this->values) > 0):
    foreach ($this->values as $key => $value):
    echo '<tr><th align="left" style="background-color: #ccc;">' . $key . '</th><td>' . $value . '</td></tr>';
    endforeach;
    endif;
    echo '</table>';
    echo '</pre>';
}

function __call ($method, $params)    {
    return call_user_func_array (array ($this->stmt, $method), $params); 
}

}
2
répondu bkilinc 2016-03-17 17:47:11

pour se connecter MySQL dans WAMP , vous aurez besoin d'éditer le my.ini (par exemple sous wamp\bin\mysql\mysql5.6.17 \ my.ini) 151950920"

et ajouter à [mysqld] :

general_log = 1
general_log_file="c:\tmp\mysql.log"
2
répondu Spezi 2016-11-18 11:43:06

Voici une fonction que j'ai faite pour retourner une requête SQL avec des paramètres" résolus".

function paramToString($query, $parameters) {
    if(!empty($parameters)) {
        foreach($parameters as $key => $value) {
            preg_match('/(\?(?!=))/i', $query, $match, PREG_OFFSET_CAPTURE);
            $query = substr_replace($query, $value, $match[0][1], 1);
        }
    }
    return $query;
    $query = "SELECT email FROM table WHERE id = ? AND username = ?";
    $values = [1, 'Super'];

    echo paramToString($query, $values);

en supposant que vous exécutez comme ceci

$values = array(1, 'SomeUsername');
$smth->execute($values);

cette fonction n'ajoute pas de guillemets aux requêtes mais fait le travail pour moi.

1
répondu rezden 2017-07-22 03:32:47

Comment déboguer PDO mysql requêtes de base de données Ubuntu

TL;DR consigner l'ensemble de vos requêtes et de la queue mysql journal.

ces instructions sont pour mon installation D'Ubuntu 14.04. Lancez la commande lsb_release -a pour obtenir votre version. Votre installation pourrait être différente.

Activer la journalisation dans mysql

  1. Modifier les répertoires cd /etc/mysql . Vous devriez voir un fichier appelé my.cnf . C'est le fichier que nous allons changer.
  2. vérifiez que vous êtes au bon endroit en tapant cat my.cnf | grep general_log . Cela filtre le fichier my.cnf pour vous. Vous devriez voir deux entrées: #general_log_file = /var/log/mysql/mysql.log && #general_log = 1 .
  3. décommentez ces deux lignes et sauvegardez via l'éditeur de votre choix.
  4. Redémarrer mysql: sudo service mysql restart .
  5. vous pourriez avoir besoin de redémarrer votre serveur web aussi. (Je ne me souviens pas de la séquence que j'ai utilisée). Pour mon installation, c'est nginx: sudo service nginx restart .

Beau travail!! Vous êtes tous set. Maintenant, tout ce que vous avez à faire est de tail le fichier journal de sorte que vous pouvez voir les requêtes AOP que votre application fait en temps réel.

la Queue le journal pour voir vos requêtes

Inscrivez ce cmd tail -f /var/log/mysql/mysql.log .

votre sortie ressemblera à quelque chose comme ceci:

73 Connect  xyz@localhost on your_db
73 Query    SET NAMES utf8mb4
74 Connect  xyz@localhost on your_db
75 Connect  xyz@localhost on your_db
74 Quit 
75 Prepare  SELECT email FROM customer WHERE email=? LIMIT ?
75 Execute  SELECT email FROM customer WHERE email='a@b.co' LIMIT 5
75 Close stmt   
75 Quit 
73 Quit 

toutes les nouvelles requêtes de votre application s'afficheront automatiquement , aussi longtemps que vous continuez à filmer le journal. Pour sortir de la queue, appuyez sur cmd/ctrl c .

Notes

  1. attention: ce fichier journal peut devenir énorme. Je ne l'exécute que sur mon serveur dev.
  2. le fichier journal devient trop gros? Le tronquer. Cela signifie que le fichier reste, mais le contenu est supprimer. truncate --size 0 mysql.log .
  3. Cool que le fichier journal répertorie les connexions mysql. Je sais que l'un d'eux vient de mon héritage mysqli dont je suis en transition. La troisième vient de ma nouvelle connexion AOP. Cependant, vous ne savez pas où le deuxième viennent de. Si vous connaissez un moyen rapide de savoir, faites le moi savoir.

de Crédit et de grâce

grand cri à la réponse de Nathan Long au-dessus de pour le inspo pour comprendre cela sur Ubuntu. Aussi à dikirill pour son commentaire sur le billet de Nathan qui m'amène à cette solution.

l'Amour vous stackoverflow!

1
répondu elbowlobstercowstand 2017-11-21 20:15:54

le problème que j'ai eu avec la solution pour attraper les exemptions AOP pour des fins de débogage est qu'elle a seulement attrapé les exemptions AOP (duh), mais n'a pas attrapé les erreurs de syntaxe qui ont été enregistrées comme des erreurs de php (Je ne sais pas pourquoi c'est, mais" pourquoi " n'est pas pertinent à la solution). Tous mes appels AOP proviennent d'une classe de modèle de table que j'ai étendue pour toutes mes interactions avec toutes les tables... cela a compliqué les choses quand j'ai essayé de déboguer du code, parce que l'erreur enregistrerait la ligne de php d'où mon appel d'exécution a été appelé, mais ne m'a pas dit où l'appel a été, en fait, à partir d'. J'ai utilisé le code suivant pour résoudre ce problème:

/**
 * Executes a line of sql with PDO.
 * 
 * @param string $sql
 * @param array $params
 */
class TableModel{
    var $_db; //PDO connection
    var $_query; //PDO query

    function execute($sql, $params) { 
        //we're saving this as a global, so it's available to the error handler
        global $_tm;
        //setting these so they're available to the error handler as well
        $this->_sql = $sql;
        $this->_paramArray = $params;            

        $this->_db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $this->_query = $this->_db->prepare($sql);

        try {
            //set a custom error handler for pdo to catch any php errors
            set_error_handler('pdoErrorHandler');

            //save the table model object to make it available to the pdoErrorHandler
            $_tm = $this;
            $this->_query->execute($params);

            //now we restore the normal error handler
            restore_error_handler();
        } catch (Exception $ex) {
            pdoErrorHandler();
            return false;
        }            
    }
}

ainsi, le code ci-dessus saisit les deux exceptions PDO et les erreurs de syntaxe php et les traite de la même manière. Mon gestionnaire d'erreurs ressemble à quelque chose comme ceci:

function pdoErrorHandler() {
    //get all the stuff that we set in the table model
    global $_tm;
    $sql = $_tm->_sql;
    $params = $_tm->_params;
    $query = $tm->_query;

    $message = 'PDO error: ' . $sql . ' (' . implode(', ', $params) . ") \n";

    //get trace info, so we can know where the sql call originated from
    ob_start();
    debug_backtrace(); //I have a custom method here that parses debug backtrace, but this will work as well
    $trace = ob_get_clean();

    //log the error in a civilized manner
    error_log($message);

    if(admin(){
        //print error to screen based on your environment, logged in credentials, etc.
        print_r($message);
    }
}

si quelqu'un a de meilleures idées sur la façon d'obtenir des informations pertinentes à mon gestionnaire d'erreurs que de définir le modèle de table comme un variable globale, je serais heureux de l'entendre et de modifier mon code.

0
répondu Troy Knapp 2014-08-13 14:59:19

ce code fonctionne très bien pour moi:

echo str_replace(array_keys($data), array_values($data), $query->queryString);

n'oubliez pas de remplacer $data et $query par vos noms

0
répondu user3553866 2014-10-23 04:52:45

j'utilise cette classe pour déboguer PDO (avec Log4PHP )

<?php

/**
 * Extends PDO and logs all queries that are executed and how long
 * they take, including queries issued via prepared statements
 */
class LoggedPDO extends PDO
{

    public static $log = array();

    public function __construct($dsn, $username = null, $password = null, $options = null)
    {
        parent::__construct($dsn, $username, $password, $options);
    }

    public function query($query)
    {
        $result = parent::query($query);
        return $result;
    }

    /**
     * @return LoggedPDOStatement
     */
    public function prepare($statement, $options = NULL)
    {
        if (!$options) {
            $options = array();
        }
        return new \LoggedPDOStatement(parent::prepare($statement, $options));
    }
}

/**
 * PDOStatement decorator that logs when a PDOStatement is
 * executed, and the time it took to run
 * @see LoggedPDO
 */
class LoggedPDOStatement
{

    /**
     * The PDOStatement we decorate
     */
    private $statement;
    protected $_debugValues = null;

    public function __construct(PDOStatement $statement)
    {
        $this->statement = $statement;
    }

    public function getLogger()
    {
        return \Logger::getLogger('PDO sql');
    }

    /**
     * When execute is called record the time it takes and
     * then log the query
     * @return PDO result set
     */
    public function execute(array $params = array())
    {
        $start = microtime(true);
        if (empty($params)) {
            $result = $this->statement->execute();
        } else {
            foreach ($params as $key => $value) {
                $this->_debugValues[$key] = $value;
            }
            $result = $this->statement->execute($params);
        }

        $this->getLogger()->debug($this->_debugQuery());

        $time = microtime(true) - $start;
        $ar = (int) $this->statement->rowCount();
        $this->getLogger()->debug('Affected rows: ' . $ar . ' Query took: ' . round($time * 1000, 3) . ' ms');
        return $result;
    }

    public function bindValue($parameter, $value, $data_type = false)
    {
        $this->_debugValues[$parameter] = $value;
        return $this->statement->bindValue($parameter, $value, $data_type);
    }

    public function _debugQuery($replaced = true)
    {
        $q = $this->statement->queryString;

        if (!$replaced) {
            return $q;
        }

        return preg_replace_callback('/:([0-9a-z_]+)/i', array($this, '_debugReplace'), $q);
    }

    protected function _debugReplace($m)
    {
        $v = $this->_debugValues[$m[0]];

        if ($v === null) {
            return "NULL";
        }
        if (!is_numeric($v)) {
            $v = str_replace("'", "''", $v);
        }

        return "'" . $v . "'";
    }

    /**
     * Other than execute pass all other calls to the PDOStatement object
     * @param string $function_name
     * @param array $parameters arguments
     */
    public function __call($function_name, $parameters)
    {
        return call_user_func_array(array($this->statement, $function_name), $parameters);
    }
}
0
répondu Janos Szabo 2015-04-21 09:03:23

j'ai créé un projet / dépôt chargé de compositeur moderne pour exactement ceci ici:

aop-debug

trouver la page du projet GitHub home ici , voir un billet de blog l'expliquant ici . Une ligne à ajouter dans votre compositeur.json, et puis vous pouvez l'utiliser comme ceci:

echo debugPDO($sql, $parameters);

$ sql est la déclaration brute SQL, $parameters est un tableau de vos paramètres: la clé est le nom du conteneur ( ": user_id") ou le numéro du paramètre sans nom ("?"), la valeur est .. ainsi, la valeur.

la logique derrière: ce script va simplement grader les paramètres et les remplacer dans la chaîne SQL fournie. Super-simple, mais super-efficace pour 99% de vos cas d'utilisation. Note: il ne s'agit que d'une émulation de base, pas d'un vrai débogage PDO (car ce n'est pas possible car PHP envoie du SQL brut et des paramètres au serveur MySQL séparé).

Un grand merci à bigwebguy et Mike de la StackOverflow fil prise en raw chaîne de requête SQL à partir de PDO pour l'écriture, fondamentalement, l'ensemble de la fonction principale derrière ce script. Big up!

0
répondu Sliq 2017-05-23 12:34:22