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?
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.
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 commandelog
, commelog="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.
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 *******');
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;
}
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.
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
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;
}
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());
}
?>
il est clair que ce code peut être modifié pour être utilisé comme message d'exception ou tout autre type de traitement des erreurs
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;
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);
}
}
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"
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.
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
- Modifier les répertoires
cd /etc/mysql
. Vous devriez voir un fichier appelémy.cnf
. C'est le fichier que nous allons changer. - vérifiez que vous êtes au bon endroit en tapant
cat my.cnf | grep general_log
. Cela filtre le fichiermy.cnf
pour vous. Vous devriez voir deux entrées:#general_log_file = /var/log/mysql/mysql.log
&&#general_log = 1
. - décommentez ces deux lignes et sauvegardez via l'éditeur de votre choix.
- Redémarrer mysql:
sudo service mysql restart
. - 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
- attention: ce fichier journal peut devenir énorme. Je ne l'exécute que sur mon serveur dev.
- le fichier journal devient trop gros? Le tronquer. Cela signifie que le fichier reste, mais le contenu est supprimer.
truncate --size 0 mysql.log
. - 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!
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.
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
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);
}
}
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!