Avant de passer à PDO, j'avais créé des requêtes SQL dans PHP en concaténant des chaînes. Si je rencontrais une erreur de syntaxe de base de données, je pourrais simplement faire écho à la chaîne de requête SQL finale, essayez moi-même sur la base de données et Tweak jusqu'à ce que je corrige l'erreur, puis la réintroduire dans le code.
Les instructions 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. Lorsque je reçois des erreurs de syntaxe dans mon journal Apache ou mon fichier de journal personnalisé (je consigne les erreurs dans un bloc catch
,), je ne peux pas voir la requête qui les a provoquées.
Existe-t-il un moyen de capturer la requête SQL complète envoyée par PDO à la base de données et de la consigner dans un fichier?
Bien que Pascal MARTIN ait raison de dire que PDO n'envoie pas la requête complète à la base de données en une fois, la suggestion de ryeguy d'utiliser la fonction de journalisation de la base de données m'a permis de voir la requête complète telle qu'assemblée et exécutée par la base de données.
Voici comment: (Ces instructions concernent MySQL sur une machine Windows - votre kilométrage peut varier.)
my.ini
, Sous la section [mysqld]
, Ajoutez une commande log
, telle que log="C:\Program Files\MySQL\MySQL Server 5.1\data\mysql.log"
Ce fichier grossira rapidement. Veillez donc à le supprimer et à désactiver la journalisation lorsque vous avez terminé les tests.
Tu dis ça :
Je ne vois jamais la requête finale car elle est envoyée à la base de données
Eh bien, en fait, lorsque vous utilisez des instructions préparées, il n’existe pas de requête " finale ":
Donc, pour répondre à votre question :
Existe-t-il un moyen de capturer la requête SQL complète envoyée par PDO à la base de données et de la consigner dans un fichier?
Non: comme il n'y a pas de " requête SQL complète " où que ce soit, il n'y a aucun moyen de la capturer.
La meilleure chose à 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 l'instruction.
Ce que je fais habituellement, dans ce genre de situation, c'est:
var_dump
(ou un équivalent) juste après, pour afficher les valeurs des paramètresCe n’est pas formidable pour le débogage, mais c’est le prix des déclarations préparées et des avantages qu’elles apportent.
Bien sûr, vous pouvez déboguer en utilisant ce mode {{ PDO::ATTR_ERRMODE }}
Il suffit d’ajouter une nouvelle ligne avant votre requête pour afficher les lignes de débogage.
$db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
$db->query('SELECT *******');
Ce que vous voulez probablement faire est probablement d'utiliser debugDumParams () Il ne construit pas l'instruction préparée pour vous, mais affiche vos paramètres.
Un ancien post, mais peut-être que quelqu'un trouvera cela utile;
function pdo_sql_debug($sql,$placeholders){
foreach($placeholders as $k => $v){
$sql = preg_replace('/:'.$k.'/',"'".$v."'",$sql);
}
return $sql;
}
Voici une fonction pour voir quelle sera l'efficacité du code SQL, adaptée d'un commentaire de "Mark" à l'adresse 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;
}
Non, les requêtes PDO ne sont pas préparées côté client. PDO envoie simplement la requête SQL et les paramètres au serveur de base de données. Le base de données est ce que fait la substitution (du ?
'). Vous avez deux options:
presque rien n'a été dit sur l'affichage des erreurs, à l'exception des journaux d'erreurs de vérification, mais il existe 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());
}
?>
( lien source )
il est clair que ce code peut être modifié pour être utilisé comme message d'exception ou tout autre type de traitement d'erreur
par exemple, vous avez cette déclaration pdo:
$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;
Pour vous connecter à MySQL WAMP , vous devez éditer le fichier my.ini (par exemple, sous wamp\bin\mysql\mysql5.6.17\my.ini).
et ajouter à [mysqld]
:
general_log = 1
general_log_file="c:\\tmp\\mysql.log"
En cherchant sur Internet, j'ai trouvé cela comme une solution acceptable. Une classe différente est utilisée à la place de PDO et les fonctions PDO sont appelées via 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 fonctionnalité de journalisation sensible soit ajoutée à PDO.
Ainsi, conformément à ce thread , vous pouvez écrire un wrapper pour votre connexion PDO qui peut se connecter et lève 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;
}
}
vous pouvez donc utiliser cette classe à la place de PDOStatement:
$this->db->setAttribute (PDO::ATTR_STATEMENT_CLASS, array ('LoggedPDOStatement', array()));
Voici une implémentation de décorateur AOP mentionnée:
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);
}
}
Voici une fonction que j'ai créée pour renvoyer 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 ça
$values = array(1, 'SomeUsername');
$smth->execute($values);
Cette fonction n’ajoute pas de guillemets aux requêtes mais fait le travail à ma place.
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/référentiel moderne chargé par Composer pour exactement ceci ici:
Trouvez le projet accueil GitHub ici , voyez un article de blog l'expliquant ici . Une ligne à ajouter à votre composer.json, et vous pourrez l’utiliser comme ceci:
echo debugPDO($sql, $parameters);
$ sql est l'instruction SQL brute, $ parameters est un tableau de vos paramètres: La clé est le nom de l'espace réservé (": user_id") ou le numéro du paramètre non nommé ("?"), la valeur est .. ainsi, le valeur.
La logique derrière: Ce script va simplement graduer 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: Ceci est juste une émulation de base, pas 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 du fil de discussion StackOverflow Obtention de la chaîne de requête SQL brute de PDO pour écrire la fonction principale entière derrière ce script. Dédicace!
Le problème que j’avais avec la solution pour intercepter les exemptions de PDO à des fins de débogage est qu’il ne capturait que les exemptions de PDO (duh), mais ne détectait pas les erreurs de syntaxe enregistrées comme des erreurs php (je ne sais pas pourquoi, mais " pourquoi "n’est pas pertinent pour la solution). Tous mes appels PDO proviennent d'une classe de modèle de table unique que j'ai étendue pour toutes mes interactions avec toutes les tables ... cela compliquait les choses lorsque j'essayais de déboguer du code, car l'erreur enregistrerait la ligne de code php où se trouvait mon appel d'exécution. appelé, mais ne m’a pas dit où était l’appel. 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 intercepte les DEUX exceptions PDO ET les erreurs de syntaxe php et les traite de la même manière. Mon gestionnaire d'erreur ressemble à 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 manière d'obtenir des informations pertinentes pour mon gestionnaire d'erreurs que de définir le modèle de table en tant que variable globale, je serais heureux de l'entendre et de modifier mon code.
Dans l’environnement Debian NGINX, j’ai fait ce qui suit.
Aller à /etc/mysql/mysql.conf.d
modifier mysqld.cnf
si tu trouves log-error = /var/log/mysql/error.log
ajoutez les 2 lignes suivantes ci-dessous.
general_log_file = /var/log/mysql/mysql.log
general_log = 1
Pour voir les journaux goto /var/log/mysql
et tail -f mysql.log
N'oubliez pas de commenter ces lignes une fois que vous avez terminé le débogage si vous êtes dans un environnement de production delete mysql.log
car ce fichier journal va grandir rapidement et peut être énorme.
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
TL; DR Enregistrez toutes vos requêtes et réduisez le journal mysql.
Ces instructions concernent l’installation d’Ubuntu 14.04. Émettre la commande lsb_release -a
pour obtenir votre version. Votre installation peut être différente.
cd /etc/mysql
. Vous devriez voir un fichier nommé my.cnf
. C’est le fichier que nous allons changer.cat my.cnf | grep general_log
. Ceci filtre le my.cnf
fichier pour vous. Vous devriez voir deux entrées: #general_log_file = /var/log/mysql/mysql.log
&& #general_log = 1
.Sudo service mysql restart
.Sudo service nginx restart
.Bon travail! Vous êtes tous ensemble. Tout ce que vous avez à faire est d’afficher le fichier journal afin que vous puissiez voir en temps réel les requêtes PDO de votre application.
Entrez cette cmd tail -f /var/log/mysql/mysql.log
.
Votre sortie ressemblera à 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='[email protected]' LIMIT 5
75 Close stmt
75 Quit
73 Quit
Toutes les nouvelles requêtes créées par votre application s'afficheront automatiquement , tant que vous continuez à suivre le journal. Pour sortir de la queue, appuyez sur cmd/ctrl c
.
truncate --size 0 mysql.log
.Crier énorme à réponse de Nathan Long ci-dessus pour que l'inspecteur découvre ceci sur Ubuntu. Merci également à dikirill pour son commentaire sur le message de Nathan, qui m’a amené à cette solution.
Je vous aime stackoverflow!