From acc2d9767efb76305a82596835e511d5620c04e0 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Mon, 12 Sep 2016 17:03:48 +0200 Subject: [PATCH] =?UTF-8?q?Int=C3=A9gration=20du=20'Checker'=20+=20Quelque?= =?UTF-8?q?s=20manager?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.php | 5 +- manager/Checker.php | 146 +++++ manager/Database.php | 126 ---- manager/ModuleRequest.php | 5 +- manager/ORM/Rows.php | 1005 ++++++++++++++++++++++++++++++ manager/ORM/SQLBuilder.php | 388 ++++++++++++ manager/ORM/Table.php | 131 ++++ manager/Repo.php | 34 +- manager/module/token.php | 3 +- manager/repo/category.php | 3 +- manager/repo/relation.php | 7 +- manager/repo/subject.php | 19 +- manager/repo/token.php | 7 +- manager/repo/user.php | 13 +- phpunit/tests/Database_check.php | 82 +-- 15 files changed, 1765 insertions(+), 209 deletions(-) create mode 100644 manager/Checker.php create mode 100644 manager/ORM/Rows.php create mode 100644 manager/ORM/SQLBuilder.php create mode 100644 manager/ORM/Table.php diff --git a/index.php b/index.php index 4162f99..ed641fa 100755 --- a/index.php +++ b/index.php @@ -7,6 +7,7 @@ use \manager\ModuleResponse; use \manager\ManagerError; use \manager\Database; + use \manager\Checker; use \manager\MenuManager; @@ -31,8 +32,8 @@ /* (2) Gestion de la connection */ $login_vars = isset($_POST['login-sub']); - $login_vars = $login_vars && isset($_POST['login']) && Database::check('varchar(3,50)', $_POST['login']); - $login_vars = $login_vars && isset($_POST['password']) && Database::check('text', $_POST['password']); + $login_vars = $login_vars && isset($_POST['login']) && Checker::run('varchar(3,50)', $_POST['login']); + $login_vars = $login_vars && isset($_POST['password']) && Checker::run('text', $_POST['password']); // Status de login $_SESSION['login_status'] = 'no'; diff --git a/manager/Checker.php b/manager/Checker.php new file mode 100644 index 0000000..4394e6f --- /dev/null +++ b/manager/Checker.php @@ -0,0 +1,146 @@ + Type que l'on veut verifier + * @value Valeur a verifier + * + * @return match Retourne si oui ou non la valeur @value est du bon type @type + * + */ + public static function run($type, $value){ + $checker = true; + + /* [0] On verifie que $value n'est pas nul + =========================================================*/ + if( is_null($value) ) return false; + + + + /* [1] Si de type VARCHAR(min, max, flags) + =========================================================*/ + if( preg_match('/^varchar\((\d+), ?(\d+)((?:, ?\w+)+)?\)$/', $type, $match) ){ + // On recupere la taille min + $min = (int) $match[1]; + // On recupere la taille max + $max = (int) $match[2]; + + // On recupere le sous-type si défini + $flags = isset($match[3]) ? explode(',', substr($match[3], 1)) : null; + + // On effectue la verification de taille + $lenCheck = $checker && is_string($value) && strlen($value) <= $max && strlen($value) >= $min; + + // On vérifie les FLAGS s'il est donné + if( is_array($flags) ) + foreach( $flags as $flag ) + $lenCheck = $lenCheck && self::run($flag, $value); + + return $lenCheck; + } + + + /* [2] Si de type ARRAY(type_elements) + =========================================================*/ + if( preg_match('/^array<(.+)>$/', $type, $match) ){ + + // Si c'est pas un tableau on retourne une erreur + if( !is_array($value) ) + return false; + + + $elements_type = $match[1]; + + // On verifie le type pour chaque element + foreach($value as $element) + // Si erreur dans au moins 1 element, on retourne que c'est incorrect + if( !self::run($elements_type, trim($element) ) ) + return false; + + // Si aucune erreur, on retourne que tout est bon + return true; + } + + + /* [n] Sinon, tous les autres types definis + =========================================================*/ + switch($type){ + // Quoi que ce soit + case 'mixed': + return $checker && !is_null($value); + break; + + // Entier positif (id dans BDD) + case 'id': + return $checker && is_numeric($value) && $value <= 2147483647 && $value >= 0; + break; + + // Code RFID + case 'rfid': + return $checker && is_string($value) && preg_match('/^[\dA-F]{2}(\-[\dA-F]{2}){3,5}$/i', $value); + break; + + // String quelconque (peut etre vide) + case 'text': + return $checker && is_string($value); + + // Adresse mail (255 caracteres max) + case 'mail': + return $checker && is_string($value) && strlen($value) <= 50 && preg_match('/^[\w\.-]+@[\w\.-]+\.[a-z]{2,4}$/i', $value); + break; + + // Hash sha1/md5 + case 'hash': + return $checker && is_string($value) && preg_match('/^[\da-f]+$/i', $value) && (strlen($value) == 40 || strlen($value) == 64); + break; + + case 'alphanumeric': + return $checker && is_string($value) && preg_match('/^[\w\.-]+$/ui', $value); + break; + + case 'letters': + return $checker && is_string($value) && preg_match('/^[a-z -]+$/i', $value); + break; + + case 'status': + return $checker && is_numeric($value) && floor($value) == $value && $value >= 0 && $value <= 100; + break; + + // Tableau non vide + case 'array': + return $checker && is_array($value) && count($value) > 0; + break; + + // Boolean + case 'boolean': + return $checker && is_bool($value); + break; + + // Objet non vide + case 'object': + return $checker && is_object($value) && count((array) $value) > 0; + break; + + // Chaine JSON (on vérifie via le parser) + case 'json': + return $checker && is_string($value) && json_decode($value, true) !== NULL; + break; + + default: + return false; + break; + } + + return $checker; + + } + + + } +?> diff --git a/manager/Database.php b/manager/Database.php index faab123..8c8a277 100755 --- a/manager/Database.php +++ b/manager/Database.php @@ -164,132 +164,6 @@ - //////////////////////////////////////////////////////////////// - // _ __ _ _ _ - // __ _____ _ __(_)/ _(_) ___ __ _| |_(_) ___ _ __ ___ - // \ \ / / _ \ '__| | |_| |/ __/ _` | __| |/ _ \| '_ \/ __| - // \ V / __/ | | | _| | (_| (_| | |_| | (_) | | | \__ \ - // \_/ \___|_| |_|_| |_|\___\__,_|\__|_|\___/|_| |_|___/ - // - //////////////////////////////////////////////////////////////// - - - /* VERIFICATIONS DES TYPES UTILES GENERIQUES - * - * @type Type que l'on veut verifier - * @value Valeur a verifier - * - * @return match Retourne si oui ou non la valeur @value est du bon type @type - * - */ - public static function check($type, $value){ - $checker = true; - - /* [0] On verifie que $value n'est pas nul - =========================================================*/ - if( is_null($value) ) return false; - - - - /* [1] Si de type VARCHAR(min, max) - =========================================================*/ - if( preg_match('/^varchar\((\d+), ?(\d+)\)$/', $type, $match) ){ - // On recupere la taille min - $min = (int) $match[1]; - // On recupere la taille max - $max = (int) $match[2]; - - // On effectue la verification - return $checker && is_string($value) && strlen($value) <= $max && strlen($value) >= $min; - } - - - /* [2] Si de type ARRAY(type_elements) - =========================================================*/ - if( preg_match('/^array<(.+)>$/', $type, $match) ){ - - // Si c'est pas un tableau on retourne une erreur - if( !is_array($value) ) - return false; - - - $elements_type = $match[1]; - - // On verifie le type pour chaque element - foreach($value as $element) - // Si erreur dans au moins 1 element, on retourne que c'est incorrect - if( !self::check($elements_type, $element) ) - return false; - - // Si aucune erreur, on retourne que tout est bon - return true; - } - - - /* [n] Sinon, tous les autres types definis - =========================================================*/ - switch($type){ - // Quoi que ce soit - case 'mixed': - return $checker && !is_null($value); - break; - - // Entier positif (id dans BDD) - case 'id': - return $checker && is_numeric($value) && $value <= 2147483647 && $value >= 0; - break; - - // String quelconque (peut etre vide) - case 'text': - return $checker && is_string($value); - - // Adresse mail (255 caracteres max) - case 'mail': - return $checker && is_string($value) && strlen($value) <= 50 && preg_match('/^[\w\.-]+@[\w\.-]+\.[a-z]{2,4}$/i', $value); - break; - - // Hash sha1 - case 'sha1': - return $checker && is_string($value) && preg_match('/^[\da-f]{40}$/i', $value); - break; - - // Numéro de téléphone - case 'number': - return $checker && is_string($value) && preg_match('/^(?:0|\+33 ?|0?0?33 ?|)([1-9] ?(?:[0-9] ?){8})$/i', $value); - break; - - // Tableau non vide - case 'array': - return $checker && is_array($value) && count($value) > 0; - break; - - // Boolean - case 'boolean': - return $checker && is_bool($value); - break; - - // Objet non vide - case 'object': - return $checker && is_object($value) && count((array) $value) > 0; - break; - - // Chaine JSON (on vérifie via le parser) - case 'json': - return $checker && is_string($value) && json_decode($value, true) !== NULL; - break; - - default: - return false; - break; - } - - return $checker; - - } - - - - /* FONCTION QUI FORMATTE UN NUMÉRO DE TÉLÉPHONE * diff --git a/manager/ModuleRequest.php b/manager/ModuleRequest.php index 73efddb..0efdb76 100755 --- a/manager/ModuleRequest.php +++ b/manager/ModuleRequest.php @@ -2,6 +2,7 @@ namespace manager; use \manager\Database; + use \manager\Checker; class ModuleRequest{ @@ -380,7 +381,7 @@ /* [2] Gestion si un @token est defini =========================================================*/ - if( Database::check('sha1', $token) ){ + if( Checker::run('sha1', $token) ){ /* (1) On verifie que le token est valide */ @@ -478,7 +479,7 @@ /* (6) Si le paramètre est renseigné */ }else // Si la verification est fausse, on retourne faux - if( !Database::check($paramsdata['type'], $params[$name]) ) + if( !Checker::run($paramsdata['type'], $params[$name]) ) return false; } diff --git a/manager/ORM/Rows.php b/manager/ORM/Rows.php new file mode 100644 index 0000000..85bbffd --- /dev/null +++ b/manager/ORM/Rows.php @@ -0,0 +1,1005 @@ +__'; + 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} Constantes d'insertion // + const INSERT_DEFAULT = '__DEFAULT__'; // Valeur DEFAULT (pour insertion) + + /* 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 + =========================================================*/ + /* (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 ; $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 LE CHAMP SELECTIONNE + * + * @field Libellé du champ à afficher + * @func Fonction d'aggrégation (ou NULL) + * @distinct Clause DISTINCT + * + * @return this Retourne l'object courant + * + */ + public function select($field=null, $func=null, $distinct=false){ + /* [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; + + /* (4) On met la valeur par défaut à @distinct si type mauvais */ + $distinct = !is_bool($distinct) ? false : $distinct; + + + /* [2] On enregistre le champ + =========================================================*/ + /* (1) Si aucun SELECT pour ce champ, on le crée */ + if( !isset($this->select[$field]) ) + $this->select[$field] = [$func, $distinct]; + + + /* [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){ + + $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 = Database::getPDO()->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 = Database::getPDO()->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 = Database::getPDO()->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 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).';'; + + /* (3) On prépare la requête */ + $request = Database::getPDO()->prepare($requestString); + // var_dump($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 retire les indices numériques */ + // {1} On récupère les colonnes locales // + $existingColumns = $this->schema['columns']; + + // {2} On ajoute les colonnes des jointures // + foreach($this->joined as $j) + $existingColumns = array_merge( $existingColumns, $j['object']->schema['columns'] ); + + // {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 ); + else if( $existingColumns[$index]['type'] == 'float' ) + $formatted[$i][$index] = floatval( $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; + + } + + } + + + +?> diff --git a/manager/ORM/SQLBuilder.php b/manager/ORM/SQLBuilder.php new file mode 100644 index 0000000..58e182b --- /dev/null +++ b/manager/ORM/SQLBuilder.php @@ -0,0 +1,388 @@ + Liste de champs : [table => field => [func, alias] ] + * + * @return sql Renvoie un tableau formatté + * + */ + public static function SELECT($sqlFields){ + return $sqlFields; + } + + + + + + + + /* CONSTRUIT LA REQUETE FORMATTEE "GROUP BY" AVEC UNE LISTE DE CHAMPS + * + * @tables Liste de champs : [table => fields] + * + * @return sql Renvoie un tableau formatté + * + */ + public static function GROUPBY($tables){ + return $tables; + } + + + + + + + /* CONSTRUIT LA REQUETE FORMATTEE "FROM" AVEC UNE LISTE DE TABLES + * + * @tables Liste de tables OU SQL PUR + * + * @return sql Renvoie un tableau formatté + * + */ + public static function FROM($tables){ + return $tables; + } + + + + + + + /* CONSTRUIT LA REQUETE FORMATTEE "UPDATE" AVEC LA TABLE EN QUESTION + * + * @table Table en question + * + * @return sql Renvoie un tableau formatté + * + */ + public static function UPDATE($table){ + return $table; + } + + + + + + + /* CONSTRUIT LA REQUETE FORMATTEE "DELETE" AVEC LA TABLE EN QUESTION + * + * @table Table en question + * + * @return sql Renvoie un tableau formatté + * + */ + public static function DELETE($table){ + return $table; + } + + + + + + + /* CONSTRUIT LA REQUETE TEXTUELLE "IN" AVEC UNE LISTE DE TABLES + * + * @field Tableau contenant [table, field] + * @array Valeurs de la clause IN + * @offset Permet de rendre la condition unique (nommage des variables) + * @bound Tableau associatif contenant les variables "bindés" -> ajout des champs + * + * @return sql Renvoie le textuel formatté + * + */ + public static function IN($field, $array, $offset=0, &$bound){ + /* [0] Initialisation + =========================================================*/ + $sql = ''; + + /* [1] On construit la requête + =========================================================*/ + /* (1) Champ */ + $sql .= $field[0].'.'.$field[1].' IN ('; + + /* (2) Valeurs */ + $c = 0; + foreach($array as $i=>$value){ + if( $c > 0 ) $sql .= ', '; + + $sql .= ':'.$field[0].'_x_'.$field[1].'_'.$offset.'_'.$i; + + $bound[':'.$field[0].'_x_'.$field[1].'_'.$offset.'_'.$i] = $value; + + $c++; + } + + return $sql.")"; + } + + + + + + /* CONSTRUIT LA REQUETE TEXTUELLE "WHERE" AVEC UNE LISTE DE TABLES + * + * @field Tableau contenant [table, field] + * @valeur Valeurs de la clause WHERE [valeur, opérateur] + * @offset Permet de rendre la condition unique (nommage des variables) + * @bound Tableau associatif contenant les variables "bindés" -> ajout des champs + * + * @return sql Renvoie le textuel formatté + * + */ + public static function WHERE($field, $value, $offset=0, &$bound){ + /* [0] Initialisation + =========================================================*/ + $sql = ''; + + + /* [1] On construit la requête + =========================================================*/ + /* (1) Chamo */ + $sql .= $field[0].'.'.$field[1].' '; + + /* (2) Opérateur */ + $sql .= substr($value[1], 2, -2).' '; + + /* (3) Variable */ + $sql .= ':'.$field[0].'_x_'.$field[1].'_'.$offset; + + $bound[':'.$field[0].'_x_'.$field[1].'_'.$offset] = $value[0]; + + + return $sql; + } + + + + + + /* CONSTRUIT LA REQUETE FORMATTEE "SET" AVEC UNE LISTE DE TABLES + * + * @values Tableau de la forme [ field=>value, field2=>value2 ] + * @bound Tableau associatif contenant les variables "bindés" -> ajout des champs + * + * @return sql Renvoie un tableau formatté + * + */ + public static function SET($values, &$bound){ + /* [0] Initialisation + =========================================================*/ + $sql = []; + + + /* [1] On construit la requête + =========================================================*/ + $c = 0; + foreach($values as $field=>$value){ + /* (1) Champ */ + $sql[$c] = $field.' = '; + + /* (2) Variable */ + $sql[$c] .= ':update_'.$field; + + $bound[':update_'.$field] = $value; + + $c++; + } + + return $sql; + } + + + + + + /* CONSTRUIT LA REQUETE FORMATTEE "LIMIT" AVEC UN NOMBRE D'ENTREES + * + * @count Nombre limite + * + * @return sql Renvoie un sql formatté + * + */ + public static function LIMIT($count=null){ + /* [0] Initialisation + =========================================================*/ + $sql = ''; + + + /* [1] On construit la requête + =========================================================*/ + if( intval($count) == $count ) + $sql = intval($count); + + return $sql; + } + + + + + + + + + + + + + + /* CONSTRUIT LA REQUETE A PARTIR D'UNE REQUETTE FORMATTEE + * + * @request Requête formattée + * + * @return sql Requête formattée en SQL + * + */ + public static function BUILD($request){ + /* [0] On initialise le retour + =========================================================*/ + $sql = ''; + + /* [1] Gestion dans l'ordre + =========================================================*/ + foreach($request as $clause=>$statements){ + + switch($clause){ + + /* (1) Clause SELECT + ---------------------------------------------------------*/ + case 'SELECT': + $sql .= "SELECT "; + $c = 0; + foreach($statements as $table=>$fields) + foreach($fields as $field=>$select){ + + /* (1) On construit le nom du champ */ + $fieldStr = "$table.$field"; + + /* (2) On ajout le DISTINCT s'il y a lieu */ + if( isset($select[1]) && $select[1] ) + $fieldStr = "DISTINCT $fieldStr"; + + /* (3) On ajoute la fonction d'aggrégation s'il y a lieu */ + if( isset($select[0]) && !is_null($select[0]) ) + $fieldStr = substr($select[0], 2, -2)."($fieldStr)"; + + + /* (4) On ajoute l'alias */ + if( isset($select[0]) && !is_null($select[0]) ) + $fieldStr = "$fieldStr as agg_$field"; + else + $fieldStr = "$fieldStr"; + + $sql .= ($c==0) ? "$fieldStr" : ", $fieldStr"; + + $c++; + } + + $sql .= "\n"; + break; + + /* (2) Clause FROM + ---------------------------------------------------------*/ + case 'FROM': + $sql .= 'FROM '; + + $c = 0; + foreach($statements as $field){ + $sql .= ($c==0) ? "$field" : ", $field"; + $c++; + } + + $sql .= "\n"; + break; + + + /* (3) Clause WHERE + ---------------------------------------------------------*/ + case 'WHERE': + $c = 0; + foreach($statements as $field){ + $sql .= ($c==0) ? "WHERE $field\n" : "AND $field\n"; + $c++; + } + + $sql .= ($c==0) ? '' : "\n"; + break; + + + + /* (4) Clause LIMIT + ---------------------------------------------------------*/ + case 'LIMIT': + if( is_numeric($statements) ) + $sql .= 'LIMIT '.intval($statements); + break; + + + /* (5) Clause DELETE + ---------------------------------------------------------*/ + case 'DELETE': + $sql .= "DELETE FROM $statements\n"; + break; + + + /* (6) Clause UPDATE + ---------------------------------------------------------*/ + case 'UPDATE': + $sql .= "UPDATE $statements\n"; + break; + break; + + + /* (7) Clause SET + ---------------------------------------------------------*/ + case 'SET': + $c = 0; + foreach($statements as $field){ + $sql .= ($c>0) ? "\n, $field" : "SET $field"; + $c++; + } + $sql .= "\n"; + break; + + /* (8) Clause GROUP BY + ---------------------------------------------------------*/ + case 'GROUPBY': + $sql .= 'GROUP BY '; + + $c = 0; + foreach($statements as $table=>$fields) + foreach($fields as $field){ + $sql .= ($c==0) ? "$table.$field" : ", $table.$field"; + $c++; + } + + $sql .= "\n"; + break; + } + + + } + + + + + + + + /* [2] On retourne le résultat + =========================================================*/ + return $sql; + } + + + + } + + +?> diff --git a/manager/ORM/Table.php b/manager/ORM/Table.php new file mode 100644 index 0000000..995f129 --- /dev/null +++ b/manager/ORM/Table.php @@ -0,0 +1,131 @@ + Nom de la table à selectionner + * + * @return this Retourne une instance de l'ORM + * + */ + public static function get($table_name){ + /* [0] Initialisation des attributs + =========================================================*/ + $schema = [ + 'database' => self::$database, + 'table' => null, + 'columns' => null + ]; + + + /* [1] On vérifie que la table existe + =========================================================*/ + /* (1) Requête */ + $checkTable = Database::getPDO()->query("SHOW tables FROM ".self::$database); + $checkTableResult = Database::delNumeric( $checkTable->fetchAll() ); + + /* (2) On met en forme les données */ + $tables = []; + foreach($checkTableResult as $table) + $tables[] = $table['Tables_in_'.self::$database]; + + /* (3) Si n'existe pas, on renvoie une erreur */ + if( !in_array($table_name, $tables) ) + return null; + + /* (4) On enregistre les données */ + $schema['table'] = $table_name; + + + + /* [2] Si la table existe, on récupère les colonnes + =========================================================*/ + /* (1) On récupère les colonnes */ + $getColumns = Database::getPDO()->query("SHOW columns FROM ".self::$database.'.'.$table_name); + $columnsResult = Database::delNumeric( $getColumns->fetchAll() ); + + /* (2) On met en forme les données */ + $columns = []; + foreach($columnsResult as $col){ + // On formatte le type // + $type = $col['Type']; + if( preg_match('/^(int|float|varchar|text)/i', $type, $m) ) + $type = strtolower($m[1]); + + // On ajoute la colonne // + $columns[$col['Field']] = [ + 'type' => $type, + 'primary' => $col['Key'] == 'PRI' + ]; + } + + + /* (3) Si on trouve rien, on envoie une erreur */ + if( !is_array($columns) || count($columns) == 0 ) + return null; + + /* (4) On enregistre les colonnes */ + $schema['columns'] = $columns; + + + + /* [3] On récupère les clés étrangères + =========================================================*/ + /* (1) On récupère le texte du 'CREATE TABLE' */ + $getCreateTable = Database::getPDO()->query("show create table ".$table_name); + $create_table = $getCreateTable->fetch()['Create Table']; + + /* (2) On découpte en lignes */ + $create_table_lines = explode("\n", $create_table); + + /* (3) Pour chaque ligne, si c'est une contrainte, on l'enregistre dans la colonne associée */ + foreach($create_table_lines as $i=>$line) + if( preg_match('/CONSTRAINT `.+` FOREIGN KEY \(`(.+)`\) REFERENCES `(.+)` \(`(.+)`\)+/i', $line, $m) ) + $schema['columns'][$m[1]]['references'] = [$m[2], $m[3]]; + + + + /* [3] On renvoie une instance de 'Rows' + =========================================================*/ + return new Rows($schema); + + } + + + + }; + + + + // // USE CASE :: ACCESS TABLE + // ORM::Table = ORM::Table('user'); + // + // // USE CASE :: getBy{ATTRIBUTE} + // ORM::Row = ORM::Table->getByUsername('someUsername'); // ORM_FETCH by default + // ORM::Row = ORM::Table->getByUsername('someUsername', ORM_FETCH); + // ORM::Column = ORM::Table->getByUsername('someUsername', ORM_FETCHALL); + // + // // USE CASE :: getById -> primary key(s) + // ORM::Row = ORM::Table->getById(5, 7); // because PRIMARY KEY is composed by '5' and '7' + // + // // USE CASE :: getAll + // ORM::Column = ORM::Table->getAll(); + // + // // USE CASE :: select(FIELD) + // mixed = ORM::Row->select('username'); + // + // // USE CASE :: select(FIELD1, FIELD2, ...) + // mixed = ORM::Row->select('id_user', 'username'); diff --git a/manager/Repo.php b/manager/Repo.php index e6a14c9..87a98a0 100755 --- a/manager/Repo.php +++ b/manager/Repo.php @@ -6,8 +6,8 @@ // FORMAT: // // path: "nomModule/nomMethode" - // data1: {donnee1} - // data2: {donnee2} + // params1: {donnee1} + // params2: {donnee2} // ... // // @@ -17,12 +17,12 @@ class Repo{ // Constantes - public static $config_path = __ROOT__.'/config/repositories.json'; + public static function config_path(){ return __ROOT__.'/config/repositories.json'; } // Attributs prives utiles (initialisation) private $path; - private $data; + private $params; // Paramètres de la requête private $repositories; // Contiendra la reponse a la requete @@ -38,12 +38,13 @@ /* CONSTRUCTEUR D'UNE REQUETE DE MODULE * * @path Chemin de delegation ("repo/methode") - * @data Tableau contenant les parametres utiles au traitement + * @params Tableau contenant les parametres utiles au traitement * * @return status Retourne si oui ou non tout s'est bien passe * */ - public function __construct($path=null, $data=null){ + public function __construct($path=null, $params=null){ + // Si pas parametre manquant, on quitte if( $path == null ){ $this->error = ManagerError::MissingPath; @@ -53,7 +54,7 @@ /* [0] On met a jour la configuration =========================================================*/ // Modules specifies - $this->repositories = json_decode( file_get_contents(self::$config_path), true ); + $this->repositories = json_decode( file_get_contents(self::config_path()), true ); // Gestion de l'erreur de parsage if( $this->repositories == null ){ @@ -71,8 +72,8 @@ return false; // On retourne FALSE, si erreur } - // Type de @data (optionnel) - $data = (is_array($data)) ? $data : array(); + // Type de @params (optionnel) + $params = (is_array($params)) ? $params : []; /* [2] Verification du chemin (existence repo+methode) @@ -84,7 +85,7 @@ /* [3] Construction de l'objet =========================================================*/ - $this->data = $data; + $this->params = $params; $this->error = ManagerError::Success; /* [4] Enregistrement de la reponse @@ -101,6 +102,9 @@ public function answer(){ + if( $this->error != ManagerError::Success ) + return false; + return $this->answer; } @@ -128,7 +132,7 @@ /* [3] On amorce la methode =========================================================*/ - return call_user_func_array( $this->getFunctionCaller(), $this->data ); + return call_user_func_array( $this->getFunctionCaller(), $this->params ); } @@ -150,7 +154,7 @@ // On recupere les donnes de la regex $repository = $matches[1]; - $method = $matches[2]; + $method = $matches[2]; /* [2] Verification de l'existence du repo (conf) =========================================================*/ @@ -170,10 +174,10 @@ /* [4] Enregistrement du chemin et renvoi de SUCCESS =========================================================*/ - $this->path = array( + $this->path = [ 'repo' => $repository, 'method' => $method - ); + ]; return true; } @@ -188,7 +192,7 @@ * */ private function getFunctionCaller(){ - return array( '\\manager\\repo\\'.$this->path['repo'], $this->path['method'] ); + return [ '\\manager\\repo\\'.$this->path['repo'], $this->path['method'] ]; } diff --git a/manager/module/token.php b/manager/module/token.php index a8622f0..ffe4979 100755 --- a/manager/module/token.php +++ b/manager/module/token.php @@ -2,6 +2,7 @@ namespace manager\module; use \manager\Database; + use \manager\Checker; use \manager\sessionManager; use \manager\ManagerError; use \manager\Repo; @@ -52,7 +53,7 @@ /* [0] Verification des INPUT =========================================================*/ - if( !Database::check('varchar(3,50)', $name) || !Database::check('id', $duration) ) + if( !Checker::run('varchar(3,50)', $name) || !Checker::run('id', $duration) ) return array('ModuleError' => ManagerError::ParamError); // erreur de parametre diff --git a/manager/repo/category.php b/manager/repo/category.php index 3a21c2f..dd90beb 100644 --- a/manager/repo/category.php +++ b/manager/repo/category.php @@ -2,6 +2,7 @@ namespace manager\repo; use \manager\Database; + use \manager\Checker; use \manager\sessionManager; class category extends parentRepo{ @@ -20,7 +21,7 @@ /* [0] Vérification des INPUT =========================================================*/ // Si erreur de type de paramètre, on retourne FALSE - if( !Database::check('varchar(0,40)', $intitule) ) + if( !Checker::run('varchar(0,40)', $intitule) ) return false; /* [1] Vérification qu'aucune n'a cet intitulé diff --git a/manager/repo/relation.php b/manager/repo/relation.php index 1abd3f2..3511b9c 100644 --- a/manager/repo/relation.php +++ b/manager/repo/relation.php @@ -2,6 +2,7 @@ namespace manager\repo; use \manager\Database; + use \manager\Checker; use \manager\sessionManager; class relation extends parentRepo{ @@ -21,9 +22,9 @@ public static function create($A, $B, $category){ /* [0] Vérification des INPUT =========================================================*/ - $checkInput = Database::check('id', $A); - $checkInput = $checkInput && Database::check('id', $B); - $checkInput = $checkInput && Database::check('id', $category); + $checkInput = Checker::run('id', $A); + $checkInput = $checkInput && Checker::run('id', $B); + $checkInput = $checkInput && Checker::run('id', $category); // Si erreur de type de paramètre, on retourne FALSE if( !$checkInput ) diff --git a/manager/repo/subject.php b/manager/repo/subject.php index 02f2693..18e141e 100644 --- a/manager/repo/subject.php +++ b/manager/repo/subject.php @@ -2,6 +2,7 @@ namespace manager\repo; use \manager\Database; + use \manager\Checker; use \manager\sessionManager; use \manager\Repo; @@ -90,12 +91,12 @@ public static function create($username, $firstname, $lastname, $id_facebook, $number){ /* [0] Verification et formattage des INPUT =========================================================*/ - $checkInput = Database::check('varchar(0,30)', $username); - $checkInput = $checkInput && Database::check('varchar(0,30)', $firstname); - $checkInput = $checkInput && Database::check('varchar(0,30)', $lastname); + $checkInput = Checker::run('varchar(0,30)', $username); + $checkInput = $checkInput && Checker::run('varchar(0,30)', $firstname); + $checkInput = $checkInput && Checker::run('varchar(0,30)', $lastname); $checkInput = $checkInput && !!strlen($username.$firstname.$lastname); // Pseudo, prénom, ou nom, au moins un n'est pas vide - $checkInput = $checkInput && ( Database::check('id', $id_facebook) || is_null($id_facebook) ); - $checkInput = $checkInput && ( Database::check('number', $number) || is_null($number) ); + $checkInput = $checkInput && ( Checker::run('id', $id_facebook) || is_null($id_facebook) ); + $checkInput = $checkInput && ( Checker::run('number', $number) || is_null($number) ); // Si erreur en entree, on retourne FAUX if( !$checkInput ) return false; @@ -165,7 +166,7 @@ public static function remove($id_subject){ /* [0] Vérification des INPUT =========================================================*/ - if( !Database::check('id', $id_subject) ) + if( !Checker::run('id', $id_subject) ) return false; /* [1] On effectue la suppression @@ -191,9 +192,9 @@ public static function link($A, $B, $category){ /* [0] Vérification des INPUT =========================================================*/ - $checkInput = Database::check('id', $A); - $checkInput = $checkInput && Database::check('id', $B); - $checkInput = $checkInput && Database::check('varchar(0,40)', $category); + $checkInput = Checker::run('id', $A); + $checkInput = $checkInput && Checker::run('id', $B); + $checkInput = $checkInput && Checker::run('varchar(0,40)', $category); // Si erreur de type de paramètre, on retourne FALSE if( !$checkInput ) diff --git a/manager/repo/token.php b/manager/repo/token.php index dfd7dd3..b27135c 100644 --- a/manager/repo/token.php +++ b/manager/repo/token.php @@ -5,6 +5,7 @@ namespace manager\repo; use \manager\sessionManager; use \manager\Database; + use \manager\Checker; class token extends parentRepo{ @@ -60,7 +61,7 @@ /* [0] Verification des INPUT =========================================================*/ // si le format est incorrect, on retourne FAUX - if( !Database::check('sha1', $token) ) return false; + if( !Checker::run('sha1', $token) ) return false; /* [1] Verification dans la base de donnees @@ -97,7 +98,7 @@ public static function generate($name, $duration){ /* [0] Verification des INPUT =========================================================*/ - if( !Database::check('varchar(3,50)', $name) || !Database::check('id', $duration) ) return false; + if( !Checker::run('varchar(3,50)', $name) || !Checker::run('id', $duration) ) return false; // On definit la date d'expiration du token @@ -164,7 +165,7 @@ public static function remove($id_token){ /* [0] Verification des INPUT =========================================================*/ - if( !Database::check('id', $id_token) ) return false; + if( !Checker::run('id', $id_token) ) return false; /* [1] On verifie l'existance du token diff --git a/manager/repo/user.php b/manager/repo/user.php index 356e99c..1f90c65 100755 --- a/manager/repo/user.php +++ b/manager/repo/user.php @@ -2,6 +2,7 @@ namespace manager\repo; use \manager\Database; + use \manager\Checker; use \manager\sessionManager; use \manager\repo\parentRepo; @@ -22,8 +23,8 @@ public static function login($login, $password){ /* [0] Gestion des INPUT =========================================================*/ - $checker = Database::check('varchar(3,50)', $login); - $checker = $checker && Database::check('text', $password); + $checker = Checker::run('varchar(3,50)', $login); + $checker = $checker && Checker::run('text', $password); // Si les parametres sont incorrects, on retourne une erreur if( !$checker ) return false; @@ -70,10 +71,10 @@ public static function create($login, $password, $mail, $reference, $permission){ /* [0] Verification et formattage des INPUT =========================================================*/ - $checkInput = Database::check('sha1', $password); - $checkInput = $checkInput && Database::check('varchar(3, 30)', $login); - $checkInput = $checkInput && Database::check('mail', $mail); - $checkInput = $checkInput && ( Database::check('id', $reference) || is_null($reference) ); + $checkInput = Checker::run('sha1', $password); + $checkInput = $checkInput && Checker::run('varchar(3, 30)', $login); + $checkInput = $checkInput && Checker::run('mail', $mail); + $checkInput = $checkInput && ( Checker::run('id', $reference) || is_null($reference) ); $checkInput = $checkInput && in_array($permission, array('admin', 'subject')); // Si erreur en entree, on retourne FAUX diff --git a/phpunit/tests/Database_check.php b/phpunit/tests/Database_check.php index a965f50..c2b0751 100755 --- a/phpunit/tests/Database_check.php +++ b/phpunit/tests/Database_check.php @@ -7,34 +7,34 @@ =========================================================*/ /* (1) Taille inferieure correcte */ public function testIdSizeInfCorrect(){ - $this->assertTrue( \manager\Database::check('id', 0) ); + $this->assertTrue( \manager\Checker::run('id', 0) ); } public function testIdSizeInfStringCorrect(){ - $this->assertTrue( \manager\Database::check('id', '0') ); + $this->assertTrue( \manager\Checker::run('id', '0') ); } /* (2) Taille inferieure depassement */ public function testIdSizeInfIncorrect(){ - $this->assertFalse( \manager\Database::check('id', -1) ); + $this->assertFalse( \manager\Checker::run('id', -1) ); } public function testIdSizeInfStringIncorrect(){ - $this->assertFalse( \manager\Database::check('id', '-1') ); + $this->assertFalse( \manager\Checker::run('id', '-1') ); } /* (3) Taille superieure correcte */ public function testIdSizeSupCorrect(){ - $this->assertTrue( \manager\Database::check('id', 2147483647) ); + $this->assertTrue( \manager\Checker::run('id', 2147483647) ); } public function testIdSizeSupStringCorrect(){ - $this->assertTrue( \manager\Database::check('id', '2147483647') ); + $this->assertTrue( \manager\Checker::run('id', '2147483647') ); } /* (3) Taille superieure depassement */ public function testIdSizeSupIncorrect(){ - $this->assertFalse( \manager\Database::check('id', 2147483648) ); + $this->assertFalse( \manager\Checker::run('id', 2147483648) ); } public function testIdSizeSupStringIncorrect(){ - $this->assertFalse( \manager\Database::check('id', '2147483648') ); + $this->assertFalse( \manager\Checker::run('id', '2147483648') ); } @@ -42,11 +42,11 @@ =========================================================*/ /* (1) Type */ public function testVarcharTypeCorrect(){ - $this->assertTrue( \manager\Database::check('varchar(0,10)', 'string') ); + $this->assertTrue( \manager\Checker::run('varchar(0,10)', 'string') ); } public function testVarcharTypeIncorrect(){ - $this->assertFalse( \manager\Database::check('varchar(0,10)', 10 ) ); - $this->assertFalse( \manager\Database::check('varchar(0,10)', array() ) ); + $this->assertFalse( \manager\Checker::run('varchar(0,10)', 10 ) ); + $this->assertFalse( \manager\Checker::run('varchar(0,10)', array() ) ); } /* (2) Borne inferieure */ @@ -54,39 +54,39 @@ $min = rand(1, 50); $string = str_repeat('a', $min-1); - $this->assertFalse( \manager\Database::check("varchar($min, 255)", $string) ); + $this->assertFalse( \manager\Checker::run("varchar($min, 255)", $string) ); } public function testVarcharEqMin(){ $min = rand(1, 50); $string = str_repeat('a', $min); - $this->assertTrue( \manager\Database::check("varchar($min, 255)", $string) ); + $this->assertTrue( \manager\Checker::run("varchar($min, 255)", $string) ); } public function testVarcharGtMin(){ $min = rand(1, 50); $string = str_repeat('a', $min+1); - $this->assertTrue( \manager\Database::check("varchar($min, 255)", $string) ); + $this->assertTrue( \manager\Checker::run("varchar($min, 255)", $string) ); } /* (3) Borne superieure */ - public function testVarcharLtMax(){ + public function testVarcharLtMax(){ $max = rand(1, 255); $string = str_repeat('a', $max-1); - $this->assertTrue( \manager\Database::check("varchar(0, $max)", $string) ); + $this->assertTrue( \manager\Checker::run("varchar(0, $max)", $string) ); } - public function testVarcharEqMax(){ + public function testVarcharEqMax(){ $max = rand(1, 255); $string = str_repeat('a', $max); - $this->assertTrue( \manager\Database::check("varchar(0, $max)", $string) ); + $this->assertTrue( \manager\Checker::run("varchar(0, $max)", $string) ); } - public function testVarcharGtMax(){ + public function testVarcharGtMax(){ $max = rand(1, 255); $string = str_repeat('a', $max+1); - $this->assertFalse( \manager\Database::check("varchar(0, $max)", $string) ); + $this->assertFalse( \manager\Checker::run("varchar(0, $max)", $string) ); } @@ -94,34 +94,34 @@ =========================================================*/ /* (1) Type */ public function testArrayTypeCorrect(){ - $this->assertTrue( \manager\Database::check('array', array() ) ); + $this->assertTrue( \manager\Checker::run('array', array() ) ); } public function testArrayTypeIncorrect(){ - $this->assertFalse( \manager\Database::check('array', 10 ) ); - $this->assertFalse( \manager\Database::check('array', 'string' ) ); + $this->assertFalse( \manager\Checker::run('array', 10 ) ); + $this->assertFalse( \manager\Checker::run('array', 'string' ) ); } /* (2) Tests divers */ public function testArrayEmpty(){ $arr = array(); - $this->assertTrue( \manager\Database::check("array", $arr) ); + $this->assertTrue( \manager\Checker::run("array", $arr) ); } public function testArrayNotEmpty(){ $arr = array('a', 'b'); - $this->assertTrue( \manager\Database::check("array", $arr) ); + $this->assertTrue( \manager\Checker::run("array", $arr) ); } public function testArrayAllRight(){ $arr = array('a', 'aa', 'a', 'bb'); - $this->assertTrue( \manager\Database::check("array", $arr) ); + $this->assertTrue( \manager\Checker::run("array", $arr) ); } public function testArrayOneWrong(){ $arr = array('a', 'aa', 'a', 'bb', 'aaa'); - $this->assertFalse( \manager\Database::check("array", $arr) ); + $this->assertFalse( \manager\Checker::run("array", $arr) ); } @@ -133,7 +133,7 @@ array(1, 100) ); - $this->assertFalse( \manager\Database::check("array>", $arr) ); + $this->assertFalse( \manager\Checker::run("array>", $arr) ); } @@ -143,25 +143,25 @@ /* (1) Size */ public function testMailSizeEqCorrect(){ $this->assertLessThanOrEqual( 50, strlen('nom-prenom.mot@domaine-d.gouv') ); - $this->assertTrue( \manager\Database::check('mail', 'nom-prenom.mot@domaine-d.gouv') ); + $this->assertTrue( \manager\Checker::run('mail', 'nom-prenom.mot@domaine-d.gouv') ); } public function testMailSizeSupCorrect(){ $this->assertGreaterThan( 50, strlen('ab12345678901234567890nom-prenom.mot@domaine-d.gouv') ); - $this->assertFalse( \manager\Database::check('mail', 'ab12345678901234567890nom-prenom.mot@domaine-d.gouv') ); + $this->assertFalse( \manager\Checker::run('mail', 'ab12345678901234567890nom-prenom.mot@domaine-d.gouv') ); } /* (2) Content */ public function testMailContentCorrect(){ - $this->assertTrue( \manager\Database::check('mail', '0nom-prenom.mot@domaine-d.gouv') ); + $this->assertTrue( \manager\Checker::run('mail', '0nom-prenom.mot@domaine-d.gouv') ); } public function testMailContentIncorrect1(){ - $this->assertFalse( \manager\Database::check('mail', '0nom-prenom.mot@domaine-d.gouve') ); + $this->assertFalse( \manager\Checker::run('mail', '0nom-prenom.mot@domaine-d.gouve') ); } public function testMailContentIncorrect2(){ - $this->assertFalse( \manager\Database::check('mail', '0nom-prenom.mot@domaine-d.g') ); + $this->assertFalse( \manager\Checker::run('mail', '0nom-prenom.mot@domaine-d.g') ); } @@ -172,35 +172,35 @@ $password_hash = \manager\sessionManager::sha1('monmotdepasse'); $this->assertEquals( 40, strlen($password_hash) ); - $this->assertTrue( \manager\Database::check('sha1', $password_hash) ); + $this->assertTrue( \manager\Checker::run('sha1', $password_hash) ); } public function testPasswordSizeInfIncorrect(){ $password_hash = 'a'; - + $this->assertLessThan( 40, strlen($password_hash) ); - $this->assertFalse( \manager\Database::check('sha1', $password_hash) ); + $this->assertFalse( \manager\Checker::run('sha1', $password_hash) ); } public function testPasswordSizeSupIncorrect(){ $password_hash = \manager\sessionManager::sha1('monmotdepasse').'a'; - + $this->assertGreaterThan( 40, strlen($password_hash) ); - $this->assertFalse( \manager\Database::check('sha1', $password_hash) ); + $this->assertFalse( \manager\Checker::run('sha1', $password_hash) ); } public function testPasswordContentCorrect(){ - $this->assertTrue( \manager\Database::check('sha1', 'dd629d39c4576731a2bef003c72ff89d6fc2a99a') ); + $this->assertTrue( \manager\Checker::run('sha1', 'dd629d39c4576731a2bef003c72ff89d6fc2a99a') ); } public function testPasswordContentIncorrect(){ $this->assertContains( 'g', 'dd629d39c4576731a2bef003c72ff89d6fc2a9g' ); - $this->assertFalse( \manager\Database::check('sha1', 'dd629d39c4576731a2bef003c72ff89d6fc2a9g') ); + $this->assertFalse( \manager\Checker::run('sha1', 'dd629d39c4576731a2bef003c72ff89d6fc2a9g') ); } } -?> \ No newline at end of file +?>