Salut à tous,

Pour ceux que ça intéresse j'ai modifié la fonction find() du tutoriel développer son site de A à Z.
Elle permet maintenant toute une tripotée de choses, je ne l'ai pas encore optimisé avec le ternaire mais si ça peut dépanner certains d'entres vous je vous la donne.

/**
      * Fonction qui permet de trouver tout les résultats dans la base de données
      * Selon les conditions assignées.
      **/
      public function find($req = array()){
       $sql ='SELECT ';

       if(isset($req'fields'])){
            if(is_array($req'fields'])){
              $sql .= implode(', ',$req'fields']);
            }else
            {
              $sql .= $req'fields'];
            }
          }elseif(isset($req'distinct'])){
            if(is_array($req'distinct'])){
              $sql .= 'DISTINCT '.implode(', ',$req'distinct']).' ';
            }else
            {
               $sql .= 'DISTINCT '.$req'distinct'].' ';
            }
          }else
          {
            $sql .='*';                                                                                                  
          }

            $sql .= ' FROM '.$this->table.' ';

          //Construction de la jointure
       if(isset($req'join'])){
          $sql .= strtoupper($req'join']).' '.'JOIN ';
          if(isset($req'on'])){
            $cond = array();
            if(isset($req'jointab'])){
              $sql .= $req'jointab'];
              $this->jointab = $req'jointab'];
            }
              foreach($req'on'] as $k=>$v){
                $sql .= ' ON ';
                $cond] = "$this->table.$k=$this->jointab.$v";
              }
                $sql .= implode(' AND ',$cond);
          }
        }

        if(isset($req'table']))
        {
            $this->choice = $req'table'];
        }else
        {
            $this->choice = $this->table;
        }
          //Construction de la condition
        if(isset($req'conditions'])){
            $sql .= ' WHERE ';
            if(!is_array($req'conditions'])){
               $sql .= $req'conditions'];
            }else
            {
                $cond = array();
                foreach($req'conditions'] as $k=>$v){
                  if(!is_numeric($v)){
                     $v = $this->db->quote($v);
                  }
                $cond] = "$k=$v";
                }
                $sql .= implode(' AND ',$cond);
            }
        }

        if(isset($req'orderasc'])){
            $sql .= ' ORDER BY '.$this->choice.'.'.$req'orderasc'].' ASC';
        }
        if(isset($req'orderdesc'])){
            $sql .= ' ORDER BY '.$this->choice.'.'.$req'orderasc'].' DESC';
        }

        if(isset($req'limit'])){
          $sql .= ' LIMIT '.$req'limit'];
        }
        $pre = $this->db->prepare($sql);
        $pre->execute();
        return $pre->fetchAll(PDO::FETCH_OBJ);
      }

Parfois vous pouvez avoir une erreur sql du type colonne xx ambigüe, c'est dû au fait que le champ que vous essayez de sélectionner existe dans les deux tables donc pour y remédier il faut préciser la table dans laquelle vous voulez sélectionner votre champ.

Un exemple d'utilisation de la fonction find():

$this->lengths = $this->Product->find(array(
                            'fields' => 'length,lengths.online',
                            'join' => 'inner',
                            'jointab' => 'lengths',
                            'on' => array('id'=>'id_product'),
                            'table' => 'lengths',
                            'conditions' => array('id_product' => $id,'category' =>$category,'sub_category' =>$sub_category,'lengths.online' => 1),
                            'orderasc' =>'length'
                            ));

Comme vous le voyez dans les conditions il y a un lengths.online je sélectionne donc le champ online disponible dans la table lengths et non products car elle ne m'intéresse pas. Je n'ai pas encore trouvé de façon plus intuitive pour faire la même chose mais j'y travaillerai plus tard.
Le 'jointab' c'est pour définir la table de liaison afin de pouvoir mettre une condition ON sur la seconde table.
Le 'table' c'est pour définir dans quelle table on va choisir le champ que l'on veut ordonner avec ORDER BY.

J'espère avoir été assez clair et que cela aidera plus d'un.

22 réponses


Très sympa !

Belle initiative

Xtr3me
Auteur

Au plaisir d'aider ;) .
Si certains ont des suggestions pour optimiser le code ou le rendre plus lisible ne vous gênez pas pour les faire je pense que je rajouterai tout ce qu'il manque pour faire un peu tout les types de requête possible et quand j'aurai le temps je vais essayer de raccourcir le code au minimum là j'ai fait ça en l'espace de 30 min environ le temps d'en apprendre un peu plus sur l'ordre de la requête et de trouver un moyen de tout mettre en place.

Je pense que je vais supprimer l'index jointab et table pour remplacer par un système de marqueur qui définira la table dans laquelle on lance la fonction et la table de jointure. Comme ça le code devrait être plus court, plus optimisé et il y aura besoin de moins d'arguments à rentrer dans l'appel de la fonction.

J'ai réindenté le code pour que ce soit plus propre, j'arrive vraiment pas à trouver le code propre et lisible comparé aux code d'API etc...

ce sujet a deja été traité de façon encore plus complete ici:
http://www.grafikart.fr/forum/topic/5960.

Xtr3me
Auteur

En effet je ne le savais pas mais les deux fonctions ne sont pas composés de la même manière et je n'aime pas trop l'idée de créer plusieurs variables dont il faudra définir leur valeur tu te retrouves avec tout un tas d'index de tableau à devoir définir et donc tu dois retenir tout un tas d'index pour pouvoir faire une requête complexe pour choisir le préfixe etc... Après j'ai fait moi même cette fonction pour répondre à mes besoins actuels et pourquoi pas futur, je l'ai fait assez rapidement pour répondre de suite à mon besoin et je pensais l'améliorer que par la suite.

D'ailleurs le système de recherche de ce site ne m'avait pas trouvé ton topic d'où le fait que j'ai développé ma propre fonction à partir de celle du tutoriel ^^ . Je pense que je vais continuer sur mon idée tout en la rendant plus flexible et simple à utiliser je ne sais pas encore comment je vais procéder mais je verrais bien.

En tout cas merci de ton intervention je vais voir ce qu'il manque d'ailleurs il te manque le Distinct apparemment et j'ai pu apprendre l'existence du CROSS JOIN avec ta fonction ^^ .

salut Xtr3me
je pense que tu as survolé le code sans toute fois cherché à le comprendre. car:

  • il ne s'agit pas de plusieurs tableaux , mais d'un seul tableau multidimensionnel, phpmyadmin like. donc toute personne ayant un jour configuré phpmyadmin se sentirait à l'aise avec ce genre de tableau. et en plus les index tu n'as pas à retenir vu que tu doit juste incrémenter selon que tu veux joindre plusieurs tables, définir plusieurs règles de jointure (join $table ON... )ou plusieurs conditions ( Where.....AND.... )

  • pour ce qui est du préfixage du nom de tables devant ceux des champs(select table.field from table), là encore tu n'as pas t'en occupé, à condition d'adopter ma methode "find", qui grace à la methode privée "setcolumnprefix" le fait de manier automatique, et si par contre tu l'avais deja fait lors de la contruction de ta requete, alors elle le laisse telle qu'elle. c'est ainsi qu'elle transformera automatiquement cette requete:

    SELECT * FROM posts WHERE type='post' AND online=1 LIMIT 0,3

en

SELECT posts.* FROM posts WHERE posts.type='post' AND posts.online=1 LIMIT 0,3
  • ma fonction gere tres bien le "distinct", et tu n'a qu'à regarder cette portion de code:

    if(preg_match($space, $v)) //presence d'un espace ex: distinct field ou field AS alias
    {
    $col = explode(' ',$v);
    if(strtolower($col[1])==='as') $var$k] = $needle.$v;
    else $var$k] = current($col).' '.$needle.end($col);
    }

de la methode "setcolumnprefix" pour t'en apercevoir.
bref tu as juste à te pencher vraiment sur le code, et tu ne t'en débarrassera plus, car j'ai respecté au maximum la logique de construction des requêtes telle que proposée par grafikart dans le tuto, et le tableau a été mis sous cette forme pour une meilleur lisibilité. apres libre à toi de l'adopter ou d'utiliser ton propre code.

Xtr3me
Auteur

Non je n'ai pas survolé le code je me suis mal exprimé oui ^^ . J'ai bien vu que tu regroupais tout dans un seul tableau mais j'ai vu un exemple de l'utilisation de ta fonction:

$i=0;//initialisation   
$jointable$i]'name'] = 'COMMANDE';//nom de la table à joindre
$jointable$i]'fields'] = 'SUM(TotalTTC) AS TotalTTC';
$jointable$i]'joinType'] = 'leouter';//inner|left|right|outer|full|cross|leouter|riouter, : type de jointure dites moi si j'oublie qq chose
//clauses de jointure de la premiere table(situation provoquant la jointure)
$j=0; //initialisation
$jointable$i]'rules']$j]'column'] = 'ClientId';//champs de la table jointe sur la quelle sera basée la comparaison(filtre de jointure)
$jointable$i]'rules']$j]'foreigntable'] = 'CLIENT';//table contenant la clé étrangère et en fonction de laquelle se fera la jointure;
$jointable$i]'rules']$j]'foreigncolumn'] = 'id';//champ (la clé etrangere) nécessaire pour le tri      
$jointable$i]'rules']$j]'relation'] = '=';// =,<>,<,>,<=,>= //operateur de comparaison des champs:(Ex: column >= foreigntable)
$x=0;                                 
$groupby$x]'table'] = 'CLIENT';                             
$groupby$x]'fields'] = array('NomClient');

Et je trouve ça assez long à mettre en place tu crées un tableau groupby et un tableau jointable et tu mets le tout dans le tableau req.

Je ne critique pas ton code je ne le trouve juste pas assez "ergonomique" si on peut dire ça de cette façon, il y a sûrement moyen de faire une fonction qui te permettrait d'y rentrer les arguments que tu veux passer au tableau jointable,groupby etc...

J'imagine que les variables $i,$j,$x sont là pour le cas où tu ferais de multiples jointures ?

on pourra en débattre toute une journée, on en finira pas. moi perso je trouve un peu maladroit d’écrire toute une fonction dont le seul rôle est de passer des paramètres à une autre, vu que dans tous les cas il faudra quand même renseigner ces parametres et leur valeur respectives. je comprend que tu sois rebuté par le tableau. mais tu sembles oublié que dans le tuto de grafikart, les données sont également passées à la méthode "find" sous forme de tableau. Je suis en plus suppris que tu remette en question le fait qu'un tableau "$req'join']" et "$req'groupby']" soit crée dans le tableau final ($req). Si tel est le cas pourquoi ne pas critiquer l'existence du tableau "$req'conditions']" et autres dans le tuto de grafikart? j'ai juste choisi tout en restant fidele à l'esprit du tuto de declarer les tableaux "$req'join']" et "$req'groupby']" à part pour les fusionner en suite au tableau ($req), encore une fois pour une meilleur lisibilité. tu trouve çà "assez long à mettre en place et pas ergonomique"? tout ce que je peut te répondre c'est qu'avec çà quelque soit la complexité de ma requête une fois que je l'ai visualisé, çà me prend moins d'une minute à mettre en place vu qu'il faut juste copier une déjà existante et de modifier les valeurs pour qu'elles correspondent à celle qu'on veut executer, tout comme on le ferait avec les requete de grafikart.
$i=>jointure multiple de table (JOIN t1 ..... LEFT JOIN t2 ......)
$j=> clauses de jointure multiples sur une table jointe (.... ON t1.id=t2.t1_id AND t1.t3_id=t3.id ....)
$k=> condition multiples portant sur une table jointe (.... WHERE t1.field1='x' AND t1.field2=t2.field3 ....)
$x=> multiple group by, rien à voir avec les jointures;

merci

Xtr3me
Auteur

Je rebute sur le fait de créer deux nouveaux tableaux en fait.
Après je critique pas le cond j'aime bien ce principe de simplement mettre les index et d'y renseigner la valeur plutôt que de taper des noms à la rallonge.

Pour la fonction qui permettrait de faire passer les arguments de jointable à la fonction find c'est justement pour éviter d'avoir à taper $i] etc à chaque fois et d'incrémenter selon le cas où l'index du tableau concerné est déjà définis.
C'est qu'une question de goût et de rangement après tout. C'est un peu comme la fonction set du Controller principal qui permet d'envoyer une variable à une vue.

Je pense comme toi que faire une fonction pour simplement passer des paramètres à une autre est maladroite mais j'ai pas encore décidé de comment j'améliorerai ma fonction, je m'y suis pas encore penché ayant d'autres projets en cours ne nécessitant pas vraiment d'approfondir la fonction.

Si je fais du changement du côté de la fonction je mettrai à jour ce topic.

à force de débattre, cela donne des idées de perfectionnement à tous les deux... j'aime ça.. et les lecteurs peuvent également en tirer profit dans les 2 codes... et pourquoi pas d'en généré un 3ième qui en fera peut-être l'unanimité :)

Mais bon, il n'existe jamais qu'une seule façon de coder et grâce aux partages on ne peut qu'évoluer.

bravo à tous les deux

Merci Grafikfan pour ton commentaire.
Mais je voulais juste rappeler à Xtr3me que je trouve qu'il est bien plus evolutif et flexible de passer les parametres à la methode find comme je le fait, càd:

$req=array( 'fields'=>'',
               'join'=>array('0'=>array('table'=>'','selectedFields'=>'','jointype'=>'','rules'=>array(),'joinconds'=>array(),),
                              '1'=>array(.........................................................) ,
                             '..'=>array(.........................................................),
                            ),
               'conditions'=>array(),
                'orderby'=>array('field'=> '', 'direction'=>''),
                'groupby'=>array('0'=>array('table'=>'','col'=>''),
                                  '1'=>array(...................),
                                    '..'=>array(...................),
                                )
                        );

plutot que de faire comme il procede:

$req=array(
                           'fields' => 'length,lengths.online',
                            'join' => 'inner',
                            'jointab' => 'lengths',
                            'on' => array('id'=>'id_product'),
                           'table' => 'lengths',
                            'conditions' => array('id_product' => $id,'category' =>$category,'sub_category' =>$sub_category,'lengths.online' => 1),
                            'orderasc' =>'length'
                            );

vous noterez qu'il y a quand même de nouveaux champs qui sont crées dans le tableaux $req, seulement une telle structure bloc toute possibilité de rendre son code flexible(plusieurs jointures par exemple). aussi je trouve que c'est une mauvaise idée de traiter le cas de "DISTINCT" en particulier dans la méthode "find" car ce n'est qu'une fonction appliquée à un champ; ou alors il faudrait traiter le cas de chacune des fonctions de mysql. comment va se comporter son code si une fonction autre que 'distinct' est passée en parametre?
que va-t-il se passer avec son code si deux ou plusieurs tables de la BDD ont des champs portant le même nom?
comment va-t'il faire s'il veut joindre plus d'une table? en cas de different types de jointure?
je pense q'au lieu lieu de critiquer mon code pour un problème (qui n'en n'est pas un d'ailleurs) de declaration tableau, il devrait s'atteler à résoudre ces problématiques que pose son code.
en ce qui me conserne, la seule limitation que je trouve à mon code pour l'instant c'est la prise en charge des sous-requêtes, et j'y travaillerai si j'ai du temps ou si j'ai un projet qui demande à ce que je les utilise.

Une bdd propre ne comporte pas deux champs ayant les mêmes noms. Mais je comprends ce que vous voulez dire en tout cas, tout les 2.

Xtr3me
Auteur

En fait j'aimerai garder une fonction find assez similaire à celle du tutoriel pour ceux qui voudraient éviter de tout changer et en plus de ça garder un appel à la fonction toujours aussi simple ^^ .
Je vois pas le problème du Distinct ça fonctionne très bien chez moi comme ça.
Pour les préfixes je les rajoute à la mano car on sait jamais si l'utilisateur veut utiliser un préfixe autre que celui penser par une fonction j'ai pas encore regardé comment ta fonction setcolumnprefix fonctionne je regarderai ça plus tard là comme je l'ai dit j'ai d'autre projet.

J'ai déjà dis que cette fonction a été faites très rapidement pour répondre à un besoin l'affaire d'une demi-heure recherche comprise sur l'ordre des requêtes sql.
J'ai bien conscience qu'elle a des défauts je l'ai d'ailleurs dit dès le premier post et mon second post ^^ . Pour les jointures multiples j'en avais pas besoin d'où l'idée de d'abord développer pour mon besoin et voir ensuite par la suite.
J'ai proposé ma fonction surtout dans le but d'aider ceux qui rencontre des difficultés pas dans le but d'avoir la fonction parfaite dans toutes les situations.
Quand j'aurai le temps promis je la referai proprement en essayant de penser à un système peu contraignant.

Je vais sûrement reprendre du Python pour le moment je ne sais pas encore quoi faire.

slt Pewel t3.id et t1.id , même nom ou pas? mais cela ne signifie pourtant pas que la bdd soit mal construite!
Xtr3me:
pour l'appel de la function je ne sais si tu trouve que la façon dont tu procède est encore plus simple que ceci:

//construction de la requete finale
$req=array( 'fields'=>'NomClient',
                'join'=>$jointable,
                'conditions'=>array('online'=>1),
                'orderby'=>array('field'=> 'id', 'direction'=>'asc'),
                'groupby'=>$groupby
                        );
$this->find($req);

qui est en gros ce que je fais, seulement dans le post précédent j'ai essayé de détailler la structure des tableaux $jointable et $groupby.
je te rappelle que ma méthode find respecte totalement la logique de celle du tuto. et d'ailleurs un simple copier-coller et tout continu à fonctionner de manière transparente pour l'utilisateur, sans aucune modification de code dans d'autres fichiers.
moi je ne suis pas contre les critiques, j'en suis même très preneur, mais seulement quand elle sont justifiées.
là ou je ne suis pas d'accord avec ta critique c'est que :
quand grafikart pose:

'conditions'=>array()
ou
'orderby'=>array(),

çà ne te choque pas,
mais quand je pose:

'join'=>array()
et
'groupby'=>array()

çà te choque.
je te ferai également remarque que ma fonction ne pense pas le nom de la table à associer aux champs mais elle utilise bien la données qui lui sont passées en paramètre. préfixer dans le cas de ma fonction c'est transformer dans une requête le nom d'un champs "f0" de la table "T" en "T.f0" afin d'éviter toute ambiguïté. et pour les utilisateurs potentiels çà ne pose aucun probleme car quand on est developpeur et qu'on veut se servir d'un code developpé par autrui on prend toujours le temps de comprendre comment il fonctionne.
c'est avec plaisir que j'accepterai tes prochaines critiques quand tu auras analysé la méthode setcolumnprefix. car un regards extérieur me permettra soit de l'optimiser soit de l’améliorer.

Ce que je voulais dire, c'est que si la bdd ne comporte pas deux champs de même noms, alors tu n'as pas besoin de préfixe.
Les bdds que je croise, que je fait et qu'on m'a appris à faire présentent des champs dont les noms sont uniques.

Généralement les champs de mes tables se présentent comme ça :

nomTable_nomChamp

Et j'ai même vu parfois des personnes qui faisaient :

nomBase_nomTable_nomChamp

Après, je n'ai pas dit que c'est LA solution. Dans le développement, tout le monde fait un peu comme il veut.
Je préfère cette façon de faire, plutôt que de s'embêter à préfixer les champs.

Cependant, je respecte ton travail.

Xtr3me
Auteur

C'est vrai que c'est un recours à cette solution de préfixe mais au final tu rallonges le nom tout autant que si tu mettais simplement un préfixe lorsque c'est nécessaire ;) .
Je regarderai plus tard ta fonction setcolumnprefix t'en fais pas ^^ . Là j'essaye d'ajouter ckeditor mais la mise en page part un peu en cacahuète alors qu'avec Tinymce il n'y avait aucun soucis le pire c'est que sous IE 9/8/7 j'ai aucun soucis mais sous IE 10 et Firefox si quelle ironie.

moi j'ai travaillé dans le cas le plus général. il n'est pas obligé que le nom d'un champs débute par celui de la table qui la contient. et moi de mon coté en tant que développeur on m'a toujours appris qu'un bon code est celui qui prend en considération le différents comportements des futurs utilisateurs potentiels.
c'est aussi une bonne idée de ne combiner le nom de la table à celui du champ que lorsque c'est nécessaire (en cas de jointure par exemple).
mais en même temps çà risque de trop rallonger la méthode find. en tout cas je verrais ce que çà donne très prochainement.
si d'aucuns trouvent la reprentation de $jointable et $groupby telle que je propose ici: http://www.grafikart.fr/forum/topic/5960.
est très complexe, ils peuvent toujours adopter celle que posté plus haut qui en fait revient à la même chose.

Xtr3me
Auteur

Pareil pour moi on est pas obligé de marquer le nom de la table sauf quand il y a ambiguïté mais j'avoue que le fait d'être limité à une seule jointure est limitant.
Je pense un peu trop avec ma logique propre à moi en fait j'ai toujours eu une façon de penser/de faire différente de celle commune.
Une question pourquoi au lieu de faire un simple $this->Client->find(); tu rajoutes $this->table = 'CLIENT'; ?
Car si tu suis la logique du MVC initial en faisant $this->Client->find(); le Select se fera sur la table client.

la logique reste exactement la même que dans le tuto. le $this->table = 'CLIENT' a été mis juste pour des besoins d'explication par rapport à la requete exemple que j'avais à executer afin que le lecteur ne soit pas perdu. mais il est clair que pour une utilisation dans le mvc on n'a pas à poser celà.
tu constateras que je fait egalement "echo find($req);", simplement parce que pour illustrer ces exemples j'ai isolé les methodes concernées de la classe pour les transformer en fonction afin de les executer indépendament du reste du systeme mvc.

Xtr3me
Auteur

Ok c'était donc juste pour la démonstration ^^ .

effectivement

salut Xtr3me
après notre discussion de la dernière fois,
le fait que mon code ( ici)rallongeait finalement le nom du champs( car transformation de col de T en T.col ) à traiter avait été signaler comme une problème.
j'avais donc promis de revoir ma méthode "find".
mais avant de me lancer j'ai voulu savoir si c’était justifier, et j'ai donc posé la question ici: http://www.developpez.net/forums/d1365476/bases-donnees/mysql/optimisation-performance-requete-mysql/

et finalement je vais laisser mon code telle quel vu que çà n'a aucune conséquence sur les performances ni le temps d’exécution de la requête,
ce que je pensait d'ailleurs mais je ne voulait pas dire une bêtise.
et sais pour l'avoir lu quelque part (mais je retrouve plus la source) que pour PgSql, cette représentation rend la requête plus rapide, car la recherche de la table devient plus rapide.
merci.

Xtr3me
Auteur

Ok j'avais pas pensé à l'aspect performances en particulier mais à l'appel de la fonction surtout après ta fonction ne me dérange pas le moins du monde c'est la façon de l'utiliser qui ne me plaisait pas.

Mais je penses que crée 3 tableaux ainsi:

$jointable = array('rules'=> 'machin');

Et j'en passe n'est pas un véritable problème au final parce que je vois mal comment faire autrement si ce n'est en créant un système avec un tableau unique à passer dans une fonction qui se chargera de traiter les différentes informations afin de les trier comme souhaité.