__'; const COND_INF = '__<__'; const COND_SUP = '__>__'; const COND_INFEQ = '__<=__'; const COND_SUPEQ = '__>=__'; const COND_LIKE = '__LIKE__'; const COND_IN = '__IN__'; // {2} Fonctions d'aggrégation // const SEL_AVG = '__AVG__'; const SEL_SUM = '__SUM__'; const SEL_MAX = '__MAX__'; const SEL_MIN = '__MIN__'; const SEL_COUNT = '__COUNT__'; const SEL_CONCAT = '__GROUP_CONCAT__'; const SEL_DISTINCT = true; // {3} Gestion du Order By // const ORDER_ASC = '__ASC__'; const ORDER_DESC = '__DESC__'; // {3} Constantes d'insertion // const INSERT_DEFAULT = '__DEFAULT__'; // Valeur DEFAULT (pour insertion) const NULL = '__NULL__'; // Valeur DEFAULT (pour insertion) /* Attributs */ private $driver; // Database driver label private $where; // Tableau associatif contenant les conditions private $select; // Tableau contenant la liste des champs à afficher private $orderby; // Tableau contenant la liste des orderby private $unique; // VRAI si on attend une valeur unique private $schema; // Tableau contenant les informations associées aux données private $joined; // Tableau contenant les Rows liés /* CONSTRUCTEUR * * @schema Tableau contenant les informations de la requête * @driver [optional] DatabaseDriver label * */ public function __construct($schema, $driver=null){ /* (1) Database Driver */ $this->driver = $driver; /* (2) On récupère les informations */ $this->schema = $schema; /* (3) On initialise les conditions */ $this->where = []; /* (4) On initialise les champs à retourner */ $this->select = []; /* (5) On initialise l'ordonnancement' */ $this->orderby = []; /* (6) On initialise le caractère 'unique' du résultat */ $this->unique = false; /* (7) On initialise les jointures */ $this->joined = []; } /* FILTRE LES ENTREES D'UNE TABLE AVEC LA CLE PRIMAIRE SPECIFIEE * * @primary Clé primaire simple * OU * @primary Clé primaire composée * * @return Rows Tableau contenant toutes les entrées de la table * */ public function whereId($primary){ /* [0] Vérification des paramètres =========================================================*/ if( $primary == null ) return $this; /* [1] On récupère les clés primaires =========================================================*/ $keys = []; foreach($this->schema['columns'] as $k=>$v) if( $v['primary'] ) $keys[] = $k; /* [2] Si clé simple =========================================================*/ /* (1) On met au même format qu'une clé composée */ if( count($keys) == 1 ) $primary = [ $primary ]; /* [3] Si clé composée =========================================================*/ $defaultWhere = $this->where; /* (1) Pour chaque clé, On vérifie les TYPES */ foreach($keys as $i=>$key){ $inCond = is_array($primary[$i]) && count($primary[$i]) >= 2 && is_array($primary[$i][0]) && $primary[$i][1] == self::COND_IN; /* (1) Si c'est une condition "IN" ---------------------------------------------------------*/ if( $inCond ){ /* (1) On vérifie le type de chaque valeur du IN */ $type = $this->schema['columns'][$key]['type']; foreach($primary[$i][0] as $value){ if( $type == 'int' && !is_numeric($value) ){ $this->where = $defaultWhere; return $this; } if( $type == 'float' && !is_numeric($value) ){ $this->where = $defaultWhere; return $this; } if( in_array($type, ['text', 'varchar']) && !is_string($value) ){ $this->where = $defaultWhere; return $this; } } /* (2) Si c'est une condition "simple" ---------------------------------------------------------*/ }else{ /* (1) Si le type de condition est manquant, on met EQUAL par défaut */ if( !is_array($primary[$i]) ) $primary[$i] = [ $primary[$i], self::COND_EQUAL ]; /* (2) On vérifie le type de chaque valeur */ $type = $this->schema['columns'][$key]['type']; if( $type == 'int' && !is_numeric($primary[$i][0]) ){ $this->where = $defaultWhere; return $this; } if( $type == 'float' && !is_numeric($primary[$i][0]) ){ $this->where = $defaultWhere; return $this; } if( in_array($type, ['text', 'varchar']) && !is_string($primary[$i][0]) ){ $this->where = $defaultWhere; return $this; } } /* (6) Si type OK, on enregistre la condition */ if( !isset($this->where[$key]) ) $this->where[$key] = []; /* (7) On ajoute la condition */ $this->where[$key][] = $primary[$i]; } /* [4] On renvoie l'object courant =========================================================*/ return $this; } /* FILTRAGE DYNAMIQUES * * @method Nom de la méthode * @parameter Valeur du paramètre * @parameter Valeur du paramètre + type de vérification (tableau) * * @return this Retourne l'object courant * */ public function __call($m, $a){ /* [0] On vérifie que la requête est du type 'getBy{Attribute}' =========================================================*/ if( !preg_match('/^where(.+)$/', $m, $regex) ) // si requête incorrecte, on ne fais rien return $this; /* [1] On récupère le nom de la colonne =========================================================*/ $column_name = ''; /* (1) formatte la requête 'MyAttribute' -> 'my_attribute' */ for( $l = 0, $ll = strlen($regex[1]) ; $l < $ll ; $l++ ){ $letter = $regex[1][$l]; // Si la lettre est en majuscule mais que c'est pas la première ni un seul mot if( strtoupper($letter) == $letter && $l > 0 ) $column_name .= '_'; $column_name .= strtolower($letter); } /* (2) On vérifie que la colonne existe */ if( !isset($this->schema['columns'][$column_name]) ) return $this; // si n'existe pas, on ne fait rien /* [2] On vérifie le type du paramètre =========================================================*/ // On délègue $args = array_merge([$column_name], $a); return call_user_func_array([$this, 'where'], $args); } public function where($field){ // get arguments $args = array_slice(func_get_args(), 1); /* [1] Vérification de l'argument @field =========================================================*/ /* (1) Type de @field */ if( !is_string($field) ) return $this; /* (2) On vérifie que la colonne existe */ if( !isset($this->schema['columns'][$field]) ) return $this; // si n'existe pas, on ne fait rien /* [2] On vérifie le type du paramètre =========================================================*/ /* (1) Si au moins 1 param */ if( count($args) < 1 ) return $this; /* [3] If `IN` condition =========================================================*/ $defaultWhere = $this->where; $inCond = count($args[0]) > 1 && is_array($args[0][0]) && $args[0][1] == self::COND_IN; // erreur if( is_array($args[0][0]) && !$inCond ) return $this; /* (1) Si c'est une condition "IN" ---------------------------------------------------------*/ if( $inCond ){ /* (1) On vérifie le type de chaque valeur du IN */ $type = $this->schema['columns'][$field]['type']; foreach($args[0][0] as $value){ if( $type == 'int' && !is_numeric($value) ) return $this; if( $type == 'float' && !is_numeric($value) ) return $this; if( in_array($type, ['text', 'varchar']) && !is_string($value) ) return $this; } /* (2) Si c'est une condition "simple" ---------------------------------------------------------*/ }else{ /* (1) Si le type de condition est manquant, on met EQUAL par défaut */ if( !is_array($args[0]) ) $args[0] = [ $args[0], self::COND_EQUAL ]; /* (2) On vérifie le type de chaque valeur */ $type = $this->schema['columns'][$field]['type']; if( !is_null($args[0][0]) ){ if( $type == 'int' && !is_numeric($args[0][0]) ) return $this; if( $type == 'float' && !is_numeric($args[0][0]) ) return $this; if( in_array($type, ['text', 'varchar']) && !is_string($args[0][0]) ) return $this; } } /* [3] Si type OK, on enregistre la condition =========================================================*/ /* (1) Si aucune condition pour ce champ, on crée un tableau */ if( !isset($this->where[$field]) ) $this->where[$field] = []; /* (2) On ajoute la condition */ $this->where[$field][] = $args[0]; // On retourne l'object courant return $this; } /* SELECTIONNE UNIQUEMENT LE CHAMP SELECTIONNE * * @field Libellé du champ à afficher * @func Fonction d'aggrégation (ou NULL) * @distinct Clause DISTINCT * @alias Alias du champ * * @return this Retourne l'object courant * */ public function select($field=null, $func=null, $distinct=false, $alias=null){ /* [1] On formatte les champs =========================================================*/ /* (1) On vérifie le type de @field */ if( !is_string($field) ) return $this; /* (2) On vérifie que la colonne @field existe, sinon on quitte */ if( !isset($this->schema['columns'][$field]) && $field != '*' ) return $this; /* (3) On vérifie @func */ $funcList = [self::SEL_AVG, self::SEL_SUM, self::SEL_MAX, self::SEL_MIN, self::SEL_COUNT, self::SEL_CONCAT]; // Si condition non nulle et pas référencée, on quitte if( !is_null($func) && !in_array($func, $funcList) ) return $this; // If CONCAT -> force type to TEXT if( $func === Rows::SEL_CONCAT ) $this->schema['columns'][$field]['type'] = 'text'; /* (4) On met la valeur par défaut à @distinct si type mauvais */ $distinct = !is_bool($distinct) ? false : $distinct; /* (5) Si @alias incorrect, on met @field par défaut */ if( !is_string($alias) ) $alias = $field; /* [2] On enregistre le champ =========================================================*/ /* (1) Si "SELECT *" on ajout tous les champs */ if( $field === '*' ){ foreach($this->schema['columns'] as $f=>$c) if( !isset($this->select[$f]) ) $this->select[$f] = [$func, $distinct, $f]; /* (2) Si aucun SELECT pour ce champ, on le crée */ }else{ if( !isset($this->select[$field]) ) $this->select[$field] = [$func, $distinct, $alias]; } /* [3] On retourne l'object courant =========================================================*/ return $this; } /* SELECTIONNE L'ORDONNANCEMENT DES RESULTATS * * @field Libellé du champ à afficher * @order Gestion de l'ordre ASC/DESC (ou NULL) * * @return this Retourne l'object courant * */ public function orderby($field=null, $order=null){ /* [1] On formatte les champs =========================================================*/ /* (1) On vérifie le type de @field */ if( !is_string($field) ) return $this; /* (2) On vérifie que la colonne @field existe, sinon on quitte */ if( !isset($this->schema['columns'][$field]) && $field != '*' ) return $this; /* (3) On vérifie @order */ $orderList = [self::ORDER_ASC, self::ORDER_DESC]; // Valeur si NULL $order = is_null($order) ? $orderList[0] : $order; // Si ordre non référencée, on quitte if( !in_array($order, $orderList) ) return $this; /* [2] On enregistre le champ =========================================================*/ /* (1) On crée le ORDER_BY pour ce champ */ $this->orderby[$field] = $order; /* [3] On retourne l'object courant =========================================================*/ return $this; } /* JOINT UNE SECONDE TABLE () * * @localField Nom d'une colonne locale * @rows Rows d'une autre table * * @return this Retourne l'object courant * */ public function join($localField, $rows){ /* [0] Vérification / Formattage des paramètres =========================================================*/ /* (1) Si le champ n'est pas au bon format */ if( !is_string($localField) ) return $this; /* (2) Si @rows n'est pas au bon format */ if( !($rows instanceof Rows) ) return $this; /* (3) Si le champ n'existe pas dans la table */ if( !isset($this->schema['columns'][$localField]) ) return $this; /* (4) On récupère les données du champ local dans une variable */ $localFieldData = $this->schema['columns'][$localField]; /* [1] On vérifie que la clé étrangère est correcte =========================================================*/ /* (1) Si la colonne n'existe pas et qu'elle n'est pas primaire, on ne fait rien */ if( !isset($localFieldData['references']) && !$localFieldData['primary'] ) return $this; /* (2) On vérifie que la colonne a une référence vers la table de @rows */ $referencesToRows = isset($localFieldData['references']) && $localFieldData['references'][0] == $rows->schema['table']; $rowsField = null; /* (3) On vérifie que la colonne est la référence d'un champ de @rows */ $referencesFromRows = false; // On vérifie chaque champ de @rows foreach($rows->schema['columns'] as $field=>$data) // Si un champ de la table de @rows a pour référence le champ local if( isset($data['references']) && $data['references'][0] == $this->schema['table'] && $data['references'][1] == $localField ){ $referencesFromRows = true; $rowsField = $field; break; } /* (4) On vérifie que la colonne a la même référence qu'une colonne de @rows */ $referencesSameTarget = false; // On vérifie toutes les colonnes de @rows foreach($rows->schema['columns'] as $field=>$data) // Si on trouve un champ avec la même référence if( isset($data['references']) && isset($localFieldData['references']) && count(array_diff($data['references'], $localFieldData['references'])) == 0 ){ $referencesSameTarget = true; $rowsField = $field; // On enregistre le champ qui a la même cible break; } /* (4) Si aucune référence en commun, on ne fait rien */ if( !$referencesToRows && !$referencesFromRows && !$referencesSameTarget ) return $this; /* [2] On enregistre la référence =========================================================*/ $this->joined[$localField] = [ 'object' => $rows, 'field' => is_null($rowsField) ? $localFieldData['references'][1] : $rowsField // On met le nom du champ de @rows à lier ]; /* [3] On retourne l'object courant =========================================================*/ return $this; } /* PERMET DE DIRE QUE L'ON VEUT UN RESULTAT UNIQUE * * @return this Retourne l'object courant * */ public function unique(){ /* [1] On enregistre le choix =========================================================*/ $this->unique = true; /* [2] On retourne l'object courant =========================================================*/ return $this; } /* MODIFIE DES ENTREES (SANS MODIFICATION DE CLE PRIMAIRE POSSIBLE) * * @updates Tableau associatif contenant les nouvelles valeurs * * @return updated Retourne si TRUE/FALSE la modification a bien été faite * */ public function edit($updates){ /* [0] Vérification des paramètres =========================================================*/ /* (1) Si c'est pas un tableau, erreur */ if( !is_array($updates) ) return false; /* (2) On retire les champ inconnus / clés primaires */ $cleared = []; // Pour chaque entrée du tableau foreach($updates as $field=>$value) if( isset($this->schema['columns'][$field]) && !$this->schema['columns'][$field]['primary'] ) // Champ existe et n'est pas clé primaire $cleared[$field] = $value; /* (3) On vérifie les types des champs */ foreach($cleared as $field=>$value){ // let null values if( is_null($value) ) continue; $type = $this->schema['columns'][$field]['type']; // {1} Si de type INT/FLOAT et pas numérique, on retire le champ // if( in_array($type, ['int', 'float']) && !is_numeric($value) ) unset($cleared[$field]); // {2} Si de type TEXT/VARCHAR et pas string, on retire le champ // if( in_array($type, ['text', 'varchar']) && !is_string($value) ) unset($cleared[$field]); } /* (4) Si on a plus de champ, on retourne l'object courant */ if( count($cleared) == 0 ) return false; /* [1] Initialisation des paramètres =========================================================*/ /* (1) On initialise la requête */ $requestS = []; /* (2) On initialise les paramètres */ $bound = []; /* [2] Rédaction de la clause UPDATE =========================================================*/ $requestS['UPDATE'] = SQLBuilder::UPDATE($this->schema['table']); /* [3] Rédaction de la clause SET =========================================================*/ /* (1) On met tout les champs à modifier */ $requestS['SET'] = SQLBuilder::SET($cleared, $bound); /* [4] On rédige la clause WHERE/AND =========================================================*/ /* (1) On met les conditions locales */ $requestS['WHERE'] = []; $c = 0; foreach($this->where as $field=>$conditions) foreach($conditions as $cdt=>$value){ if( $value[1] == self::COND_IN ) // Si condition de type IN $requestS['WHERE'][$c] = SQLBuilder::IN([$this->schema['table'], $field], $value[0], $c, $bound); else // Sinon $requestS['WHERE'][$c] = SQLBuilder::WHERE([$this->schema['table'], $field], $value, $c, $bound); $c++; } /* (2) On ajoute les jointures */ // Note: On ajoute les requêtes des tables de jointures dans la clause WHERE // foreach($this->joined as $field=>$data){ // {1} On récupère la requête/les params de chaque jointure // $joinedFetched = $data['object']->fetch(false); // {2} On met la clé étrangère pour la clause SELECT // $joinedFetched['request']['SELECT'] = [ $data['object']->schema['table'].'.'.$data['field'] ]; // {3} On construit la nouvelle requête // $joinedRequest = SQLBuilder::BUILD($joinedFetched['request']); // {4} On supprime les retours à la ligne // $joinedRequest = str_replace("\n", " ", $joinedRequest); // {5} On l'ajoute à la clause FROM avec comme alias le nom de la table de @data['object'] // $requestS['WHERE'][] = $this->schema['table'].".$field in ($joinedRequest)"; // {6} On ajoute les variables à la requête courante // $bound = array_merge($bound, $joinedFetched['bound']); } /* [5] Clause LIMIT =========================================================*/ $requestS['LIMIT'] = ($this->unique) ? SQLBuilder::LIMIT(1) : SQLBuilder::LIMIT([]); /* [6] On prépare et compose la requête =========================================================*/ /* (1) On compose la requête */ $requestString = SQLBuilder::BUILD($requestS).';'; /* (2) On prépare la requête */ $request = DatabaseDriver::getPDO($this->driver)->prepare($requestString); /* [7] On exécute la requête et retourne le résultat =========================================================*/ /* (1) On exécute la requête */ $updated = $request->execute($bound); /* (2) On retourne l'état de la requête */ return $updated; } /* AJOUTE UNE ENTREE DANS LA TABLE * * @entry Tableau associatif de la forme (colonne => valeur) * OU * @entries Tableau de la forme ([entry1, entry2]) * * @return status Retourne si TRUE ou FALSE les entrées ont bien été supprimées * */ public function insert($entry){ /* [0] On vérifie les paramètres =========================================================*/ /* (1) Si c'est pas un tableau avec au moins une entrée, erreur */ if( !is_array($entry) || count($entry) == 0 ) return false; // S'il n'y a qu'une entrée, on met au même format que s'il y en avait plusieurs $firstIndex = array_keys($entry)[0]; if( !is_array($entry[$firstIndex]) ) $entry = [ $entry ]; /* (2) On retire les champ inconnus */ $cleared = []; // Pour chaque entrée du tableau foreach($entry as $i=>$set){ $cleared[$i] = []; foreach($set as $field=>$value){ if( isset($this->schema['columns'][$field]) ) // Champ existe $cleared[$i][$field] = $value; } } /* (3) On vérifie les types des champs */ foreach($cleared as $i=>$set){ foreach($set as $field=>$value){ $type = $this->schema['columns'][$field]['type']; // {1} Si de type INT/FLOAT et pas numérique, on retire le champ // if( in_array($type, ['int', 'float']) && !is_numeric($value) && $value != self::INSERT_DEFAULT ) unset($cleared[$i][$field]); // {2} Si de type TEXT/VARCHAR et pas string, on retire le champ // if( in_array($type, ['text', 'varchar']) && !is_string($value) && $value != self::INSERT_DEFAULT ) unset($cleared[$i][$field]); } /* (4) Si il manque des données, erreur */ if( count($cleared[$i]) != count($this->schema['columns']) ) return false; } /* [1] On crée la requête =========================================================*/ /* (1) Clause INSERT INTO table */ $requestS = 'INSERT INTO '.$this->schema['table']."("; /* (2) Clause : table(col1, col2, ...) */ $c = 0; foreach($this->schema['columns'] as $field=>$value){ if( $c > 0 ) $requestS .= ', '; $requestS .= $field; $c++; } // Fin de clause $requestS .= ")\n"; /* (3) Clause : VALUES(val1, val2, ...) */ $v = 0; foreach($cleared as $i=>$set){ if( $v == 0 ) $requestS .= 'VALUES('; else $requestS .= ",\n\t("; $c = 0; foreach($this->schema['columns'] as $field=>$column){ if( $c > 0 ) $requestS .= ', '; // Si l'entrée est donnée if( isset($set[$field]) ) if( $set[$field] == self::INSERT_DEFAULT ) $requestS .= 'DEFAULT'; // On insère directement les valeurs 'DEFAULT' else $requestS .= ':insert_'.$field.'_'.$i; else $requestS .= 'DEFAULT'; $c++; } // Fin de clause $requestS .= ")"; $v++; } /* [2] On bind les paramètres et exécute la requête =========================================================*/ /* (0) On initialise la requête et les paramètres */ $request = DatabaseDriver::getPDO($this->driver)->prepare($requestS.';'); $bound = []; /* (1) On bind les paramètres */ foreach($cleared as $i=>$set) foreach($this->schema['columns'] as $field=>$column) if( isset($set[$field]) && $set[$field] != self::INSERT_DEFAULT ) $bound[':insert_'.$field.'_'.$i] = $set[$field]; /* [3] On exécute la requête et envoie le status =========================================================*/ $inserted = $request->execute($bound); // On retourne le status return $inserted; } /* SUPPRIME LES ENTREES * * @return status Retourne si TRUE ou FALSE les entrées ont bien été supprimées * */ public function delete(){ /* [0] Initialisation des paramètres =========================================================*/ /* (1) On initialise la requête */ $requestS = []; /* (2) On initialise les paramètres */ $bound = []; /* [1] Clause DELETE FROM =========================================================*/ $requestS['DELETE'] = SQLBuilder::DELETE($this->schema['table']); /* [2] On rédige la clause WHERE/AND =========================================================*/ /* (1) On met les conditions locales */ $requestS['WHERE'] = []; $c = 0; foreach($this->where as $field=>$conditions) foreach($conditions as $cdt=>$value){ if( $value[1] == self::COND_IN ) // Si condition de type IN $requestS['WHERE'][$c] = SQLBuilder::IN([$this->schema['table'], $field], $value[0], $c, $bound); else // Sinon $requestS['WHERE'][$c] = SQLBuilder::WHERE([$this->schema['table'], $field], $value, $c, $bound); $c++; } /* (2) On ajoute les jointures */ // Note: On ajoute les requêtes des tables de jointures dans la clause WHERE // foreach($this->joined as $field=>$data){ // {1} On récupère la requête/les params de chaque jointure // $joinedFetched = $data['object']->fetch(false); // {2} On met la clé étrangère pour la clause SELECT // $joinedFetched['request']['SELECT'] = [ $data['object']->schema['table'].'.'.$data['field'] ]; // {3} On construit la nouvelle requête // $joinedRequest = SQLBuilder::BUILD($joinedFetched['request']); // {4} On supprime les retours à la ligne // $joinedRequest = str_replace("\n", " ", $joinedRequest); // {5} On l'ajoute à la clause FROM avec comme alias le nom de la table de @data['object'] // $requestS['WHERE'][] = $this->schema['table'].".$field in ($joinedRequest)"; // {6} On ajoute les variables à la requête courante // $bound = array_merge($bound, $joinedFetched['bound']); } /* [3] Clause LIMIT =========================================================*/ $requestS['LIMIT'] = ($this->unique) ? SQLBuilder::LIMIT(1) : SQLBuilder::LIMIT([]); /* [4] On prépare et compose la requête =========================================================*/ /* (1) On compose la requête */ $requestString = SQLBuilder::BUILD($requestS).';'; /* (2) On prépare la requête */ $request = DatabaseDriver::getPDO($this->driver)->prepare($requestString); /* [5] On exécute la requête et retourne le résultat =========================================================*/ /* (1) On exécute la requête */ $deleted = $request->execute($bound); /* (2) On retourne l'état de la requête */ return $deleted; } /* RETOURNE LES DONNEES / NULL si une erreur survient * * @execute VRAI si on veut exécuter la requête, sinon renvoie [requete, boundParams] * * @return data Tableau contenant les champs sélectionnés * @return data Valeur du champ sélectionné (si 1 seul champ) * @return ERROR Retourne FALSE si rien n'est trouvé * */ public function fetch($execute=true){ /* [0] On initialise =========================================================*/ /* (1) On initialise la requête */ $requestS = []; /* (2) On initialise le conteneur des variables "bindés" */ $bound = []; /* (3) On récupère la requête générée par chaque @rows de jointure */ $joinedFetched = []; foreach($this->joined as $field=>$data) $joinedFetched[$field] = $data['object']->fetch(false); /* [1] On rédige la clause SELECT =========================================================*/ /* (1) On formatte les données */ $selectTables = []; /* (2) On ajoute les champs locaux */ $selectTables[$this->schema['table']] = $this->select; /* (4) On ajoute les champs des jointures (récursif)*/ foreach($joinedFetched as $field=>$data){ foreach($data['request']['SELECT'] as $table=>$fields) foreach($fields as $field=>$sel){ // Si aucune entrée pour cette table, on l'ajoute if( !isset($selectTables[$table]) ) $selectTables[$table] = []; $selectTables[$table][$field] = $sel; } } /* (3) On génère la clause SELECT */ $requestS['SELECT'] = SQLBuilder::SELECT($selectTables); /* [2] On rédige la clause FROM ========================================================*/ /* (0) On initialise la clause */ $requestS['FROM'] = []; /* (1) Table locale */ $requestS['FROM'][] = $this->schema['table']; /* (2) On ajoute les tables de jointures */ // Note: On ajoute les tables de jointures dans la clause FROM avec comme alias le nom de la table foreach($joinedFetched as $field=>$data) // On ajoute la clause FROM de jointure à la clause FROM locale // $requestS['FROM'] = array_merge($data['request']['FROM'], $requestS['FROM']); /* [5] On rédige la clause WHERE/AND =========================================================*/ /* (1) On met les conditions locales */ $c = 0; $requestS['WHERE'] = []; foreach($this->where as $field=>$conditions) foreach($conditions as $cdt=>$value){ if( $value[1] === self::COND_IN ) // Si condition IN $requestS['WHERE'][$c] = SQLBuilder::IN([$this->schema['table'], $field], $value[0], $c, $bound); else // Sinon $requestS['WHERE'][$c] = SQLBuilder::WHERE([$this->schema['table'], $field], $value, $c, $bound); $c++; } /* (2) On ajoute les jointures */ foreach($this->joined as $localField=>$data){ $requestS['WHERE'][$c] = $this->schema['table'].".$localField = ".$data['object']->schema['table'].".".$data['field']; $c++; } /* (3) On ajoute les conditions des jointures */ foreach($joinedFetched as $field=>$data){ /* On ajoute la clause WHERE de jointure à la clause WHERE locale */ $requestS['WHERE'] = array_merge($data['request']['WHERE'], $requestS['WHERE']); /* On ajoute les variables à la requête courante */ $bound = array_merge($bound, $data['bound']); } /* [6] Clause GROUP BY =========================================================*/ /* (0) On initialise la liste des @rows non aggrégés */ $groupBy = []; /* (1) On cherche dans les champs locaux local */ foreach($selectTables as $table=>$fields) foreach($fields as $field=>$sel) // Si aucune fonction d'aggrégation if( is_null($sel[0]) ){ if( !isset($groupBy[$table]) ) $groupBy[$table] = []; // Si le champ est *, on trouve les clés primaires if( $field == '*' ){ $columns = Table::get($table)->schema['columns']; foreach($columns as $col=>$data) if( $data['primary'] ) $groupBy[$table][] = $col; }else $groupBy[$table][] = $field; $groupBy[$table] = array_unique($groupBy[$table]); } /* (2) On rédige la clause GROUP BY */ if( count($groupBy) > 0) $requestS['GROUPBY'] = SQLBuilder::GROUPBY($groupBy); /* [6] Clause ORDER BY =========================================================*/ /* (1) On formatte les données */ $orderTables = []; /* (2) On ajoute les champs locaux */ if( count($this->orderby) > 0 ) $orderTables[$this->schema['table']] = $this->orderby; /* (4) On ajoute les champs des jointures (récursif)*/ foreach($joinedFetched as $field=>$data){ foreach($data['request']['ORDERBY'] as $table=>$fields) // pour chaque ensemble de champ de chaque table foreach($fields as $field=>$orderBy) // Pour chaque orderby de chaque champ if( count($orderBy) > 0 ) $orderTables[$table][$field] = $orderBy; } /* (3) On génère la clause SELECT */ $requestS['ORDERBY'] = SQLBuilder::ORDERBY($orderTables); /* [6] Clause LIMIT =========================================================*/ $requestS['LIMIT'] = ($this->unique) ? SQLBuilder::LIMIT(1) : SQLBuilder::LIMIT([]); /* [7] On compose/prépare la requête =========================================================*/ /* (1) Si on veut pas exécuter on renvoie la requête + boundParams */ if( !$execute ) return [ 'request' => $requestS, 'bound' => $bound]; /* (2) On compose la requête */ $requestString = SQLBuilder::BUILD($requestS).';'; // var_dump($requestString); /* (3) On prépare la requête */ $request = DatabaseDriver::getPDO($this->driver)->prepare($requestString); /* [8] On exécute la requête et retourne le résultat =========================================================*/ /* (1) On exécute la requête */ $request->execute($bound); /* (2) Si unique */ if( $this->unique ) return $this->format( $request->fetch() ); /* (3) Si tout */ return $this->format( $request->fetchAll() ); } /* ON FORMATTE LES DONNEES DE SORTIE * * @data Données / Tableau de données * * @return formatted Données formattées / Tableau de données formatté * */ private function format($data){ /* [0] On initialise le processus =========================================================*/ /* (0) Initialisation du retour */ $formatted = $data; /* (1) On vérifie qu'il s'agit d'un tableau (non vide) */ if( !is_array($formatted) || count($formatted) < 1 ) return $formatted; /* (2) On regarde si c'est des données simples */ $twoDimensions = is_array($formatted[0]); /* (3) On regarde s'il s'agit d'un Tableau de données en bonne et due forme */ if( $twoDimensions ){ $sameKeys = true; // VRAI si chaque entrée a les mêmes clés $last_keys = null; // Clés de l'entrée précédente foreach($formatted as $i=>$entry){ if( !is_null($last_keys) && count(array_diff(array_keys($entry), $last_keys)) > 0 ){ // Si différent du précédent, ducoup on est pas bon $sameKeys = false; break; } $last_keys = array_keys($entry); } // Si pas les mêmes clés, on a une erreur if( !$sameKeys ) return $formatted; } /* [1] On retire les doublons à indices numériques =========================================================*/ /* (1) Si 1 dimensions, on met en 2 pour traiter tout de la même manière */ if( !$twoDimensions ) $formatted = [$formatted]; /* (2) On récupère les noms des champs à partir des select (alias) */ // {1} On récupère les colonnes locales // $existingColumns = []; foreach($this->select as $field=>$data) $existingColumns[$data[2]] = $this->schema['columns'][$field]; // {2} On ajoute les colonnes des jointures // foreach($this->joined as $j) foreach($j['object']->select as $field=>$data) $existingColumns[$data[2]] = $j['object']->schema['columns'][$field]; // {3} On vérifie chaque clé, si c'est une colonne qui existe // foreach($formatted as $i=>$entry) // Pour chaque champ foreach($entry as $index=>$value){ // Si la colonne existe on applique le type if( isset($existingColumns[$index]) ){ if( $existingColumns[$index]['type'] == 'int' ) $formatted[$i][$index] = intval( $value ); elseif( $existingColumns[$index]['type'] == 'float' ) $formatted[$i][$index] = floatval( $value ); // String utf8 management elseif( \mb_detect_encoding($value) === 'UTF-8' ) $formatted[$i][$index] = $value; else $formatted[$i][$index] = utf8_encode($value); // Si pas non plus une aggrégation et si indice numérique, on le retire }else if( !preg_match('/^agg_.+/', $index) && is_numeric($index) ) unset($formatted[$i][$index]); } /* (3) On remet 1 dimension si 1 dimension à la base */ if( !$twoDimensions ) $formatted = $formatted[0]; /* [2] On retourne le résultat =========================================================*/ return $formatted; } } ?>