[PHP5] ABSTRACTION BDD STYLE PDO AVEC ITÉRATEURS, TRANSACTIONS
FhX
Messages postés2350Date d'inscriptionmercredi 13 octobre 2004StatutMembreDernière intervention18 avril 2015
-
23 mai 2007 à 22:56
malalam
Messages postés10839Date d'inscriptionlundi 24 février 2003StatutMembreDernière intervention 2 mars 2010
-
29 janv. 2008 à 19:52
Cette discussion concerne un article du site. Pour la consulter dans son contexte d'origine, cliquez sur le lien ci-dessous.
malalam
Messages postés10839Date d'inscriptionlundi 24 février 2003StatutMembreDernière intervention 2 mars 201025 29 janv. 2008 à 19:52
@Caviar => je n'ai pas oublié hein...mais je suis entrain de modifier cette classe depuis quelques temps, j'aoute des fonctionnalités : support des procédures, support des requêtes renvoyant plusieurs jeux de résultats; etc. Du coup, j'attends de finaliser ça avant de faire un tuto complet et simple :-)
malalam
Messages postés10839Date d'inscriptionlundi 24 février 2003StatutMembreDernière intervention 2 mars 201025 18 janv. 2008 à 20:15
Hello Caviar,
ok...ce week-end, je te fais un autre fichier plus clair :-) D'autant plus que la classe a bcp évolué et pas le fichier d'exemples.
cs_caviar
Messages postés329Date d'inscriptionsamedi 4 janvier 2003StatutMembreDernière intervention29 mars 20152 18 janv. 2008 à 17:25
saluté ... euh comment dire ... tu parlais d'un fichier d'exemple pour utiliser ta classe ... disons que j'ai un peu du mal à comprendre son fonctionnement du haut de mon faible niveau mais j'aimerai bien comprendre comment l'utiliser ... sur des trucs de base hein ...
connexion à la base, select, update, gestion de transaction etc... mais là je nage un peu sans exemple quand même ...
@+
malalam
Messages postés10839Date d'inscriptionlundi 24 février 2003StatutMembreDernière intervention 2 mars 201025 7 janv. 2008 à 20:55
Ok je comprends mieux : tu es sur le même serveur DB avec la même connexion, donc par défaut, il reprend la même ressource, en effet, c'est logique. Je ne l'avais jamais remarqué parce que je ne l'ai jamais testé de cette manière : ou plutôt, utiliser la même ressource ne me pose pas de problème si je suis sur la même connexion. Et généralement, j'utilise ce multiton sur différents serveurs, ou/et avec différentes connexions.
Ok, la solution est de passer dans le tableau de configuration le booléen $new_link, en le mettant à false par défaut si il n'est pas renseigné. Comme les fonctions idoines.
Je ferai ça, très bonne remarque :-)
guill76
Messages postés193Date d'inscriptionmercredi 24 août 2005StatutMembreDernière intervention 3 juin 2016 7 janv. 2008 à 20:48
D'accord mais oublie la notion singleton/multiton point de vue POO.
pense plutot à 2 ressources de db mysql telle qu'elles sont implémentés pas PHP.
Pour moi tout se joue dans mysql_connect()
resource mysql_connect ([ string $server [, string $username [, string $password [, bool $new_link [, int $client_flags ]]]]] )
le param $new_link n'est pas défini dans ta classe donc le mysql_connect dès son premier appel, te récupéres la ressource existante si elle existe.
Chose qui ne va pas de paire avec la notion singleton multiton POO.
J'ai testé ce comportement indépendament de ta classe : Mais je peux t'assurer que ta classe le reprend :
Regarde bien le ressource_id generé par mysql_connect : c'est le même.
malalam
Messages postés10839Date d'inscriptionlundi 24 février 2003StatutMembreDernière intervention 2 mars 201025 7 janv. 2008 à 20:20
Non, tu as 3 possibilités :
- singleton de base en fonction du type de db.
- pas de singleton du tout, quoi qu'il se passe : on crée toujours une nouvelle instance (il manque à implémenter une méthode permettant de récupérer une instance créée plus tôt vu que je les stocke).
- singleton avec "espace de nom" : on passe un "espace de nom", et un type de db évidemment. Si l'usine trouve dans ses instances une instance avec le même espace de nom pour ce type de DB, elle le renvoie, sinon elle l'instancie, le stocke, et retourne l'instance.
Le paramètre est $mIsSingleton, il est à true par défaut (singleton "typé" db), si on le passe à false, pas de singleton du tout, si on passe une chaîne ou toute valeur scalaire, c'est le 3ème cas (la valeur passée étant "l'espace de nom").
Dans l'exemple que tu me donnes, je suis très surpris car chez moi cela fonctionne très bien. Et le code étant le suivant :
# self::$_instance[$saDBType] = new $saDBType ($aConConf, $aOptions);
# return self::$_instance[$saDBType];
j'avoue ne pas comprendre comment tu peux te retrouver avec le même objet au final...c'est très bizarre. Tu es sur quelle version de PHP, il y a peut-être une subtilité que je n'aurais pas remarquée sur une des versions de PHP (j'ai découvert il y a 2 jours ceci :
http://www.php.net/manual/en/language.oop5.late-static-bindings.php donc bon...il y a toujours moyen qu'on zappe une nouveauté ;-) ) (au passage, lisez, c'est une GROSSE nouveauté, TRES intéressante, parce que ce **tain de problème, je m'y suis heurté un paquet de fois...!).
Bref, j'aimerais creuser ton truc là, mais j'ai besoin de toi lol, vu que je ne le reproduis pas.
guill76
Messages postés193Date d'inscriptionmercredi 24 août 2005StatutMembreDernière intervention 3 juin 2016 7 janv. 2008 à 20:02
Ok mais je crois que si tu instancies 2 db de même type mais differentes ta classe se comporte comme un singleton même avec l'option multiton.
un exemple pour mieux me faire comprendre:
//exemple en multiton:
$instDb1=aDBFactory::getInstance('mysql',$configMysqlDb1,false);
print_r($instDb1);
//voir la propriété rlink
$instDb2=aDBFactory::getInstance('mysql',$configMysqlDb2,false);
print_r($instDb2);
//voir la propriété rlink
>tu constateras que $instDb1->rLink $instDb2->rLink donc ta classe doit se connecter avec le parametre true dans mysql_connect pour vraiment être un multiton
malalam
Messages postés10839Date d'inscriptionlundi 24 février 2003StatutMembreDernière intervention 2 mars 201025 5 janv. 2008 à 16:09
Avant c'était un singleton, c'est normal ;-) C'était intentionnel. Et j'ai en effet modifié l'usine depuis pour permettre 3 comportements distincts.
SQLite gère les transactions, et mysql 3...ça date sérieusement quand même. On pourrait oui mais très sncèrement je ne le ferai pas sur ma classe, ce serait quelques classes de plus pour pas grand chose.
guill76
Messages postés193Date d'inscriptionmercredi 24 août 2005StatutMembreDernière intervention 3 juin 2016 5 janv. 2008 à 13:54
Oh une autre remarque importante sauf erreur de ma part, pour la connexion:
l'autre jour, je me suis heurté à un problème en créant plusieurs instances de db (ce n'était pas avec cette classe mais j'ai l'impression qu'une multi instanciation peut causer le même problème ).
la fonction mysql_connect doit à tout prix utiliser le flag true ou false pour permettre la création d'une nouvelle ressource de connexion à MYSQL.
Donc il faut que ce soit paramétrable :la possibilité pour ta classe d'utiliser le multiton ne suffit pas dans ce cas..:)
guill76
Messages postés193Date d'inscriptionmercredi 24 août 2005StatutMembreDernière intervention 3 juin 2016 5 janv. 2008 à 13:30
ben SQLITE, mysql <v4, etc..
malalam
Messages postés10839Date d'inscriptionlundi 24 février 2003StatutMembreDernière intervention 2 mars 201025 4 janv. 2008 à 20:01
On pourrait effectivement faire ça. Mais en même temps, des bdd non transactionnelles...heu...tu penses auxquelles?
guill76
Messages postés193Date d'inscriptionmercredi 24 août 2005StatutMembreDernière intervention 3 juin 2016 4 janv. 2008 à 19:42
Oui, C'était bien là que je voulais en venir!!
FhX
Messages postés2350Date d'inscriptionmercredi 13 octobre 2004StatutMembreDernière intervention18 avril 20153 4 janv. 2008 à 00:41
Sinon pour les transactions, je sais qu'on peut contourner comme toi et FHX le dites, mais ce qui me gêne c'est que pour une classe de 30 methodes de transactions ça fait redéfinir 30 mèthodes à vide (bon là on est grosso modo à 5 methodes de transactions donc ça peut aller :)).
Dans ce cas :
abstract class db {
}
class Transactiondb extends db {
}
class NonTransactiondb extends db {
}
Et puis roule ma poule :)
malalam
Messages postés10839Date d'inscriptionlundi 24 février 2003StatutMembreDernière intervention 2 mars 201025 28 déc. 2007 à 19:38
Merci Marc :-)
Je viens de faire une très grosse mise à jour : aucune incompatibilité avec les versions précédentes (vous pôuvez conserver sans les modifier les codes utilisant cette classe), mais un tas de nouveautés et d'améliorations par contre :-)
marc1306
Messages postés115Date d'inscriptionsamedi 14 juin 2003StatutMembreDernière intervention31 décembre 2009 23 déc. 2007 à 19:15
Parfait quel beau travail !! peu de sources sont aussi récentes à jour et propre . thanks
guill76
Messages postés193Date d'inscriptionmercredi 24 août 2005StatutMembreDernière intervention 3 juin 2016 28 mai 2007 à 19:36
Voici le code qui boucle inifiment
$test aDBFactory::getInstance ('mysql', array ('HOST'>'localhost', 'LOGIN' => 'login', 'PWD' => 'pwd', 'DB' => 'wrongdb'));
Sinon pour les transactions, je sais qu'on peut contourner comme toi et FHX le dites, mais ce qui me gêne c'est que pour une classe de 30 methodes de transactions ça fait redéfinir 30 mèthodes à vide (bon là on est grosso modo à 5 methodes de transactions donc ça peut aller :)).
PS Malalam au passage : Si t'as l'occasion de rejeter un oeil à la source calendrier html que j'ai postée:
un avis d'expert en itérateurs me serait bien utile : j'ai fait une bonne Mise à jour.
malalam
Messages postés10839Date d'inscriptionlundi 24 février 2003StatutMembreDernière intervention 2 mars 201025 28 mai 2007 à 09:11
Guill76 => désolé, mais je ne reproduis pas le bug. Et je ne vois pas comment il est possible vu qu'aucune exception n'est lancée dans une boucle.
Sauf si tu as utilisé ma dernière fonction dans les exemples, où, là, c'est possible puisque c'est ujne fonction qui fait de la récursivité en cas d'erreur. Auquel cas ce n'est pas un bug, mais une mauvaise utilisation de l'exemple ;-)
Peux-tu me montrer ton code ?
Pour les transactions, même remarque que FhX. Ca n'a pas réellement d'incidence.
Kankrelune : why not! Je vais la tester, merci :-)
FhX
Messages postés2350Date d'inscriptionmercredi 13 octobre 2004StatutMembreDernière intervention18 avril 20153 26 mai 2007 à 19:18
"J'aurais bien vu une implémentation de 2 types de Db (les transactionnelles et les autres) sinon ça oblige à implémenter les methodes de transaction même pour les bases non transactionnelles."
abstract class x {
abstract public function truc();
}
class y extends x {
abstract public function truc();
}
Rien ne t'empèche de ne rien mettre dedand :)
guill76
Messages postés193Date d'inscriptionmercredi 24 août 2005StatutMembreDernière intervention 3 juin 2016 26 mai 2007 à 14:13
Sinon une autre remarque importante pour ta classe :
Elle n'est malheureursement pas compatible avec les db non transactionnelles.
Toutes tes methodes transactionnelles sont abstraites :
J'aurais bien vu une implémentation de 2 types de Db (les transactionnelles et les autres) sinon ça oblige à implémenter les methodes de transaction même pour les bases non transactionnelles.
guill76
Messages postés193Date d'inscriptionmercredi 24 août 2005StatutMembreDernière intervention 3 juin 2016 26 mai 2007 à 13:58
Ouais, mais sur le nom de la base de données pas sur le type :
j'ai essayé avec mysql et une exception est lancée à priori dans un foreach:
pour t'aider j'ai ce message:
Connexion failed with message [Unknown database 'wrongdb']mysql :: select_db() : [0] Connexion failed with message [Unknown database 'wrongdb']
mysql :: select_db() : [0] Connexion failed with message [Unknown database 'wrongdb']
mysql :: select_db() : [0] Connexion failed with message [Unknown database 'wrongdb']
mysql :: select_db() : [0] Connexion failed with message [Unknown database 'wrongdb']
mysql :: select_db() : [0] Connexion failed with message [Unknown database 'wrongdb']
kankrelune
Messages postés1293Date d'inscriptionmardi 9 novembre 2004StatutMembreDernière intervention21 mai 2015 26 mai 2007 à 11:35
"Enfin, ok, je réflêchirai à une manière de passer la config dans une chaîne, façon DBI ou PDO :-)"
Vite fait comme ça, en reprenant l'exemple de coucou (sgdb://login:pass@host:port/db), je dirais un truc du genre...
malalam
Messages postés10839Date d'inscriptionlundi 24 février 2003StatutMembreDernière intervention 2 mars 201025 26 mai 2007 à 08:12
Coucou => par "traditionnelle", j'entends la façon que l'on voit le plus fréquemment dans les classes DB en PHP, à savoir un tableau de config ou des variables séparées host, login, pwd, db.
Mais évidemment, je biens d'accord sur le fait qu'il n'y a pas de règle générale pour ça.
PDO est calqué sur DBI, donc je vois très bien ce que tu veux dire. Oui c'est une excellente implémentation! Mais tout n'est pas très facile à simuler, notamment les marqueurs (avec les bindValue, bindParam, bindColumn et compagnie).Je n'ai pas non plus calqué le principe de la classe DBhandler ($dbh), et la classe Statementhandler ($sth), mais j'y songe aussi...
Enfin, ok, je réflêchirai à une manière de passer la config dans une chaîne, façon DBI ou PDO :-)
Codefalse => merci, déjà :-)
Généralement, en effet, on étend un tas d'exceptions spécialisées dans les classes du package que l'on crée. Bref, oui, c'est bien une volonté de ma part. Cela permet de gérer correctement les exceptions derrière avec différents catch. Ceci dit, rien n'empêche d'avoir aussi des exceptions plus génériques hein. Mais il faudrait alors les inclure dans tous les packages créés...autant la jouer spécialisé. Je vais les scinder un peu plus encore, c'est loin d'être suffisemment précis, en l'état, d'ailleurs.
En effet,le but est de ne lever les exceptions que si l'option EXCEPTION_ON_ERROR est active. Et pour la méthode aDB::interceptException(), tu as parfaitement raison. J'étais parti sur une tout autre façon de gérér cette méthode au départ, et l'utilisation de la réflexion est un reste malencontreux de ces essais :-) Je l'ai enlevée depuis.
Je corrigerai le commentaire, merci :-)
Guill76 => merci :-)
Par contre, je ne te suis pas sur l'erreur ? Tu tombes vraiment dans une boucle infinie quand tu entres un mauvais type de db dans l'usine? Je ne vois pas comment ?
guill76
Messages postés193Date d'inscriptionmercredi 24 août 2005StatutMembreDernière intervention 3 juin 2016 26 mai 2007 à 00:02
Nickel très grande classe.
great!!!
Just a little mistake=> "UNLIMITED LOOP" when using a wrong dbname in the factory method.
Sinon l'emploi des combinaisons pour le fetchall est pas mal: ça me fait penser au systeme du chmod sous unix.
codefalse
Messages postés1123Date d'inscriptionmardi 8 janvier 2002StatutModérateurDernière intervention21 avril 20091 25 mai 2007 à 10:28
Yop !
Alors deja, si on pouvait mettre 11 sur 10, je l'aurai fait !
Vraiment impressionné !
Bon, j'ai du relire 3 fois tes 1400 lignes histoire de bien tout comprendre (j'ai un peu de retard sur la poo ...) mais c'est bien rentré !
Par contre, quelques questions que je me doit de poser pour mieux comprendre :)
Ta classe d'exception (aDBException), se limite au sgbd. Est-ce une volontée de ta part pour cette partie ou est-ce que ca restera comme ca pour tout ton projet ?
Dites moi si je me trompe, mais ne serait-il pas plus interessant de faire une classe de gestion d'exception globale, et ensuite heriter les erreures comme tu le fait, mais spécialisé pour chaque usages ? (x classes héritant de l'exception globale pour la db, x classes héritant de l'exception globale pour la classe xxx, etc). Bon si c'est une volontée de ta part juste pour te limiter au db, ok :)
Par contre, dans ta classe abstraite aDB, tu utilise interceptException pour envoyer des exceptions.
L'utilité de cette fonction est juste pour la condition (EXCEPTION_ON_ERROR mode is active) alors on genere une nouvelle erreur ? J'ai bien compris ?
Dans cette fonction interceptException, pourquoi utilise tu l'usinage alors que ton appel envers la classe aDBException reste toujours pareil ?
car tu fait
# $e = new ReflectionClass($sExceptionClass);
# throw $e -> newInstance ($sErrorMsg, $iCode, $sClass, $sFunction);
ca pourrait être pareil que faire
# throw new $sExceptionClass ($sErrorMsg, $iCode, $sClass, $sFunction);
Je me trompe ? (c'est probable !! :p)
Sinon une petite note, dans le commentaire descriptif du constructeur de la classe class aDBException extends Exception, tu a oublié le parametre $iCode
(j'ai pas passé tous tes commentaires en revue, mais je trouvé celui la :p)
Voila voila !
coucou747
Messages postés12303Date d'inscriptionmardi 10 février 2004StatutMembreDernière intervention30 juillet 201244 25 mai 2007 à 08:55
//java
Class.forName("org.gjt.mm.mysql.Driver").newInstance();
connection=DriverManager.getConnection("jdbc:mysql://localhost/database","root","");
en perl :
$dbh = DBI->connect("DBI:mysql:database","root","");
en openoffice.org basic, j'ai pas le code sous les yeux (en fait je ne l'ai plus depuis un triste formatage) mais ca ressemblait au code java :)
a partir de ca, j'en conclue qu'il n'existe pas de facon traditionelle de se connecter a une BDD...
en perl, ca donne un truc comme ca :
$sth = $dbh->prepare("SELECT LAST_INSERT_ID()");
$sth->execute();
($id) =$sth->fetchrow();
$sth->finish();
et si t'as un parametre, tu mets un '?' dans la requette et une variable a execute alors perl est ok un vieux langage, mais tu vois, cette classe dbi, c'est geant...
sinon t'as
$rows_affected = $dbh->do("...") || die "Database Error: $DBI::errstr";
principale probleme du perl, j'ai rien vu au sujet des exeptions...
malalam
Messages postés10839Date d'inscriptionlundi 24 février 2003StatutMembreDernière intervention 2 mars 201025 25 mai 2007 à 08:23
Zou, allons au taf...:-(
malalam
Messages postés10839Date d'inscriptionlundi 24 février 2003StatutMembreDernière intervention 2 mars 201025 25 mai 2007 à 08:23
Kankrelune => Ok ok lol je me rends. De tte manière FhX a raison, je n'avais pas pensé au foreach, qui plante effectivement, en l'état.
je verrai ça dans la journée (je vais pas tarder à aller au taf là)
Coucou => Si si, j'ai dit Kankrelune parce que c'était le dernier message. Mais j'ai réflêchi à ce que tu m'as dit concernant plusieurs connexions et effectivement l'usinage était la meilleure solution pour ça. On doit même pouvoir pousser le concept, et je suis sûr que t'as plein d'idées pour ça ;-)
Pour la manière de se connecter, je laisserai peut-être le choix entre ce que tu proposes, et la manière "traditionnelle", pourquoi pas, oui. je n'ai pas eu le temps de me pencher dessus, c'est tout :-) Mais j'avais bien noté ce que tu disais.
Idem pour le bindValue : j'ai bien noté et c'est une très bonne idée (même si ça va causer des erreurs, mais ça...charge à l'utilisateure de faire attention à son typage!). Je vais l'implémenter. Mais j'ai passé pas mal de temps à bosser sur les exceptions, à corriger quelques bugs, et sur cet itérateur. Je pense avoir le temps aujourd'hui.
Toute suggestion est la bienvenue, vu que ce package, je vais l'utiliser, moi, alors autant qu'il soit sympa à utiliser et pratique ;-)
coucou747
Messages postés12303Date d'inscriptionmardi 10 février 2004StatutMembreDernière intervention30 juillet 201244 24 mai 2007 à 19:22
"J'ai fait pas mal d'ajouts cette fois : l'usinage avec multiton (comme quoi, Kankrelune...t'as raison, c'était une très bonne idée)" => usinage et factory c'est pas pareil ?
public function bindValue ($sNeedle, $mValue, $cType) {
t'es pas decide a mettre ce troisieme parametre optionel ?
kankrelune
Messages postés1293Date d'inscriptionmardi 9 novembre 2004StatutMembreDernière intervention21 mai 2015 24 mai 2007 à 18:35
Perso je suis comme FhX... je vois pas en quoi l'appel de next() avant un while() dérange... hormis une ligne de code en plus c'est comme initialiser une variable avant un while()... .. .
@ tchaOo°
FhX
Messages postés2350Date d'inscriptionmercredi 13 octobre 2004StatutMembreDernière intervention18 avril 20153 24 mai 2007 à 18:15
"ce que j'ai fait, c'est que j'appelle automatiquement sqlIterator::next() dans le constructeur."
Fait attention, car si tu utilises foreach(), regarde ce que cela fait :
--->instanciation itérateur
---> Appel de foreach() sur l'objet itérateur
| --> Rewind()
| --> next()
| --> valid()
---> current() + key().
Si tu mets next() dans ton constructeur, tu vas récupérer le premier enregistrement. Hors, sur un foreach(), rewind() est appelé en début de boucle, donc tu vas "perdre" le bénéfice du next() dans ton constructeur.
Tu as le choix dans ton rewind(), soit tu t'en sers, soit tu t'en sers pas. Dans le cas ou tu t'en sert (donc ==> mysql_seek(0)), tu vas effectivement perdre un appel de méthode. Si tu ne t'en sers pas, tu vas "sauter" un enregistrement.
Vala, un ptit rappel :p
malalam
Messages postés10839Date d'inscriptionlundi 24 février 2003StatutMembreDernière intervention 2 mars 201025 24 mai 2007 à 18:03
Ok ok, en effet, c'est plus joli de toute manière.
ce que j'ai fait, c'est que j'appelle automatiquement sqlIterator::next() dans le constructeur. Comme ça, pas la peine de l'appeler avant la boucle while () (ce qui n'est décidément pas naturel pour moi).
FhX
Messages postés2350Date d'inscriptionmercredi 13 octobre 2004StatutMembreDernière intervention18 avril 20153 24 mai 2007 à 17:26
Mince, j'ai dis ni bonjour, ni aurevoir, ni rien.....
Vala c'est fait :D
FhX
Messages postés2350Date d'inscriptionmercredi 13 octobre 2004StatutMembreDernière intervention18 avril 20153 24 mai 2007 à 17:26
"FhX => je n'ai tjrs pas pu réparer mon pc perso...mais je n'oublie pas ce que je t'ai promis (si t'en as tjrs besoin depuis le temps), dès que je le retrouve (au taf, je ne peux décemment pas lol)."
Oh, c'est quoi déja ?? ;)
"Ce qui m'a géné, dans ce cas, c'est que si je décide de traverser mon itérateur avec un while au lieu d'un foreach, je dois alors faire un next AVANT de faire un current...et pas à la fin de chaque boucle while comme habituellement. C'était trop étrange à mon goût..."
Ah vi, sauf qu'habituellement tu fais :
$i=0;
while ( $i < 100 } {
echo $i;
$i++;
}
donc, tu as posé le schéma type :
$iterateur = new iterateur(.....);
while ( $iterateur->valid() ) {
$contenu = $iterateur->current();
echo $contenu[...].$contenu[...];
$iterateur->next();
}
On voit bien ici que le ::current() doit retourner le contenu de l'itérateur "en fonction du compteur interne de l'itérateur". Ce compteur est incrémenté par ::next(). Hors, dans ta classe d'itération, c'est ::current() qui se charge de la récupération des données MAIS SANS prendre en compte le compteur interne.
Si j'utilise ta classe et que je fais :
$iterateur = new iterateur(.....);
while ( $iterateur->valid() ) {
$contenu = $iterateur->current();
echo $contenu[...].$contenu[...];
$contenu2 = $iterateur->current();
echo $contenu2[...].$contenu2[...];
$iterateur->next();
}
On voit bien que $contenu et $contenu2 contiennent des données différentes AU LIEU de contenir la même chose. Mais le ::next() en haut de la boucle te gène, où est donc le problème ? Regarde, c'est ici :
Et voui, j'ai initialisé le contenu de ma variable avant la boucle.
Hors chez toi :
$iterateur = new iterateur(.....);
// <======== la clé devrait valoir -1 (0 étant le premier enregistrement), et la propriété stockant la ressource courante est de type null.
while ( $iterateur->valid() ) {
$contenu = $iterateur->current();
echo $contenu[...].$contenu[...];
$iterateur->next();
}
Pour que cela fonctionne correctement, tu dois faire ceci :
$iterateur = new iterateur(.....);
$iterateur->next();
while ( $iterateur->valid() ) {
$contenu = $iterateur->current();
echo $contenu[...].$contenu[...];
$iterateur->next();
}
Cela implique à ta classe d'itération quelques changements que voici : (c'est un schéma type)
public function __construct($query) {
$this->key = -1;
$this->query = $query;
$this->output = null;
}
public function valid() {
return ( isset($this->output) || false !== $this->output);
}
public function next() {
$this->key++;
$this->output = mysql_fetch_****($query);
}
public function current() {
return $this->output;
}
//etc...
}
Cette classe marche ensuite comme ceci :
// foreach
foreach ($it as $key=>$val) {
// $val sortant de ::current();
// next étant exécuté à chaque itération de boucle
}
// while
$it->next();
while ( $it->valid() ) {
echo $it->current();
echo $it->current();
$it->next();
}
Vala vala :)
Pour l'abstraction, je vais faire un ptit truc ce week end. Je te filerai ca exclusivement :p
malalam
Messages postés10839Date d'inscriptionlundi 24 février 2003StatutMembreDernière intervention 2 mars 201025 24 mai 2007 à 15:16
Merci :-)
J'ai fait pas mal d'ajouts cette fois : l'usinage avec multiton (comme quoi, Kankrelune...t'as raison, c'était une très bonne idée), les différentes exceptions...et des exemples pour utiliser les différentes exceptions justement.
reste tjrs à bosser sur l'abstraction :-)
kankrelune
Messages postés1293Date d'inscriptionmardi 9 novembre 2004StatutMembreDernière intervention21 mai 2015 24 mai 2007 à 15:07
Wouaw... que dire... beau boulot... .. .
Je n'ai pas eu le temps de regarder le code en détail je ne le commenterais donc pas mais de ce que j'ai vu c'est plus que propre... .. .
Pour commenter les commentaires (lol) précédant je pense que l'idée d'une factory est plutot bonne... on a pas souvent besoin de se connecter à plusieurs serveur mais ça peut arriver et dans ce cas une factory simplifie quand même les choses... dans la même idée j'opterais plus pour un multiton qu'un singleton... ça permet des connections à différents serveur mais également à différentes bases de données... .. .
"(Moi je place ca dans mes signets, voir si ca evolue, si ca evolue bien, je veux dire avec les differentes exeptions, et autres ameliorations, je dois dire que j'aurais du mal a faire mieux... du moins en partant de 0 ca serait trop long, alors pourquoi ne pas utiliser ca si un jours j'ai une grosse usine a mettre en place...)"
+1
10/10... .. . ;o)
@ tchaOo°
coucou747
Messages postés12303Date d'inscriptionmardi 10 février 2004StatutMembreDernière intervention30 juillet 201244 24 mai 2007 à 08:42
je veux me connecter a odbc, mysql, acces avec le meme objet... c'est pas possible avec ton code, c'est la ou factory entre en scene... (imagine, tu fais un gestionnaire de forum, et tu veux qu'il puisse gerer tout type de BDD juste en changeant un fichier de conf, le fichier de conf ne contenant que des define et autre...) voila ce qui peut te motiver a faire une factory...
(Moi je place ca dans mes signets, voir si ca evolue, si ca evolue bien, je veux dire avec les differentes exeptions, et autres ameliorations, je dois dire que j'aurais du mal a faire mieux... du moins en partant de 0 ca serait trop long, alors pourquoi ne pas utiliser ca si un jours j'ai une grosse usine a mettre en place...)
malalam
Messages postés10839Date d'inscriptionlundi 24 février 2003StatutMembreDernière intervention 2 mars 201025 24 mai 2007 à 08:10
Hello,
déjà, merci à tous les deux :-)
Maintenant, je vais tâcher d'expliquer quelques points car n'ayant pas fait de doc encore, certaines fonctionnalités restent obscures je pense :-)).
FhX => je n'ai tjrs pas pu réparer mon pc perso...mais je n'oublie pas ce que je t'ai promis (si t'en as tjrs besoin depuis le temps), dès que je le retrouve (au taf, je ne peux décemment pas lol).
L'itérateur : il fonctionne très bien. Je comprends ta réticence, mais j'ai testé les deux façons de faire : l'actuelle, et faire le fetch dans le next, le stocker dans une propriété, et renvoyer cette propriété dans le current. Ce qui m'a géné, dans ce cas, c'est que si je décide de traverser mon itérateur avec un while au lieu d'un foreach, je dois alors faire un next AVANT de faire un current...et pas à la fin de chaque boucle while comme habituellement. C'était trop étrange à mon goût...
Extract : attention...ça n'a pas de rapport direct avec l'itérateur. C'est juste un paramètre, le aDB::FETCH_EXTRACT qui permet de récupérer les valeurs de cette manière. Mais on peut aussi fonctionner "normalement" via aDB::BOTH, aDB::FETCH_ASSOC etc...et retrouver un tableau associatif, numérique, les deux...Par contre, c'est dangereuyx en effet, et à utiliser en connaissance de cause. Comme un extract (), ou le PDOStatement::bindColumn() avec PDO::FETCH_BOUND.
Pour ta notion d'abstraction, c'est effectivement une jolie idée...je vais me pencher dessus.
Coucou => Tu ne te trompes pas pour les exceptions, il faudrait que je répartisse un peu tout ça, en effet, et que j'appelle différents types d'exceptions en fonction des erreurs trouvées.
Le cache, j'y pense. Le bench, je ne suis pas pour, je préfère utiliser ça indépendemment de toute classe : comme ça, j'ai une classe bench, que j'utilise où je le veux. Du coup, en ajouter une à ces classes les alourdirai inutilement.
Selon la méthode utilisée, la classe renvoie un tableau (un vrai lol, c'est aDB::fetchAll()) ou un itérateur (un objet donc, c'est aDB::fetch), ou même une simple valeur (aDB::fetchColumn).
L'usinage, je n'en voyais pas non plus l'intérêt dans ma classe : je ne veux pas de singleton (j'utilise souvent plusieurs objets db distincts). Ceci dit, on ne change pas de méthode pour se connecter à des bases différentes là ? Ce sera tjrs la même méthode et les mêmes paramètres à passer.
Mais je vais quand même revenir sur cette histoire d'usine...je pense. On verra.
coucou747
Messages postés12303Date d'inscriptionmardi 10 février 2004StatutMembreDernière intervention30 juillet 201244 24 mai 2007 à 04:56
j'aime bien le bindValue, le prepare et autre, ca rappelle le dbi du perl :) euh, par contre, pour bindValue, pourquoi ne pas utiliser gettype pour le dernier parametre ?
c'est joli, propre, OO, proposer des exeptions differentes pour les differentes fonctions permet d'avoir plusieurs catch qui font des choses differentes, ici si je ne me trompe pas (il est 5h du matin, alors pardonnez mes erreurs), tu renvois toujours les memes exeptions...
sinon, proposer un cache ca peut etre interessant, un bench, ou autre...
Donc concretement, ca permet d'utiliser des resultats de requettes comme un tableau, dans ce cas, on pourrait renvoyer un array et non un objet... (ouais ca fait moins OO, et ? ca n'est pas une erreur...)
Ca permet de ne pas avoir d'injections de codes disons malveillants... ok, c'est un avantage.
Ca permet de manipuler n'importe quel type de bdd sans pour autant s'ennuyer avec des "pointeurs sur fonctions", oui, c'est un fait...
Sinon, pourquoi ne pas faire une class genre Factory et faire la connexion comme on la ferait en java ou en perl ?
a=new SqlConnection('mysql://user:pass@Bdd');
histoire d'avoir vraiment juste une variable a changer et pas une fonction lorseque l'on change de base.... (ouais, balader un String dans un fichier de conf, c'est plus propre que de balader un nom d'objet...)
FhX
Messages postés2350Date d'inscriptionmercredi 13 octobre 2004StatutMembreDernière intervention18 avril 20153 23 mai 2007 à 22:56
Ca me rappèle bien des choses :)
J'avais fais un truc "similaire" (quoi que...), disons pour l'abstraction en tout cas mdr.
Pour ce que j'en pense :
- Je trouve l'itérateur un peu bizzare.
En effet, je m'attendais à voir dans ::Next() quelque chose qui fasse un fetch() dans ton objet de bdd. Hors, c'est le ::current() qui le fait chez toi.
C'est une grosse contradiction, car si j'appèle ::current() plusieurs fois, j'ai passé plusieurs tours de fetching alors que ton iKey n'a pas bougé d'un poil. De plus, sur un "foreach ( $ton_iterateur as $key=>$val ) ... ", je ne suis pas sur qu'il marche. Je dis bien que je n'en suis pas sur, je n'ai pas essayé.
Je trouve ca bizzare c'est tout :)
Ah non, ce que je trouve bizarre en faite, c'est ca :
"results are fetched through an iterator wich returns variables corresponding to columns name (just like an extract)"
Ca me fait un peu peur ^^
- Concernant l'abstraction.
La aussi, j'emets quelques rétissance. D'abord pour la duplication de méthodes internes. C'est ce que j'avais fait il y a un temps, mais je me suis appercu du mauvais coté.
Ce que tu fais de cette abstraction, ce n'est que de la vérification. En réalité, ce que tu devrais faire, c'est de l'agregation d'objet. Explication :
Classe abstraite SQL
+ $var - lien de connection sql.
+ $var - nom d'hote, user, password et nom de la base de donnée
+ method() - Constructeur public
+ method() - connection à la base de donnée
+ method() - fermeture de la base de donnée
Interface iSQL
+ method() - Exécute la requète
+ method() - Prépare une requète
+ method() - libère la mémoire d'une requète
Classe concrète MySQL étendant SQL implémentant iSQL
+ method() - return mysql_query($sql)
+ method() - return new Query_Prepare($sql)
+ method() - mysql_free_result($query)
Class concrète "d'abstraction" SQL implémentant iSQL
+ $var - objet contenant la classe SQL voulu
+ method() - construct( SQL $object )
+ method() - query($sql) ==> $this->object->query($sql); // vérification avant et après
+ method() - prepare($sql) ==> $this->object->prepare($sql); // Vérification avant et après
...etc...
A l'instanciation :
$db = new abstraction( new MySQL );
$db2 = new abstraction( new MSSQL );
La classe "d'abstraction" se chargera de faire la même chose que ton abstraction à toi :)
Et si je n'ai pas envie de cette "abstraction", je la vire :)
Je vois les choses sous cet angle, car tu ne fais pas une vrai abstraction en réalité.
- Tu ne peux traiter qu'une query à la fois.
- Je rêve, mais j'aimerai pouvoir faire un jour :
$objet = new mysql;
echo $objet->prepare('SELECT.....', $var1, $var2)->query()->fetch_object()->getMachinFromTruc(...)->getMachinChose();
29 janv. 2008 à 19:52
18 janv. 2008 à 20:15
ok...ce week-end, je te fais un autre fichier plus clair :-) D'autant plus que la classe a bcp évolué et pas le fichier d'exemples.
18 janv. 2008 à 17:25
connexion à la base, select, update, gestion de transaction etc... mais là je nage un peu sans exemple quand même ...
@+
7 janv. 2008 à 20:55
Ok, la solution est de passer dans le tableau de configuration le booléen $new_link, en le mettant à false par défaut si il n'est pas renseigné. Comme les fonctions idoines.
Je ferai ça, très bonne remarque :-)
7 janv. 2008 à 20:48
pense plutot à 2 ressources de db mysql telle qu'elles sont implémentés pas PHP.
Pour moi tout se joue dans mysql_connect()
resource mysql_connect ([ string $server [, string $username [, string $password [, bool $new_link [, int $client_flags ]]]]] )
le param $new_link n'est pas défini dans ta classe donc le mysql_connect dès son premier appel, te récupéres la ressource existante si elle existe.
Chose qui ne va pas de paire avec la notion singleton multiton POO.
J'ai testé ce comportement indépendament de ta classe : Mais je peux t'assurer que ta classe le reprend :
Regarde bien le ressource_id generé par mysql_connect : c'est le même.
7 janv. 2008 à 20:20
- singleton de base en fonction du type de db.
- pas de singleton du tout, quoi qu'il se passe : on crée toujours une nouvelle instance (il manque à implémenter une méthode permettant de récupérer une instance créée plus tôt vu que je les stocke).
- singleton avec "espace de nom" : on passe un "espace de nom", et un type de db évidemment. Si l'usine trouve dans ses instances une instance avec le même espace de nom pour ce type de DB, elle le renvoie, sinon elle l'instancie, le stocke, et retourne l'instance.
Le paramètre est $mIsSingleton, il est à true par défaut (singleton "typé" db), si on le passe à false, pas de singleton du tout, si on passe une chaîne ou toute valeur scalaire, c'est le 3ème cas (la valeur passée étant "l'espace de nom").
Dans l'exemple que tu me donnes, je suis très surpris car chez moi cela fonctionne très bien. Et le code étant le suivant :
# self::$_instance[$saDBType] = new $saDBType ($aConConf, $aOptions);
# return self::$_instance[$saDBType];
j'avoue ne pas comprendre comment tu peux te retrouver avec le même objet au final...c'est très bizarre. Tu es sur quelle version de PHP, il y a peut-être une subtilité que je n'aurais pas remarquée sur une des versions de PHP (j'ai découvert il y a 2 jours ceci :
http://www.php.net/manual/en/language.oop5.late-static-bindings.php
donc bon...il y a toujours moyen qu'on zappe une nouveauté ;-) ) (au passage, lisez, c'est une GROSSE nouveauté, TRES intéressante, parce que ce **tain de problème, je m'y suis heurté un paquet de fois...!).
Bref, j'aimerais creuser ton truc là, mais j'ai besoin de toi lol, vu que je ne le reproduis pas.
7 janv. 2008 à 20:02
un exemple pour mieux me faire comprendre:
$configMysqlDb1=array('HOST'=>'localhost','LOGIN'=>'root','PWD'=>'xxx',
'DB'=>'information_schema');
$configMysqlDb2=array('HOST'=>'localhost','LOGIN'=>'root','PWD'=>'xxx',
'DB'=>'db_blindtest');
//exemple en multiton:
$instDb1=aDBFactory::getInstance('mysql',$configMysqlDb1,false);
print_r($instDb1);
//voir la propriété rlink
$instDb2=aDBFactory::getInstance('mysql',$configMysqlDb2,false);
print_r($instDb2);
//voir la propriété rlink
>tu constateras que $instDb1->rLink $instDb2->rLink donc ta classe doit se connecter avec le parametre true dans mysql_connect pour vraiment être un multiton
5 janv. 2008 à 16:09
SQLite gère les transactions, et mysql 3...ça date sérieusement quand même. On pourrait oui mais très sncèrement je ne le ferai pas sur ma classe, ce serait quelques classes de plus pour pas grand chose.
5 janv. 2008 à 13:54
l'autre jour, je me suis heurté à un problème en créant plusieurs instances de db (ce n'était pas avec cette classe mais j'ai l'impression qu'une multi instanciation peut causer le même problème ).
la fonction mysql_connect doit à tout prix utiliser le flag true ou false pour permettre la création d'une nouvelle ressource de connexion à MYSQL.
Donc il faut que ce soit paramétrable :la possibilité pour ta classe d'utiliser le multiton ne suffit pas dans ce cas..:)
5 janv. 2008 à 13:30
4 janv. 2008 à 20:01
4 janv. 2008 à 19:42
4 janv. 2008 à 00:41
Dans ce cas :
abstract class db {
}
class Transactiondb extends db {
}
class NonTransactiondb extends db {
}
Et puis roule ma poule :)
28 déc. 2007 à 19:38
Je viens de faire une très grosse mise à jour : aucune incompatibilité avec les versions précédentes (vous pôuvez conserver sans les modifier les codes utilisant cette classe), mais un tas de nouveautés et d'améliorations par contre :-)
23 déc. 2007 à 19:15
28 mai 2007 à 19:36
$test aDBFactory::getInstance ('mysql', array ('HOST'>'localhost', 'LOGIN' => 'login', 'PWD' => 'pwd', 'DB' => 'wrongdb'));
Sinon pour les transactions, je sais qu'on peut contourner comme toi et FHX le dites, mais ce qui me gêne c'est que pour une classe de 30 methodes de transactions ça fait redéfinir 30 mèthodes à vide (bon là on est grosso modo à 5 methodes de transactions donc ça peut aller :)).
PS Malalam au passage : Si t'as l'occasion de rejeter un oeil à la source calendrier html que j'ai postée:
un avis d'expert en itérateurs me serait bien utile : j'ai fait une bonne Mise à jour.
28 mai 2007 à 09:11
Sauf si tu as utilisé ma dernière fonction dans les exemples, où, là, c'est possible puisque c'est ujne fonction qui fait de la récursivité en cas d'erreur. Auquel cas ce n'est pas un bug, mais une mauvaise utilisation de l'exemple ;-)
Peux-tu me montrer ton code ?
Pour les transactions, même remarque que FhX. Ca n'a pas réellement d'incidence.
Kankrelune : why not! Je vais la tester, merci :-)
26 mai 2007 à 19:18
abstract class x {
abstract public function truc();
}
class y extends x {
abstract public function truc();
}
Rien ne t'empèche de ne rien mettre dedand :)
26 mai 2007 à 14:13
Elle n'est malheureursement pas compatible avec les db non transactionnelles.
Toutes tes methodes transactionnelles sont abstraites :
J'aurais bien vu une implémentation de 2 types de Db (les transactionnelles et les autres) sinon ça oblige à implémenter les methodes de transaction même pour les bases non transactionnelles.
26 mai 2007 à 13:58
j'ai essayé avec mysql et une exception est lancée à priori dans un foreach:
pour t'aider j'ai ce message:
Connexion failed with message [Unknown database 'wrongdb']mysql :: select_db() : [0] Connexion failed with message [Unknown database 'wrongdb']
mysql :: select_db() : [0] Connexion failed with message [Unknown database 'wrongdb']
mysql :: select_db() : [0] Connexion failed with message [Unknown database 'wrongdb']
mysql :: select_db() : [0] Connexion failed with message [Unknown database 'wrongdb']
mysql :: select_db() : [0] Connexion failed with message [Unknown database 'wrongdb']
26 mai 2007 à 11:35
Vite fait comme ça, en reprenant l'exemple de coucou (sgdb://login:pass@host:port/db), je dirais un truc du genre...
preg_match('~^([[:lower:]]+)://(\w+):(\w+)@(\w+)(?::([0-9]+))?/([\w-.]+)$~', $connectStr, $connectInfos);
@ tchaOo°
26 mai 2007 à 08:12
Mais évidemment, je biens d'accord sur le fait qu'il n'y a pas de règle générale pour ça.
PDO est calqué sur DBI, donc je vois très bien ce que tu veux dire. Oui c'est une excellente implémentation! Mais tout n'est pas très facile à simuler, notamment les marqueurs (avec les bindValue, bindParam, bindColumn et compagnie).Je n'ai pas non plus calqué le principe de la classe DBhandler ($dbh), et la classe Statementhandler ($sth), mais j'y songe aussi...
Enfin, ok, je réflêchirai à une manière de passer la config dans une chaîne, façon DBI ou PDO :-)
Codefalse => merci, déjà :-)
Généralement, en effet, on étend un tas d'exceptions spécialisées dans les classes du package que l'on crée. Bref, oui, c'est bien une volonté de ma part. Cela permet de gérer correctement les exceptions derrière avec différents catch. Ceci dit, rien n'empêche d'avoir aussi des exceptions plus génériques hein. Mais il faudrait alors les inclure dans tous les packages créés...autant la jouer spécialisé. Je vais les scinder un peu plus encore, c'est loin d'être suffisemment précis, en l'état, d'ailleurs.
En effet,le but est de ne lever les exceptions que si l'option EXCEPTION_ON_ERROR est active. Et pour la méthode aDB::interceptException(), tu as parfaitement raison. J'étais parti sur une tout autre façon de gérér cette méthode au départ, et l'utilisation de la réflexion est un reste malencontreux de ces essais :-) Je l'ai enlevée depuis.
Je corrigerai le commentaire, merci :-)
Guill76 => merci :-)
Par contre, je ne te suis pas sur l'erreur ? Tu tombes vraiment dans une boucle infinie quand tu entres un mauvais type de db dans l'usine? Je ne vois pas comment ?
26 mai 2007 à 00:02
great!!!
Just a little mistake=> "UNLIMITED LOOP" when using a wrong dbname in the factory method.
Sinon l'emploi des combinaisons pour le fetchall est pas mal: ça me fait penser au systeme du chmod sous unix.
25 mai 2007 à 10:28
Alors deja, si on pouvait mettre 11 sur 10, je l'aurai fait !
Vraiment impressionné !
Bon, j'ai du relire 3 fois tes 1400 lignes histoire de bien tout comprendre (j'ai un peu de retard sur la poo ...) mais c'est bien rentré !
Par contre, quelques questions que je me doit de poser pour mieux comprendre :)
Ta classe d'exception (aDBException), se limite au sgbd. Est-ce une volontée de ta part pour cette partie ou est-ce que ca restera comme ca pour tout ton projet ?
Dites moi si je me trompe, mais ne serait-il pas plus interessant de faire une classe de gestion d'exception globale, et ensuite heriter les erreures comme tu le fait, mais spécialisé pour chaque usages ? (x classes héritant de l'exception globale pour la db, x classes héritant de l'exception globale pour la classe xxx, etc). Bon si c'est une volontée de ta part juste pour te limiter au db, ok :)
Par contre, dans ta classe abstraite aDB, tu utilise interceptException pour envoyer des exceptions.
L'utilité de cette fonction est juste pour la condition (EXCEPTION_ON_ERROR mode is active) alors on genere une nouvelle erreur ? J'ai bien compris ?
Dans cette fonction interceptException, pourquoi utilise tu l'usinage alors que ton appel envers la classe aDBException reste toujours pareil ?
car tu fait
# $e = new ReflectionClass($sExceptionClass);
# throw $e -> newInstance ($sErrorMsg, $iCode, $sClass, $sFunction);
ca pourrait être pareil que faire
# throw new $sExceptionClass ($sErrorMsg, $iCode, $sClass, $sFunction);
Je me trompe ? (c'est probable !! :p)
Sinon une petite note, dans le commentaire descriptif du constructeur de la classe class aDBException extends Exception, tu a oublié le parametre $iCode
(j'ai pas passé tous tes commentaires en revue, mais je trouvé celui la :p)
Voila voila !
25 mai 2007 à 08:55
Class.forName("org.gjt.mm.mysql.Driver").newInstance();
connection=DriverManager.getConnection("jdbc:mysql://localhost/database","root","");
en perl :
$dbh = DBI->connect("DBI:mysql:database","root","");
en openoffice.org basic, j'ai pas le code sous les yeux (en fait je ne l'ai plus depuis un triste formatage) mais ca ressemblait au code java :)
a partir de ca, j'en conclue qu'il n'existe pas de facon traditionelle de se connecter a une BDD...
en perl, ca donne un truc comme ca :
$sth = $dbh->prepare("SELECT LAST_INSERT_ID()");
$sth->execute();
($id) =$sth->fetchrow();
$sth->finish();
et si t'as un parametre, tu mets un '?' dans la requette et une variable a execute alors perl est ok un vieux langage, mais tu vois, cette classe dbi, c'est geant...
sinon t'as
$rows_affected = $dbh->do("...") || die "Database Error: $DBI::errstr";
principale probleme du perl, j'ai rien vu au sujet des exeptions...
25 mai 2007 à 08:23
25 mai 2007 à 08:23
je verrai ça dans la journée (je vais pas tarder à aller au taf là)
Coucou => Si si, j'ai dit Kankrelune parce que c'était le dernier message. Mais j'ai réflêchi à ce que tu m'as dit concernant plusieurs connexions et effectivement l'usinage était la meilleure solution pour ça. On doit même pouvoir pousser le concept, et je suis sûr que t'as plein d'idées pour ça ;-)
Pour la manière de se connecter, je laisserai peut-être le choix entre ce que tu proposes, et la manière "traditionnelle", pourquoi pas, oui. je n'ai pas eu le temps de me pencher dessus, c'est tout :-) Mais j'avais bien noté ce que tu disais.
Idem pour le bindValue : j'ai bien noté et c'est une très bonne idée (même si ça va causer des erreurs, mais ça...charge à l'utilisateure de faire attention à son typage!). Je vais l'implémenter. Mais j'ai passé pas mal de temps à bosser sur les exceptions, à corriger quelques bugs, et sur cet itérateur. Je pense avoir le temps aujourd'hui.
Toute suggestion est la bienvenue, vu que ce package, je vais l'utiliser, moi, alors autant qu'il soit sympa à utiliser et pratique ;-)
24 mai 2007 à 19:22
$myDb = aDBFactory::getInstance ('mysql', $aCon); j'aurais vraiment prefere
$myDb = aDBFactory::getInstance ('mysql://login:pass@host:port/basededonnee');
public function bindValue ($sNeedle, $mValue, $cType) {
t'es pas decide a mettre ce troisieme parametre optionel ?
24 mai 2007 à 18:35
@ tchaOo°
24 mai 2007 à 18:15
Fait attention, car si tu utilises foreach(), regarde ce que cela fait :
--->instanciation itérateur
---> Appel de foreach() sur l'objet itérateur
| --> Rewind()
| --> next()
| --> valid()
---> current() + key().
Si tu mets next() dans ton constructeur, tu vas récupérer le premier enregistrement. Hors, sur un foreach(), rewind() est appelé en début de boucle, donc tu vas "perdre" le bénéfice du next() dans ton constructeur.
Tu as le choix dans ton rewind(), soit tu t'en sers, soit tu t'en sers pas. Dans le cas ou tu t'en sert (donc ==> mysql_seek(0)), tu vas effectivement perdre un appel de méthode. Si tu ne t'en sers pas, tu vas "sauter" un enregistrement.
Vala, un ptit rappel :p
24 mai 2007 à 18:03
ce que j'ai fait, c'est que j'appelle automatiquement sqlIterator::next() dans le constructeur. Comme ça, pas la peine de l'appeler avant la boucle while () (ce qui n'est décidément pas naturel pour moi).
24 mai 2007 à 17:26
Vala c'est fait :D
24 mai 2007 à 17:26
Oh, c'est quoi déja ?? ;)
"Ce qui m'a géné, dans ce cas, c'est que si je décide de traverser mon itérateur avec un while au lieu d'un foreach, je dois alors faire un next AVANT de faire un current...et pas à la fin de chaque boucle while comme habituellement. C'était trop étrange à mon goût..."
Ah vi, sauf qu'habituellement tu fais :
$i=0;
while ( $i < 100 } {
echo $i;
$i++;
}
donc, tu as posé le schéma type :
$iterateur = new iterateur(.....);
while ( $iterateur->valid() ) {
$contenu = $iterateur->current();
echo $contenu[...].$contenu[...];
$iterateur->next();
}
On voit bien ici que le ::current() doit retourner le contenu de l'itérateur "en fonction du compteur interne de l'itérateur". Ce compteur est incrémenté par ::next(). Hors, dans ta classe d'itération, c'est ::current() qui se charge de la récupération des données MAIS SANS prendre en compte le compteur interne.
Si j'utilise ta classe et que je fais :
$iterateur = new iterateur(.....);
while ( $iterateur->valid() ) {
$contenu = $iterateur->current();
echo $contenu[...].$contenu[...];
$contenu2 = $iterateur->current();
echo $contenu2[...].$contenu2[...];
$iterateur->next();
}
On voit bien que $contenu et $contenu2 contiennent des données différentes AU LIEU de contenir la même chose. Mais le ::next() en haut de la boucle te gène, où est donc le problème ? Regarde, c'est ici :
$i=0; // <============================= ICI
while ( $i < 100 } {
echo $i;
$i++;
}
Et voui, j'ai initialisé le contenu de ma variable avant la boucle.
Hors chez toi :
$iterateur = new iterateur(.....);
// <======== la clé devrait valoir -1 (0 étant le premier enregistrement), et la propriété stockant la ressource courante est de type null.
while ( $iterateur->valid() ) {
$contenu = $iterateur->current();
echo $contenu[...].$contenu[...];
$iterateur->next();
}
Pour que cela fonctionne correctement, tu dois faire ceci :
$iterateur = new iterateur(.....);
$iterateur->next();
while ( $iterateur->valid() ) {
$contenu = $iterateur->current();
echo $contenu[...].$contenu[...];
$iterateur->next();
}
Cela implique à ta classe d'itération quelques changements que voici : (c'est un schéma type)
class IterateMe extends Iterator {
private $key;
private $query;
private $output;
public function __construct($query) {
$this->key = -1;
$this->query = $query;
$this->output = null;
}
public function valid() {
return ( isset($this->output) || false !== $this->output);
}
public function next() {
$this->key++;
$this->output = mysql_fetch_****($query);
}
public function current() {
return $this->output;
}
//etc...
}
Cette classe marche ensuite comme ceci :
// foreach
foreach ($it as $key=>$val) {
// $val sortant de ::current();
// next étant exécuté à chaque itération de boucle
}
// while
$it->next();
while ( $it->valid() ) {
echo $it->current();
echo $it->current();
$it->next();
}
Vala vala :)
Pour l'abstraction, je vais faire un ptit truc ce week end. Je te filerai ca exclusivement :p
24 mai 2007 à 15:16
J'ai fait pas mal d'ajouts cette fois : l'usinage avec multiton (comme quoi, Kankrelune...t'as raison, c'était une très bonne idée), les différentes exceptions...et des exemples pour utiliser les différentes exceptions justement.
reste tjrs à bosser sur l'abstraction :-)
24 mai 2007 à 15:07
Je n'ai pas eu le temps de regarder le code en détail je ne le commenterais donc pas mais de ce que j'ai vu c'est plus que propre... .. .
Pour commenter les commentaires (lol) précédant je pense que l'idée d'une factory est plutot bonne... on a pas souvent besoin de se connecter à plusieurs serveur mais ça peut arriver et dans ce cas une factory simplifie quand même les choses... dans la même idée j'opterais plus pour un multiton qu'un singleton... ça permet des connections à différents serveur mais également à différentes bases de données... .. .
"(Moi je place ca dans mes signets, voir si ca evolue, si ca evolue bien, je veux dire avec les differentes exeptions, et autres ameliorations, je dois dire que j'aurais du mal a faire mieux... du moins en partant de 0 ca serait trop long, alors pourquoi ne pas utiliser ca si un jours j'ai une grosse usine a mettre en place...)"
+1
10/10... .. . ;o)
@ tchaOo°
24 mai 2007 à 08:42
(Moi je place ca dans mes signets, voir si ca evolue, si ca evolue bien, je veux dire avec les differentes exeptions, et autres ameliorations, je dois dire que j'aurais du mal a faire mieux... du moins en partant de 0 ca serait trop long, alors pourquoi ne pas utiliser ca si un jours j'ai une grosse usine a mettre en place...)
24 mai 2007 à 08:10
déjà, merci à tous les deux :-)
Maintenant, je vais tâcher d'expliquer quelques points car n'ayant pas fait de doc encore, certaines fonctionnalités restent obscures je pense :-)).
FhX => je n'ai tjrs pas pu réparer mon pc perso...mais je n'oublie pas ce que je t'ai promis (si t'en as tjrs besoin depuis le temps), dès que je le retrouve (au taf, je ne peux décemment pas lol).
L'itérateur : il fonctionne très bien. Je comprends ta réticence, mais j'ai testé les deux façons de faire : l'actuelle, et faire le fetch dans le next, le stocker dans une propriété, et renvoyer cette propriété dans le current. Ce qui m'a géné, dans ce cas, c'est que si je décide de traverser mon itérateur avec un while au lieu d'un foreach, je dois alors faire un next AVANT de faire un current...et pas à la fin de chaque boucle while comme habituellement. C'était trop étrange à mon goût...
Extract : attention...ça n'a pas de rapport direct avec l'itérateur. C'est juste un paramètre, le aDB::FETCH_EXTRACT qui permet de récupérer les valeurs de cette manière. Mais on peut aussi fonctionner "normalement" via aDB::BOTH, aDB::FETCH_ASSOC etc...et retrouver un tableau associatif, numérique, les deux...Par contre, c'est dangereuyx en effet, et à utiliser en connaissance de cause. Comme un extract (), ou le PDOStatement::bindColumn() avec PDO::FETCH_BOUND.
Pour ta notion d'abstraction, c'est effectivement une jolie idée...je vais me pencher dessus.
Coucou => Tu ne te trompes pas pour les exceptions, il faudrait que je répartisse un peu tout ça, en effet, et que j'appelle différents types d'exceptions en fonction des erreurs trouvées.
Le cache, j'y pense. Le bench, je ne suis pas pour, je préfère utiliser ça indépendemment de toute classe : comme ça, j'ai une classe bench, que j'utilise où je le veux. Du coup, en ajouter une à ces classes les alourdirai inutilement.
Selon la méthode utilisée, la classe renvoie un tableau (un vrai lol, c'est aDB::fetchAll()) ou un itérateur (un objet donc, c'est aDB::fetch), ou même une simple valeur (aDB::fetchColumn).
L'usinage, je n'en voyais pas non plus l'intérêt dans ma classe : je ne veux pas de singleton (j'utilise souvent plusieurs objets db distincts). Ceci dit, on ne change pas de méthode pour se connecter à des bases différentes là ? Ce sera tjrs la même méthode et les mêmes paramètres à passer.
Mais je vais quand même revenir sur cette histoire d'usine...je pense. On verra.
24 mai 2007 à 04:56
c'est joli, propre, OO, proposer des exeptions differentes pour les differentes fonctions permet d'avoir plusieurs catch qui font des choses differentes, ici si je ne me trompe pas (il est 5h du matin, alors pardonnez mes erreurs), tu renvois toujours les memes exeptions...
sinon, proposer un cache ca peut etre interessant, un bench, ou autre...
Donc concretement, ca permet d'utiliser des resultats de requettes comme un tableau, dans ce cas, on pourrait renvoyer un array et non un objet... (ouais ca fait moins OO, et ? ca n'est pas une erreur...)
Ca permet de ne pas avoir d'injections de codes disons malveillants... ok, c'est un avantage.
Ca permet de manipuler n'importe quel type de bdd sans pour autant s'ennuyer avec des "pointeurs sur fonctions", oui, c'est un fait...
Sinon, pourquoi ne pas faire une class genre Factory et faire la connexion comme on la ferait en java ou en perl ?
a=new SqlConnection('mysql://user:pass@Bdd');
histoire d'avoir vraiment juste une variable a changer et pas une fonction lorseque l'on change de base.... (ouais, balader un String dans un fichier de conf, c'est plus propre que de balader un nom d'objet...)
23 mai 2007 à 22:56
J'avais fais un truc "similaire" (quoi que...), disons pour l'abstraction en tout cas mdr.
Pour ce que j'en pense :
- Je trouve l'itérateur un peu bizzare.
En effet, je m'attendais à voir dans ::Next() quelque chose qui fasse un fetch() dans ton objet de bdd. Hors, c'est le ::current() qui le fait chez toi.
C'est une grosse contradiction, car si j'appèle ::current() plusieurs fois, j'ai passé plusieurs tours de fetching alors que ton iKey n'a pas bougé d'un poil. De plus, sur un "foreach ( $ton_iterateur as $key=>$val ) ... ", je ne suis pas sur qu'il marche. Je dis bien que je n'en suis pas sur, je n'ai pas essayé.
Je trouve ca bizzare c'est tout :)
Ah non, ce que je trouve bizarre en faite, c'est ca :
"results are fetched through an iterator wich returns variables corresponding to columns name (just like an extract)"
Ca me fait un peu peur ^^
- Concernant l'abstraction.
La aussi, j'emets quelques rétissance. D'abord pour la duplication de méthodes internes. C'est ce que j'avais fait il y a un temps, mais je me suis appercu du mauvais coté.
Ce que tu fais de cette abstraction, ce n'est que de la vérification. En réalité, ce que tu devrais faire, c'est de l'agregation d'objet. Explication :
Classe abstraite SQL
+ $var - lien de connection sql.
+ $var - nom d'hote, user, password et nom de la base de donnée
+ method() - Constructeur public
+ method() - connection à la base de donnée
+ method() - fermeture de la base de donnée
Interface iSQL
+ method() - Exécute la requète
+ method() - Prépare une requète
+ method() - libère la mémoire d'une requète
Classe concrète MySQL étendant SQL implémentant iSQL
+ method() - return mysql_query($sql)
+ method() - return new Query_Prepare($sql)
+ method() - mysql_free_result($query)
Classe concrète MSSQL étendant SQL implémentant iSQL
+ method() - return mssql_query($sql)
+ method() - return new Query_Prepare($sql)
+ method() - mssql_free_result($query)
Class concrète "d'abstraction" SQL implémentant iSQL
+ $var - objet contenant la classe SQL voulu
+ method() - construct( SQL $object )
+ method() - query($sql) ==> $this->object->query($sql); // vérification avant et après
+ method() - prepare($sql) ==> $this->object->prepare($sql); // Vérification avant et après
...etc...
A l'instanciation :
$db = new abstraction( new MySQL );
$db2 = new abstraction( new MSSQL );
La classe "d'abstraction" se chargera de faire la même chose que ton abstraction à toi :)
Et si je n'ai pas envie de cette "abstraction", je la vire :)
Je vois les choses sous cet angle, car tu ne fais pas une vrai abstraction en réalité.
- Tu ne peux traiter qu'une query à la fois.
- Je rêve, mais j'aimerai pouvoir faire un jour :
$objet = new mysql;
echo $objet->prepare('SELECT.....', $var1, $var2)->query()->fetch_object()->getMachinFromTruc(...)->getMachinChose();
Chose impossible sur ta classe :)
Cependant, c'est pas mal, ainsi je noterai 9.
Non 10, tout est propre :)