1. Objet DBQuery
Désormais, notre objet DBQuery émule simplement une procédure stockée - une fois exécuté, il renvoie une ressource de résultat qui doit être enregistrée et si vous souhaitez utiliser des fonctions sur le jeu de résultats (telles que num_rows() ou fetch_row() ) ), vous devez passer l'objet MySqlDB. Alors, quel est l'effet si l'objet DBQuery implémente les fonctions implémentées par l'objet MySqlDB (qui est conçu pour fonctionner sur les résultats d'une requête exécutée) ? Continuons à utiliser le code de l'exemple précédent ; et supposons que nos ressources de résultats sont désormais gérées par un objet DBQuery. Le code source de la classe DBQuery est présenté dans le listing 1.
Listing 1. Utilisation de la classe DBQuery.
nécessite 'mysql_db.php' ;
require_once 'query.php';
$db = nouveau MySqlDb ;
$db->connect('hôte', 'nom d'utilisateur', 'pass');
$db->query('utilisez content_management_system');
$query = nouveau DBQuery($db);
$query->prepare('SELECT fname,sname FROM utilisateurs WHERE username=:1S AND pword=:2S AND expire_time<:3I');
essayer {
if($query->execute("visualad", "apron", time()))->num_rows() == 1) {
echo('Corriger les informations d'identification');
} autre {
echo('Identifiants incorrects / Session expirée');
}
} catch (QueryException $e) {
echo('Erreur lors de l'exécution de la requête : ' . $e);
}
Ce qui nous intéresse le plus dans le code modifié ci-dessus, ce sont l'instruction catch et l'instruction d'exécution.
· L'instruction d'exécution ne renvoie plus de ressource de résultat, elle renvoie désormais l'objet DBQuery lui-même.
· L'objet DBQuery implémente désormais la fonction num_rows(), que nous connaissons déjà depuis l'interface DB.
· Si l'exécution de la requête échoue, elle lève une exception de type QueryException. Lorsqu'il est converti en chaîne, il renvoie les détails de l'erreur survenue.
Pour ce faire, vous devez utiliser un proxy. En fait, vous utilisez déjà des proxys dans notre objet DBQuery, mais vous allez maintenant l'utiliser plus en profondeur pour le lier étroitement à l'objet MySqlDB. L'objet DBQuery a été initialisé avec un objet qui implémente l'interface DB et il contient déjà une fonction membre exécuter, qui appelle la méthode query() de l'objet DB pour exécuter la requête. L'objet DBQuery lui-même n'interroge pas réellement la base de données, il laisse cette tâche à l'objet DB. Il s'agit d'un proxy, qui est un processus par lequel un objet peut implémenter un comportement spécifique en envoyant des messages à un autre objet qui implémente un comportement identique ou similaire.
Pour ce faire, vous devez modifier l'objet DBQuery pour inclure toutes les fonctions qui opèrent sur une ressource de résultat de l'objet DB. Vous devez utiliser les résultats stockés lors de l'exécution d'une requête pour appeler la fonction correspondante de l'objet DB et renvoyer ses résultats. Les fonctions suivantes seront ajoutées :
Listing 2 : Extension de la classe DBQuery à l'aide de proxys.
classeDBQuery
{
.....
fonction publique fetch_array()
{
if (! is_resource($this->result)) {
throw new Exception('Requête non exécutée.');
}
return $this->db->fetch_array($this->result);
}
fonction publique fetch_row()
{
if (! is_resource($this->result)) {
throw new Exception('Requête non exécutée.');
}
return $this->db->fetch_row($this->result);
}
fonction publique fetch_assoc()
{
if (! is_resource($this->result)) {
throw new Exception('Requête non exécutée.');
}
return $this->db->fetch_assoc($this->result);
}
fonction publique fetch_object()
{
if (! is_resource($this->result)) {
throw new Exception('Requête non exécutée.');
}
return $this->db->fetch_object($this->result);
}
fonction publique num_rows()
{
if (! is_resource($this->result)) {
throw new Exception('Requête non exécutée.');
}
return $this->db->num_rows($this->result);
}
}
L'implémentation de chaque fonction est assez simple. Il vérifie d'abord que la requête a été exécutée, puis délègue la tâche à l'objet DB, renvoyant ses résultats comme s'il s'agissait de l'objet requête lui-même (appelé fonction de base de données de base).
2. Tapez Hinting
Pour que le proxy fonctionne, nous devons nous assurer que la variable $db de l'objet DBQuery est une instance d'un objet qui implémente l'interface DB. Les indices de type sont une nouvelle fonctionnalité de PHP 5 qui vous permet de contraindre les paramètres de fonction en objets d'un type spécifique. Avant PHP 5, le seul moyen de garantir qu'un paramètre de fonction était un type d'objet spécifique était d'utiliser la fonction de vérification de type fournie en PHP (c'est-à-dire is_a()). Désormais, vous pouvez simplement convertir un type d'objet en préfixant le paramètre de fonction avec le nom du type. Vous avez déjà vu les astuces de type de notre objet DBQuery, qui garantissent qu'un objet implémentant l'interface DB est transmis au constructeur d'objet.
fonction publique __construct(DB $db)
{
$this->db = $db;
}
Lorsque vous utilisez des astuces de type, vous pouvez spécifier non seulement des types d'objets, mais également des classes abstraites et des interfaces.
3. Lancez des exceptions
Vous avez peut-être remarqué dans le code ci-dessus que ce que vous interceptez est une exception appelée QueryException (nous implémenterons cet objet plus tard). Une exception est similaire à une erreur, mais de manière plus générale. La meilleure façon de décrire une exception est d'utiliser l'urgence. Même si une situation d'urgence n'est pas « fatale », elle doit quand même être gérée. Lorsqu'une exception est levée en PHP, la portée actuelle de l'exécution est rapidement terminée, qu'il s'agisse d'une fonction, d'un bloc try..catch ou du script lui-même. L'exception parcourt ensuite la pile d'appels (terminant chaque portée d'exécution) jusqu'à ce qu'elle soit interceptée dans un bloc try..catch ou qu'elle atteigne le sommet de la pile d'appels, auquel cas elle génère une erreur fatale.
La gestion des exceptions est une autre nouvelle fonctionnalité de PHP 5. Lorsqu'elle est utilisée conjointement avec la POO, elle permet d'obtenir un bon contrôle sur la gestion des erreurs et le reporting. Un bloc try..catch est un mécanisme important pour gérer les exceptions. Une fois interceptée, l'exécution du script continuera à partir de la ligne de code suivante où l'exception a été interceptée et gérée.
Si la requête échoue, vous devez modifier votre fonction d'exécution pour lever une exception. Vous lancerez un objet d'exception personnalisé appelé QueryException - l'objet DBQuery qui a provoqué l'erreur lui sera transmis.
Listing 3. Lève une exception.
/**
*Exécuter la requête en cours
*
* Exécuter la requête actuelle en remplaçant les points par les arguments fournis
* .
*
* @parameters : paramètres de requête mixtes $queryParams,...
* @return : Ressource A – référence décrivant la ressource sur laquelle la requête est exécutée.
*/
fonction publique exécuter ($queryParams = '')
{
//Par exemple : SELECT * FROM table WHERE nom=:1S AND type=:2I AND level=:3N
$args = func_get_args();
si ($this->stored_procedure) {
/*Appelez la fonction de compilation pour obtenir la requête*/
$query = call_user_func_array(array($this, 'compile'), $args);
} autre {
/*Une procédure stockée n'a pas été initialisée, elle est donc exécutée comme une requête standard*/
$ requête = $ requêteParams ;
}
$result = $this->db->query($query);
si (! $résultat) {
lancer une nouvelle QueryException ($ this);
}
$this->result = $result;
/* Remarquez comment maintenant nous renvoyons l'objet lui-même, ce qui nous permet d'appeler des fonctions membres à partir du résultat de retour de cette fonction */
renvoie $this ;
}
4. Utiliser l'héritage pour lancer des exceptions personnalisées
En PHP, vous pouvez lancer n'importe quel objet comme exception ; cependant, l'exception doit d'abord hériter de la classe d'exception intégrée à PHP. En créant votre propre exception personnalisée, vous pouvez enregistrer d'autres informations sur l'erreur, créer une entrée dans un fichier journal ou faire ce que vous voulez. Votre exception personnalisée effectuera les opérations suivantes :
· Enregistrez le message d'erreur de l'objet DB généré par la requête.
· Donnez des détails précis sur la ligne de code sur laquelle l'erreur de requête s'est produite en examinant la pile d'appels.
· Afficher les messages d'erreur et le texte de la requête, une fois convertis en chaîne.
Afin d'obtenir le message d'erreur et le texte de la requête, plusieurs modifications doivent être apportées à l'objet DBQuery.
1. Un nouvel attribut protégé (compiledQuery) doit être ajouté à la classe.
2. La fonction compile() met à jour la propriété compileQuery de la requête avec le texte de la requête.
3. Une fonction doit être ajoutée pour récupérer le texte de requête compilé.
4. Une fonction doit également être ajoutée : elle obtient l'objet DB actuel associé à l'objet DBQuery.
Listing 4. Lancez une exception.
classeDBQuery
{
/**
*Stockez la version compilée de la requête après avoir appelé compile() ouexecute()*
* @var chaîne $compiledQuery
*/
protégé $compiledQuery ;
/**
* Renvoie la requête compilée sans l'exécuter.
* @parameters : $params mixtes,...paramètres de requête* @return : string—requête compilée*/
compilation de fonction publique ($params ='')
{
si (! $this->stored_procedure) {
throw new Exception("La procédure stockée n'a pas été initialisée.");
}
/*remplacement des paramètres*/
$params = func_get_args(); //Obtenir les paramètres de la fonction $query = preg_replace("/(?compile_callback($params, 1, "2")', $this->query);
return ($this->compiledQuery = $this->add_strings($query)); //Remettez la chaîne dans la requête}
fonction publique getDB()
{
retourner $this->db ;
}
fonction publique getCompiledQuery()
{
return $this->compiledQuery ;
}
}
Vous pouvez désormais implémenter la classe QueryException. Remarquez comment vous parcourez la pile d'appels pour trouver l'emplacement dans le script qui a réellement provoqué l'erreur. C'est exactement le cas lorsque l'objet DBQuery qui lève l'exception est une sous-classe qui hérite de l'objet DBQuery.
Listing 5 : classe QueryException.
/**
*Exception à la requête
*
*Lorsque vous essayez d'exécuter une requête, si une erreur se produit, une erreur sera renvoyée par l'objet {@link DBQuery}
*/
la classe QueryException étend l'exception
{
/**
*Texte de la requête*
* @var chaîne $QueryText ;
*/
protégé $QueryText ;
/**
*Numéro/code d'erreur de la base de données*
* @var chaîne $ErrorCode
*/
protégé $ErrorNumber ;
/**
*Message d'erreur de la base de données*
* @var chaîne $ErrorMessage
*/
protégé $ErrorMessage ;
/**
*Constructeur de classe*
* @Parameter : DBQuery $db, qui est l'objet de requête qui lève l'exception */
fonction publique __construct(DBQuery $query)
{
/*Récupérer la pile d'appels*/
$backtrace = $this->GetTrace();
/*Définit la ligne et le fichier à l'emplacement où l'erreur s'est réellement produite*/
si (compte($backtrace) > 0) {
$x = 1 ;
/*Si la classe de requête est héritée, alors nous devons ignorer les appels effectués par les sous-classes*/
while((! isset($backtrace[$x]['line'])) ||
(isset($backtrace[$x]['class']) && is_subclass_of($backtrace[$x]['class'], 'DBQuery')) ||
(strpos(strtolower (@$backtrace[$x]['function']), 'call_user_func')) !== false ) {
/*Exécution en boucle, tant qu'il n'y a pas de numéro de ligne ou que la fonction appelée est une sous-classe de la classe DBQuery*/
++$x;
/*Si on atteint le bas de la pile, alors on utilise le premier appelant*/
si (($x) >= compte($backtrace)) {
$x = compte ($backtrace);
casser;
}
}
/*Si la boucle ci-dessus s'exécute au moins une fois, alors nous pouvons la décrémenter de 1 pour trouver la ligne de code qui a provoqué l'erreur*/
si ($x != 1) {
$x -= 1 ;
}
/*Enfin, nous pouvons définir les numéros de fichier et de ligne, qui doivent refléter l'instruction SQL qui a provoqué l'erreur*/
$this->line = $backtrace[$x]['line'];
$this->file = $backtrace[$x]['file'];
}
$this->QueryText = $query->getCompiledQuery();
$this->ErrorNumber = $query->getDB()->errno();
$this->ErrorMessage = $query->getDB()->error();
/*Appelle le constructeur d'exception de la superclasse*/
parent::__construct('Erreur de requête', 0);
}
/**
*obtenir le texte de la requête*
* Texte de requête de chaîne @return */
fonction publique GetQueryText()
{
retourner $this->QueryText ;
}
/**
*j'ai le numéro d'erreur*
* @numéro d'erreur de chaîne de retour */
fonction publique GetErrorNumber()
{
return $this->ErrorNumber ;
}
/**
*j'ai reçu un message d'erreur*
* Message d'erreur de chaîne @return */
fonction publique GetErrorMessage()
{
return $this->ErrorMessage ;
}
/**
* Appelé lorsque l'objet est converti en chaîne.
* @chaîne de retour */
fonction publique __toString()
{
$output = "Erreur de requête dans {$this->file} sur la ligne {$this->line}nn" ;
$output .= "Requête : {$this->QueryText}n" ;
$output .= "Erreur : {$this->ErrorMessage} ({$this->ErrorNumber})nn" ;
return $output ;
}
}
Maintenant, le code que vous avez vu au début de cette section fonctionne.
5. Conclusion
Dans cet article, vous avez vu comment l'agent mappe l'interface de base de données associée à la requête aux opérations sur un résultat de requête spécifique. Les objets DBQuery exposent les mêmes fonctions, telles que fetch_assoc(), que les objets DB. Cependant, tout cela fonctionne pour une seule requête. Vous avez également appris à utiliser des exceptions personnalisées pour fournir des informations détaillées sur le moment et l'endroit où une erreur s'est produite, et comment mieux contrôler la gestion des erreurs.