'; const COND_INF = '<'; const COND_SUP = '>'; const COND_INFEQ = '<='; const COND_SUPEQ = '>='; const COND_LIKE = 'LIKE'; /* Attributs */ private $where; // Tableau associatif contenant les conditions private $select; // Tableau contenant la liste des champs à afficher 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 * */ public function __construct($schema){ /* (1) On récupère les informations */ $this->schema = $schema; /* (2) On initialise les conditions */ $this->where = []; /* (3) On initialise les champs à retourner */ $this->select = []; /* (4) On initialise le caractère 'unique' du résultat */ $this->unique = false; /* (5) 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 =========================================================*/ if( count($keys) == 1 ){ /* (1) Si on a juste la valeur, on ajoute le type de condition */ if( !is_array($primary) ) $primary = [ $primary, self::COND_EQUAL ]; /* (2) Si type INT et pas numérique */ if( $this->schema['columns'][$keys[0]]['type'] == 'int' && !is_numeric($primary[0]) ) return $this; /* (3) Si type FLOAT et pas numérique */ if( $this->schema['columns'][$keys[0]]['type'] == 'float' && !is_numeric($primary[0]) ) return $this; /* (4) Si type STRING et pas string */ if( $this->schema['columns'][$keys[0]]['type'] == 'text' && !is_string($primary[0]) ) return $this; /* (5) Si type OK, on enregistre la condition */ // {1} Si aucune condition pour ce champ, on crée un tableau // if( !isset($this->where[$keys[0]]) ) $this->where[$keys[0]] = []; // {2} On ajoute la condition // $this->where[$keys[0]][] = $primary; /* [3] Si clé composée =========================================================*/ }else{ $defaultWhere = $this->where; /* (1) Pour chaque clé, On vérifie les TYPES */ foreach($keys as $i=>$key){ /* (2) Si c'est un paramètre seul, on ajoute par défaut self::COND_EQUAL */ if( !is_array($primary[$i]) ) $primary[$i] = [ $primary[$i], self::COND_EQUAL ]; /* (3) Si type INT et pas numérique */ if( $this->schema['columns'][$column_name]['type'] == 'int' && !is_numeric($primary[$i][0]) ){ $this->where = $defaultWhere; // On réinitialise les conditions si au moins 1 est fausse return $this; } /* (4) Si type FLOAT et pas numérique */ if( $this->schema['columns'][$column_name]['type'] == 'float' && !is_numeric($primary[$i][0]) ){ $this->where = $defaultWhere; // On réinitialise les conditions si au moins 1 est fausse return $this; } /* (5) Si type STRING et pas string */ if( $this->schema['columns'][$column_name]['type'] == 'text' && !is_string($primary[$i][0]) ){ $this->where = $defaultWhere; // On réinitialise les conditions si au moins 1 est fausse return $this; } /* (6) Si type OK, on enregistre la condition */ // {1} Si aucune condition pour ce champ, on crée un tableau // if( !isset($this->where[$key]) ) $this->where[$key] = []; // {2} 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 ; $l < strlen($regex[1]) ; $l++ ){ $letter = $regex[1][$l]; // Si la lettre est en majuscule mais que c'est pas la première 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 =========================================================*/ /* (1) Si aucun param, on quitte */ if( count($a) == 0 ) return $this; /* (2) Si c'est un paramètre seul, on ajoute par défaut self::COND_EQUAL */ if( !is_array($a[0]) ) $a[0] = [ $a[0], self::COND_EQUAL ]; /* (3) Si type INT et pas numérique */ if( $this->schema['columns'][$column_name]['type'] == 'int' && !is_numeric($a[0][0]) ) return $this; /* (4) Si type FLOAT et pas numérique */ if( $this->schema['columns'][$column_name]['type'] == 'float' && !is_numeric($a[0][0]) ) return $this; /* (5) Si type STRING et pas string */ if( $this->schema['columns'][$column_name]['type'] == 'text' && !is_string($a[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[$column_name]) ) $this->where[$column_name] = []; /* (2) On ajoute la condition */ $this->where[$column_name][] = $a[0]; // On retourne l'object courant return $this; } /* SELECTIONNE UNIQUEMENT LES CHAMPS SELECTIONNES * * @fields Libellé du champ à afficher * * @return this Retourne l'object courant * */ public function select($fields=[]){ /* [1] On formatte les champs =========================================================*/ /* (1) On met en tableau quoi qu'il en soit */ $fields = is_array($fields) ? $fields : [$fields]; /* (2) On vérifie que chaque champ existe, sinon on le retire */ foreach($fields as $f=>$field) if( !isset($this->schema['columns'][$field]) && $field != '*' ) unset($fields[$f]); /* (3) Permet d'avoir les indices de 0 à count-1 */ sort($fields); /* [2] On enregistre la liste des champs =========================================================*/ foreach($fields as $f=>$field) if( !in_array($field, $this->select) ) // On ajoute si pas déja $this->select[] = $field; /* [3] On retourne l'object courant =========================================================*/ return $this; } /* JOINT UNE SECONDE TABLE () * * @field Nom d'une colonne * @rows Rows d'une autre table * * @return this Retourne l'object courant * */ public function join($field, $rows){ /* [0] Vérification des paramètres =========================================================*/ if( !is_string($field) || !($rows instanceof Rows) ) return $this; /* [1] On vérifie que la clé étrangère est correcte =========================================================*/ /* (1) On vérifie que la colonne existe et qu'elle est étrangère*/ if( !isset($this->schema['columns'][$field]['references']) ) return $this; /* (2) On vérifie que la table de @rows est correcte */ if( $this->schema['columns'][$field]['references'][0] != $rows->schema['table'] ) return $this; /* [2] On enregistre la référence =========================================================*/ $this->joined[$field] = $rows; /* [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 $this; /* (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){ $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 $this; /* [1] Rédaction de la clause UPDATE =========================================================*/ /* (1) On initialise la requête */ $requestS = "UPDATE ".$this->schema['table']."\n"; /* [2] Rédaction de la clause SET =========================================================*/ /* (5) On met tout les champs à modifier */ $c = 0; foreach($cleared as $field=>$value){ if( $c == 0 ) $requestS .= 'SET '.$field.' = :update_'.$field; else $requestS .= "\n, ".$field.' = :update_'.$field; $c++; } $requestS .= "\n"; /* [3] On rédige la clause WHERE/AND =========================================================*/ /* (1) On met les conditions locales */ $c = 0; foreach($this->where as $field=>$conditions) foreach($conditions as $cdt=>$value){ if( $c == 0 ) $requestS .= 'WHERE '.$this->schema['table'].'.'.$field.' '.$value[1].' :'.$this->schema['table'].'_x_'.$field.'_'.$cdt."\n"; else $requestS .= 'AND '.$this->schema['table'].'.'.$field.' '.$value[1].' :'.$this->schema['table'].'_x_'.$field.'_'.$cdt."\n"; $c++; } /* (2) On ajoute les jointures */ foreach($this->joined as $localField=>$rows){ if( $c == 0 ) $requestS .= 'WHERE '; else $requestS .= 'AND '; // {1} Clause SELECT interne // $requestS .= $this->schema['table'].'.'.$localField." = (\n\tSELECT "; // Jointure du SELECT, champ joint lié au champ local $requestS .= $rows->schema['table'].'.'.$this->schema['columns'][$localField]['references'][1]."\n"; // {2} Clause FROM interne // $requestS .= "\tFROM ".$this->schema['columns'][$localField]['references'][0]."\n"; // {3} Clause WHERE/AND interne // $c2 = 0; foreach($rows->where as $field=>$conditions) foreach($conditions as $cdt=>$value){ if( $c2 == 0 ) $requestS .= "\tWHERE ".$rows->schema['table'].'.'.$field.' '.$value[1].' :'.$rows->schema['table'].'_x_'.$field.'_'.$cdt."\n"; else $requestS .= "\tAND ".$rows->schema['table'].'.'.$field.' '.$value[1].' :'.$rows->schema['table'].'_x_'.$field.'_'.$cdt."\n"; $c2++; } $requestS .= "\tLIMIT 1)\n"; } /* [4] On exécute la requête et 'bind' les paramètres =========================================================*/ /* (0) On prépare la requête */ $request = Database::getPDO()->prepare($requestS.';'); $binded = []; /* (1) On bind tous les paramètres locaux */ foreach($this->where as $field=>$conditions) foreach($conditions as $cdt=>$value) $binded[':'.$this->schema['table'].'_x_'.$field.'_'.$cdt] = $value[0]; /* (3) On bind tous les paramètres des jointures */ foreach($this->joined as $rows) foreach($rows->where as $field=>$conditions) foreach($conditions as $cdt=>$value) $binded[':'.$rows->schema['table'].'_x_'.$field.'_'.$cdt] = $value[0]; /* (4) On bind les nouvelles valeurs */ foreach($cleared as $field=>$value) $binded[':update_'.$field] = $value; /* [6] On exécute la requête et retourne le résultat =========================================================*/ /* (1) On exécute la requête */ $updated = $request->execute($binded); /* (2) On retourne l'état de la requête */ return $updated; } /* SUPPRIME LES ENTREES * * @return status Retourne si TRUE ou FALSE les entrées ont bien été supprimées * */ public function delete(){} /* RETOURNE LES DONNEES / NULL si une erreur survient * * @unique VRAI si on veut un seul résultat (itérateur) * * @return data Tableau contenant les champs sélectionnés * @return data Valeur du champ sélectionné (si 1 seul champ) * */ public function fetch($unique=null){ /* [0] Initialisation des paramètres =========================================================*/ $unique = !is_bool($unique) ? $this->unique : $unique; /* [1] On rédige la clause SELECT =========================================================*/ /* (1) On initialise la requête */ $requestS = "SELECT "; $c = 0; /* (2) Sinon, on sélectionne les champs locaux */ foreach($this->select as $field){ if( $c == 0 ) $requestS .= $this->schema['table'].'.'.$field; else $requestS .= ', '.$this->schema['table'].'.'.$field; $c++; } /* (3) Sinon, on sélectionne les champs de jointure */ foreach($this->joined as $rows) foreach($rows->select as $field){ if( $c == 0 ) $requestS .= $rows->schema['table'].'.'.$field; else $requestS .= ', '.$rows->schema['table'].'.'.$field; $c++; } /* [2] On rédige la clause FROM ========================================================*/ /* (1) Table locale */ $requestS .= "\nFROM ".$this->schema['table']; /* (2) Tables de jointure */ foreach($this->joined as $j=>$rows) $requestS .= ", ".$rows->schema['table']; /* (3) Retour à la ligne */ $requestS .= "\n"; /* [5] On rédige la clause WHERE/AND =========================================================*/ /* (1) On met les conditions locales */ $c = 0; foreach($this->where as $field=>$conditions) foreach($conditions as $cdt=>$value){ if( $c == 0 ) $requestS .= 'WHERE '.$this->schema['table'].'.'.$field.' '.$value[1].' :'.$this->schema['table'].'_x_'.$field.'_'.$cdt."\n"; else $requestS .= 'AND '.$this->schema['table'].'.'.$field.' '.$value[1].' :'.$this->schema['table'].'_x_'.$field.'_'.$cdt."\n"; $c++; } /* (2) On ajoute les jointures */ foreach($this->joined as $localField=>$rows){ if( $c == 0 ) $requestS .= 'WHERE '.$this->schema['table'].'.'.$localField.' = '.$this->schema['columns'][$localField]['references'][0].'.'.$this->schema['columns'][$localField]['references'][1]."\n"; else $requestS .= 'AND '.$this->schema['table'].'.'.$localField.' = '.$this->schema['columns'][$localField]['references'][0].'.'.$this->schema['columns'][$localField]['references'][1]."\n"; $c++; } /* (3) On ajoute les conditions des jointures */ foreach($this->joined as $rows) foreach($rows->where as $field=>$conditions) foreach($conditions as $cdt=>$value){ if( $c == 0 ) $requestS .= 'WHERE '.$rows->schema['table'].'.'.$field.' '.$value[1].' :'.$rows->schema['table'].'_x_'.$field.'_'.$cdt."\n"; else $requestS .= 'AND '.$rows->schema['table'].'.'.$field.' '.$value[1].' :'.$rows->schema['table'].'_x_'.$field.'_'.$cdt."\n"; $c++; } /* [6] On exécute la requête et 'bind' les paramètres =========================================================*/ /* (0) On prépare la requête */ $request = Database::getPDO()->prepare($requestS.';'); $binded = []; /* (1) On bind tous les paramètres locaux */ foreach($this->where as $field=>$conditions) foreach($conditions as $cdt=>$value) $binded[':'.$this->schema['table'].'_x_'.$field.'_'.$cdt] = $value[0]; /* (3) On bind tous les paramètres des jointures */ foreach($this->joined as $rows) foreach($rows->where as $field=>$conditions) foreach($conditions as $cdt=>$value) $binded[':'.$rows->schema['table'].'_x_'.$field.'_'.$cdt] = $value[0]; /* [6] On exécute la requête et retourne le résultat =========================================================*/ /* (1) On exécute la requête */ $request->execute($binded); /* (2) Si unique */ if( $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 retire les indices numériques */ // Pour chaque entrée foreach($formatted as $i=>$entry) // Pour chaque champ foreach($entry as $index=>$value) // Si dans le schéma on applique le type if( isset($this->schema['columns'][$index]) ){ if( $this->schema['columns'][$index]['type'] == 'int' ) $formatted[$i][$index] = intval( $value ); else if( $this->schema['columns'][$index]['type'] == 'float' ) $formatted[$i][$index] = floatval( $value ); }else // Si pas dans le schéma, on le retire 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; } } ?>