diff --git a/README.md b/README.md index 6c29f9d..a4c0412 100644 --- a/README.md +++ b/README.md @@ -52,4 +52,4 @@ xdrm-framework is based on `all is in config` so you will have this structure : #### [3.3] database - database wrapper and repository manager #### [3.4] orm - sql orm #### [3.5] router - apache2 router -#### [3.6] lightdb - fast key-value storage \ No newline at end of file +#### [3.6] lightdb - fast key-value storage diff --git a/exporter/Builder.php b/exporter/Builder.php new file mode 100644 index 0000000..849d4cb --- /dev/null +++ b/exporter/Builder.php @@ -0,0 +1,64 @@ + Path to project root + * @pModules Modules to load + * + */ + public function __construct($pRoot, $pModules){ + /* [1] Stores the path + =========================================================*/ + $this->root = $pRoot; + + /* [2] Stores the modules + =========================================================*/ + $this->modules = $pModules; + } + + + + public function build(){ + + /* [1] Builds project's base file structure + =========================================================*/ + /* (1) Copy from src/files */ + shell_exec("cp -r ".__ROOT__."/src/files/* ".$this->root); + + + + /* [2] Browse each module to load + =========================================================*/ + foreach($this->modules as $module=>$version){ + $path = "/$module/$version"; + + /* (1) Copy module folder if it exists */ + if( file_exists(__ROOT__."/src/modules$path/") && is_dir(__ROOT__."/src/modules$path/") && count(scandir(__ROOT__."/src/modules$path/")) > 2 ) + shell_exec("cp -r ".__ROOT__."/src/modules$path ".$this->root."/build/$module"); + + /* (2) Copy module config if it exists */ + if( file_exists(__ROOT__."/src/config$path/") && is_dir(__ROOT__."/src/config$path/") && count(scandir(__ROOT__."/src/config$path/")) > 2 ) + shell_exec("cp -r ".__ROOT__."/src/config$path/* ".$this->root."/config/"); + + } + + } + + + + + + + + + } diff --git a/exporter/Exporter.php b/exporter/Exporter.php index 0fa880d..bcdbb0d 100644 --- a/exporter/Exporter.php +++ b/exporter/Exporter.php @@ -1,5 +1,7 @@ LOADS CONFIG * */ @@ -17,6 +22,9 @@ } + + + /* RETURNS AVAILABLE MODULE LIST * * @return modules Set containing modules and their versions @@ -29,7 +37,11 @@ $modules[$module] = []; foreach($versions as $version=>$dependencies) - $modules[$module][] = $version; + // if version of module enabled + if( isset($this->modules['enabled'][$module]) && is_array($this->modules['enabled'][$module]) && in_array($version, $this->modules['enabled'][$module]) ) + $modules[$module][] = [ 'version' => $version, 'enabled' => true ]; + else + $modules[$module][] = [ 'version' => $version, 'enabled' => false ]; } return $modules; @@ -65,12 +77,12 @@ /* [2] Version management =========================================================*/ /* (1) Set default version name & content */ - $vname = array_slice(array_keys($module), -1)[0]; + $vname = array_slice( array_keys($module), -1 )[0]; $version = $module[$vname]; /* (2) If wrong version given, set to default */ if( is_null($pVersion) || !isset($module[$pVersion]) ) - echo "chosing latest $vname version.\n"; + echo "chosing latest version:\n-----------------------\n [x] $mname:$vname (can throw errors)\n\n"; /* (2) Else, we get given @version */ else @@ -84,14 +96,52 @@ if( !isset($this->modules['enabled'][$mname]) ) $this->modules['enabled'][$mname] = '0'; - /* (2) If not dependency or version lower -> Set version */ + /* (3) Checks cross version dependency -> trying to enable lower-than-required version of a module*/ + $crossDep = false; + + // For each available module + foreach($this->modules['enabled'] as $xModule=>$xVersion){ + + // Loads each module's dependencies + if( !isset($this->modules['available'][$xModule][$xVersion]) ) + continue; + + $xDependencies = $this->modules['available'][$xModule][$xVersion]; + + // if module needs a higher version of current module + if( isset($xDependencies[$mname]) && $this->lower($this->modules['available'][$mname], $vname, $xDependencies[$mname]) ){ + $crossDep = true; + break; + } + + } + + + /* (4) If trying to load lower than required version -> error */ + if( $crossDep ){ + + // Update module's version + if( $this->lower($this->modules['available'][$mname], $this->modules['enabled'][$mname], $xDependencies[$mname]) ) + $this->modules['enabled'][$mname] = $xDependencies[$mname]; + + $this->store(); + + return "module `$xModule:$xVersion` needs `$mname:".$xDependencies[$mname]."`\n\naborted.\n"; + } + + + /* (5) If not a dependency or higher version -> set/update version */ if( !$pDep || $this->lower($module, $this->modules['enabled'][$mname], $vname) ){ - if( $pDep ) echo "- $mname-$vname ($vname+ required)\n"; + // if a dependency, set new params + if( $pDep ) + echo " [x] $mname:$vname (version $vname+ required)\n"; + // else, store new module $this->modules['enabled'][$mname] = $vname; - } else if( $pDep ) echo "- $mname-".$this->modules['enabled'][$mname]." ($vname+ required)\n"; + }else if( $pDep ) + echo " [x] $mname:".$this->modules['enabled'][$mname]." (version $vname+ required)\n"; /* [4] Loading dependencies @@ -181,6 +231,27 @@ } + + + + + /* BUILDS A PROJECT + * + * @pPath Path to project root + * + */ + public function build($pPath){ + $builder = new Builder($pPath, $this->modules['enabled']); + + $builder->build(); + } + + + + + + + /* CHECKS IF @pFirst IS LOWER THAN @pSecond VERSION * * @pModule Module's content @@ -202,6 +273,9 @@ /* (2) Get @pSecond index */ $ps_index = array_search($pSecond, $keys); + if( $pf_index === false ) + return true; + return $pf_index < $ps_index; } diff --git a/exporter/main.php b/exporter/main.php index 35b1643..910b3bf 100644 --- a/exporter/main.php +++ b/exporter/main.php @@ -32,7 +32,11 @@ foreach($modules as $module=>$versions){ foreach($versions as $version) - echo " - $module-$version\n"; + // if enabled + if( $version['enabled'] ) + echo " [x] $module (version ".$version['version'].")\n"; + else + echo " [ ] $module (version ".$version['version'].")\n"; echo "\n"; } @@ -43,15 +47,15 @@ ---------------------------------------------------------*/ case 'enable': { - if( $arglen < 2 || !preg_match("/^(.+)-([0-9\.]+)$/i", $arguments[1], $matches) ){ - echo "You must specify @module-@version.\n"; + if( $arglen < 2 || !preg_match("/^(.+):([0-9\.-]+)$/i", $arguments[1], $matches) ){ + echo "You must specify @module:@version.\n"; return; } $err = $exporter->enable($matches[1], $matches[2]); /* (1) Managing state */ - if( $err === true ) echo "success.\n"; + if( $err === true ) echo "\n\n** success **\n"; else echo $err; } break; @@ -68,7 +72,27 @@ $err = $exporter->disable($arguments[1]); /* (1) Managing state */ - if( $err === true ) echo "success.\n"; + if( $err === true ) echo "\n\n** success **\n"; + else echo $err; + + } break; + + /* (3) Builds a project + ---------------------------------------------------------*/ + case 'build': { + + if( $arglen < 2 || !is_dir($arguments[1]) ){ + echo "You must specify your project root's @path.\n"; + return; + } + + // Removes the optional final '/' + $arguments[1] = preg_replace('/^(.+)\/$/', '$1', $arguments[1]); + + $err = $exporter->build($arguments[1]); + + /* (1) Managing state */ + if( $err === true ) echo "\n\n** success **\n"; else echo $err; } break; diff --git a/exporter/modules.json b/exporter/modules.json index 3ba694a..434867c 100644 --- a/exporter/modules.json +++ b/exporter/modules.json @@ -1,35 +1,43 @@ { - "available": { - "error": { - "1": [], - "1.2": [], - "1.5": [] - }, - "api": { - "0.8": [], - "1": { - "error": "1.2" - } - }, - "orm": { - "0.8": { - "database": "1" - } - }, - "database": { - "1": { - "error": "1" - } - }, - "lightdb": { - "1": [] - }, - "router": { - "1": [] - } - }, - "enabled": { - "api": "1", - "error": "1.2" - } -} \ No newline at end of file + "available": { + "error": { + "1.0": [], + "2.0": [] + }, + "api": { + "1.0": { + "error": "1.0" + }, + "2.0": { + "error": "2.0" + } + }, + "orm": { + "0.8-1": { + "database": "1.0" + }, + "0.8-2": { + "database": "2.0" + } + }, + "database": { + "1.0": { + "error": "1.0" + }, + "2.0": { + "error": "2.0" + } + }, + "lightdb": { + "1.0": [] + }, + "router": { + "1.0": [] + } + }, + "enabled": { + "orm": "0.8", + "database": "1.0", + "error": "1.0" + } +} diff --git a/src/config/api/1/modules.json b/src/config/api/1.0/modules.json similarity index 100% rename from src/config/api/1/modules.json rename to src/config/api/1.0/modules.json diff --git a/src/config/api/2.0/modules.json b/src/config/api/2.0/modules.json new file mode 100644 index 0000000..a982879 --- /dev/null +++ b/src/config/api/2.0/modules.json @@ -0,0 +1,60 @@ +{ + + "RESTexample": { + "POST::article": { + "description": "Posts a new article", + "permissions": ["journalist"], + "parameters": { + "title": { "description": "Article's title", "type": "varchar(5,100)" }, + "content": { "description": "Article's content", "type": "text" } + }, + "output": { + "created_id": { "description": "Id of the created article", "type": "id" } + } + }, + + "GET::article": { + "description": "Gets all or a specific article", + "permissions": ["viewer", "journalist"], + "parameters": { + "URL_0": { "description": "Article id", "type": "id", "optional": true } + }, + "output": { + "articles": { "description": "List of selected articles", "type": "array" } + } + }, + + "VIEW::article": { + "description": "Gets a specific article into a json file (download)", + "permissions": ["viewer", "journalist"], + "options": { "download": true }, + "parameters": { + "URL_0": { "description": "Article id", "type": "id" } + }, + "output": { + "article": { "description": "Selected article as JSON file", "type": "text" } + } + }, + + "PUT::article": { + "description": "Updates a specific article", + "permissions": ["journalist"], + "parameters": { + "URL_0": { "description": "Article id", "type": "id" }, + "content": { "description": "Article's content", "type": "text" } + }, + "output": { + "article": { "description": "Returns updated article", "type": "array" } + } + }, + + "DELETE::article": { + "description": "Deletes a specific article", + "permissions": ["journalist"], + "parameters": { + "URL_0": { "description": "Article id", "type": "id" } + }, + "output": {} + } + } +} diff --git a/src/config/database/1/database-local.json b/src/config/database/1.0/database-local.json similarity index 100% rename from src/config/database/1/database-local.json rename to src/config/database/1.0/database-local.json diff --git a/src/config/database/1/database.json b/src/config/database/1.0/database.json similarity index 100% rename from src/config/database/1/database.json rename to src/config/database/1.0/database.json diff --git a/src/config/database/1/repositories.json b/src/config/database/1.0/repositories.json similarity index 100% rename from src/config/database/1/repositories.json rename to src/config/database/1.0/repositories.json diff --git a/src/config/database/2.0/database-driver.json b/src/config/database/2.0/database-driver.json new file mode 100644 index 0000000..4f51030 --- /dev/null +++ b/src/config/database/2.0/database-driver.json @@ -0,0 +1,16 @@ +{ + "default": { + "local": { + "host" : "db_local_host", + "dbname" : "db_local_name", + "user" : "db_local_user", + "password" : "db_local_password" + }, + "remote": { + "host" : "db_remote_host", + "dbname" : "db_remote_name", + "user" : "db_remote_user", + "password" : "db_remote_password" + } + } +} diff --git a/src/modules/api/1/core/Authentification.php b/src/modules/api/1.0/core/Authentification.php similarity index 100% rename from src/modules/api/1/core/Authentification.php rename to src/modules/api/1.0/core/Authentification.php diff --git a/src/modules/api/1/core/Checker.php b/src/modules/api/1.0/core/Checker.php similarity index 100% rename from src/modules/api/1/core/Checker.php rename to src/modules/api/1.0/core/Checker.php diff --git a/src/modules/api/1/core/ModuleRequest.php b/src/modules/api/1.0/core/ModuleRequest.php similarity index 100% rename from src/modules/api/1/core/ModuleRequest.php rename to src/modules/api/1.0/core/ModuleRequest.php diff --git a/src/modules/api/1/core/ModuleResponse.php b/src/modules/api/1.0/core/ModuleResponse.php similarity index 100% rename from src/modules/api/1/core/ModuleResponse.php rename to src/modules/api/1.0/core/ModuleResponse.php diff --git a/src/modules/api/1/module/module.php b/src/modules/api/1.0/module/module.php similarity index 100% rename from src/modules/api/1/module/module.php rename to src/modules/api/1.0/module/module.php diff --git a/src/modules/api/2.0/core/Authentification.php b/src/modules/api/2.0/core/Authentification.php new file mode 100644 index 0000000..03903d7 --- /dev/null +++ b/src/modules/api/2.0/core/Authentification.php @@ -0,0 +1,60 @@ +answer(); + + if( $user != false ){ + $this->userType = $user["Type"]; + $this->userId = $user["Id"]; + } + + new Repo("AuthentificationRepo/updateToken",[$token]); + + new Repo("AuthentificationRepo/purgeOldTokens",[]); + + self::$instance = $this; + } + + public function getUserType(){ + return $this->userType; + } + + public function getUserId(){ + return $this->userId; + } + + public static function permission(array $perm){ + if(in_array(self::$instance->userType,$perm)){ + return new Error(Err::Success); + } + + return new Error(Err::TokenError); + } + + /** + * @return Authentification + */ + public static function getInstance(){ + return self::$instance; + } + + + } diff --git a/src/modules/api/2.0/core/Checker.php b/src/modules/api/2.0/core/Checker.php new file mode 100644 index 0000000..7b4f3c0 --- /dev/null +++ b/src/modules/api/2.0/core/Checker.php @@ -0,0 +1,150 @@ + 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; + + case 'numeric': + return $checker && (is_numeric($value) || $value == null || $value == 'null'); + break; + + default: + return false; + break; + } + + return $checker; + + } + + + } +?> diff --git a/src/modules/api/2.0/core/ModuleFactory.php b/src/modules/api/2.0/core/ModuleFactory.php new file mode 100644 index 0000000..527d5e9 --- /dev/null +++ b/src/modules/api/2.0/core/ModuleFactory.php @@ -0,0 +1,31 @@ + Nom du module + * @arguments [OPTIONNEL] Arguments à passer au constructeur + * + * @return instance Instance du module en question + * + */ + public static function getModule($module, $arguments=[]){ + /* (1) On gère les arguments */ + $arguments = is_array($arguments) ? $arguments : []; + + /* (1) On vérifie que la classe existe */ + if( !file_exists(__BUILD__."/api/module/$module.php") ) + return false; + + /* (2) On récupère la classe */ + $class_name = "\\api\\module\\$module"; + + /* (3) On retourne une instance */ + return new $class_name($arguments); + } + + } diff --git a/src/modules/api/2.0/core/Request.php b/src/modules/api/2.0/core/Request.php new file mode 100644 index 0000000..9c8f4e3 --- /dev/null +++ b/src/modules/api/2.0/core/Request.php @@ -0,0 +1,581 @@ + false + ]; + + // Attributs prives utiles (initialisation) + private $path; + private $params; + private $modules; + private $options; + + // Contiendra la reponse a la requete + public $answer; + + // Contiendra l'etat de la requete + public $error; + + + + + + /* CONSTRUCTEUR D'UNE REQUETE DE MODULE + * + * @path Chemin de delegation ("module/methode") + * @param Tableau associatif contenant les parametres utiles au traitement + * + * @return status Retourne si oui ou non tout s'est bien passe + * + */ + public function __construct($path=null, $params=null){ + /* [0] Initialisation + =========================================================*/ + /* (1) Erreur par défaut */ + $this->error = new Error(Err::Success); + + /* (2) Si pas parametre manquant, on quitte */ + if( $path == null ){ + $this->error->set(Err::MissingPath); + return false; + } + + + /* [1] On met a jour la configuration + =========================================================*/ + /* (1) Section Title */ + $this->modules = json_decode( file_get_contents(self::config_path()), true ); + + /* (2) Gestion de l'erreur de parsage */ + if( $this->modules == null ){ + $this->error->set(Err::ParsingFailed, 'json'); + return false; + } + + + + /* [2] Verification des types des parametres + =========================================================*/ + /* (1) Section Title */ + if( !is_string($path) ){ // Si le type est incorrect + $this->error->set(Err::WrongPathModule); + return false; // On retourne FALSE, si erreur + } + + /* (2) Section Title */ + $params = (is_array($params)) ? $params : []; + + /* (3) On définit en constante la méthode HTTP */ + define('__HTTP_METHOD__', strtoupper($_SERVER['REQUEST_METHOD'])); + + + /* [3] Verification du chemin (existence module+methode) + =========================================================*/ + if( !$this->checkPath($path) ) // Verification de la coherence du chemin + attribution + return false; + + + /* [4] Verification des droits + =========================================================*/ + if( !$this->checkPermission() ) // Si on a pas les droits + return false; + + + /* [5] Verification des parametres (si @type est defini) + =========================================================*/ + if( !$this->checkParams($params) ) // Verification de tous les types + return false; + + + /* [6] Récupèration des options + =========================================================*/ + $this->buildOptions(); + + + /* [7] Construction de l'objet + =========================================================*/ + $this->params = $params; + $this->error->set(Err::Success); + + return true; // On retourne que tout s'est bien passe + + } + + + + /* EXECUTE LE TRAITEMENT ASSOCIE ET REMPLIE LA REPONSE + * + * @return answer Retourne une reponse de type si tout s'est bien passe + * + */ + public function dispatch(){ + /* [0] Si c'est un download, on lance la methode `download()` + =========================================================*/ + if( $this->options['download'] === true ) + return $this->download(); + + /* [1] On verifie qu'aucune erreur n'a ete signalee + =========================================================*/ + if( $this->error->get() !== Err::Success ) // si il y a une erreur + return new Response($this->error); // on la passe a la reponse + + + /* [2] On essaie d'instancier le module + =========================================================*/ + $instance = ModuleFactory::getModule($this->path['module']); + + if( $instance === false ){ + $this->error->set(Err::UncallableModule, $this->path['module']); + return new Response($this->error); + } + + /* [3] On verifie que la methode est amorcable + =========================================================*/ + if( !is_callable([$instance, $this->getModuleMethod()]) ){ + $this->error->set(Err::UncallableMethod, preg_replace('/\w+::/i', '', $this->path['method']) ); + return new Response($this->error); + } + + + /* [4] On amorce la methode + =========================================================*/ + /* (1) On lance la fonction */ + $returned = call_user_func( [$instance, $this->getModuleMethod()], $this->params ); + + /* (2) On appelle le destructeur (si défini) */ + $instance = null; + + + /* [5] Gestion de la reponse + =========================================================*/ + /* (1) On construit la réponse avec l'erreur */ + $response = new Response($this->error); + + /* (2) On ajoute les données */ + $response->appendAll($returned); + + // On retourne la réponse + return $response; + } + + + + /* EXECUTE LE TRAITEMENT ASSOCIE ET RENVOIE UN FICHIER AVEC LE HEADER ET LE BODY SPECIFIE + * + */ + public function download(){ + /* [1] On verifie qu'aucune erreur n'a ete signalee + =========================================================*/ + if( $this->error->get() !== Err::Success ) // si il y a une erreur + return new Response($this->error); // on la passe a la reponse + + + /* [2] On essaie d'instancier le module + =========================================================*/ + $instance = ModuleFactory::getModule($this->path['module']); + + if( $instance === false ){ + $this->error->set(Err::UncallableModule); + return new Response($this->error); + } + + /* [3] On verifie que la methode est amorcable + =========================================================*/ + if( !is_callable([$instance, $this->getModuleMethod()]) ){ + $this->error->set(Err::UncallableMethod); + return new Response($this->error); + } + + + /* [4] On amorce la methode + =========================================================*/ + /* (1) On lance la fonction */ + $returned = call_user_func( [$instance, $this->getModuleMethod()], $this->params ); + + /* (2) On appelle le destructeur (si défini) */ + $instance = null; + + + /* [5] Vérification des erreurs et paramètres + =========================================================*/ + /* (1) Vérification de l'erreur retournée, si pas Success, on retourne l'erreur */ + if( isset($returned['error']) && $returned['error'] instanceof Error && $returned['error']->get() != Err::Success ){ + $this->error = $returned['error']; + return new Response($this->error); + } + + /* (2) Vérification du contenu, si pas défini */ + if( !isset($returned['body']) ){ + $this->error->set(Err::ParamError); + return new Response($this->error); + } + + /* (3) Si @headers n'est pas défini on met par défaut */ + if( !isset($returned['headers']) || !is_array($returned['headers']) ) + $returned['headers'] = []; + + $fromAjax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'; + + + /* [6.1] Si requête ajax on crée un fichier temporaire et on renvoie son URL + =========================================================*/ + if( $fromAjax ){ + + + $tmpfname = '/tmp/download_'.uniqid().'.php'; + $bodyfname = __ROOT__.'/tmp/content_'.uniqid().'.php'; + + /* (1) On crée le fichier temporaire */ + $tmpfnameroot = __ROOT__.$tmpfname; + $tmpfile = fopen($tmpfnameroot, 'w'); + + fwrite($tmpfile, '$value) + fwrite($tmpfile, "header(\"$header: $value\");".PHP_EOL); + + /* (3) Script qui écrira le contenu */ + // 1) On écrit le contenu dans un fichier temporaire (et oui encore) + $bodyfile = fopen($bodyfname, 'w'); + fwrite($bodyfile, $returned['body']); + fclose($bodyfile); + chmod($bodyfname, 0775); + + fwrite($tmpfile, "readfile('$bodyfname');".PHP_EOL); + + /* (4) Script qui supprimera les fichiers temporaires */ + fwrite($tmpfile, "unlink('$bodyfname');".PHP_EOL); + fwrite($tmpfile, "unlink(__FILE__);".PHP_EOL); + + fwrite($tmpfile, '?>'.PHP_EOL); + + /* (5) On ferme le fichier */ + fclose($tmpfile); + chmod($tmpfnameroot, 0775); + + $response = new Response($this->error); + $response->append('link', $tmpfname); + + return $response; + + /* [6.2] Gestion du download direct si pas AJAX + =========================================================*/ + }else{ + /* (1) On définit les headers */ + foreach($returned['headers'] as $header=>$value) + header($header.': '.$value); + + /* (2) On affiche le contenu */ + echo $returned['body']; + + return true; + } + } + + + /* DESERIALISATION A PARTIR DE L'URL ET DES DONNEES POST (OPT) + * + * @url Contenu de l'url après api/ (si existe) + * @post [opt] Tableau des donnes + * + * @return instance Retourne un objet de type + * + * @note + * 1. `path` peut être dans l'url : /method/module + * `path` peut être dans les données $_POST + * 2. les données peuvent être dans l'url : /module/method/data1/data2/... + * les données peuvent être dans les données $_POST + * + */ + public static function remote($url, $data=null){ + is_null($data) && ($data = []); + + + /* [1] On verifie que le @path est renseigne + =========================================================*/ + /* (1) Si le path est dans @url */ + $pathInUrl = count($url) > 0 && is_string($url[0]) && strlen($url[0]) > 0 && preg_match('#^([\w_-]+/[\w_-]+)(?:/?|/((?:\w+/)*(?:\w+/?)))$#', $url[0], $urlMatches); + + /* (2) On récupère le @path + les arguments dans l'URL */ + if( $pathInUrl ){ + // {1} On ajoute le @path aux données // + $data['path'] = $urlMatches[1]; + + // {2} On ajoute les arguments d'URL aux données // + if( count($urlMatches) > 2 ){ + + $urlParams = explode('/', trim($urlMatches[2], '/')); + foreach($urlParams as $k=>$v) + $data["URL_$k"] = $v; + + } + + } + + + /* (2) On vérifie dans tous les cas si le path existe */ + if( !isset($data['path']) ) + return new Request(); + + + + /* [3] On met les paramètres en JSON + =========================================================*/ + /* (1) On initialise les paramètres*/ + $params = []; + + /* (2) On met tous les paramètres en json (sauf @path) */ + foreach($data as $name=>$value){ + if( $name === 'path' ) + continue; + + // {1} On met en JSON // + $json = json_decode( $value, true ); + + // {2} Si ok -> on remplace // + if( !is_null($json) ) + $params[$name] = $json; + + // {3} Sinon, on laisse tel quel // + else + $params[$name] = $value; + } + + /* [4] On retourne une instance de + =========================================================*/ + return new Request($data['path'], $params); + } + + + + + + /* VERIFICATION DU FORMAT ET DE LA COHERENCE DU CHEMIN SPECIFIE + * + * @path String correspondant au chemin de delegation ("module/methode") + * + * @return validity Retourne si oui ou non l'objet est correct + * + */ + private function checkPath($path){ + /* [1] Verification format general + =========================================================*/ + if( !preg_match('#^([\w_-]+)/([\w_-]+)$#i', $path, $matches) ){ // Si mauvais format + $this->error->set(Err::WrongPathModule); + return false; + } + + // On recupere les données de la regex + $module = $matches[1]; + $method = __HTTP_METHOD__.'::'.$matches[2]; + + + /* [2] Verification de l'existence du module (conf) + =========================================================*/ + if( !array_key_exists($module, $this->modules) ){ // Si le module n'est pas specifie dans la conf + $this->error->set(Err::UnknownModule, $module); + return false; // On retourne FALSE, si erreur + } + + + /* [3] Verification de l'existence de la methode (conf) + =========================================================*/ + if( array_key_exists($method, $this->modules[$module]) === false ){ // Si la methode n'est pas specifie dans la conf + $this->error->set(Err::UnknownMethod, preg_replace('/\w+::/i', '', $method) ); + return false; // On retourne FALSE, si erreur + } + + + + /* [4] Enregistrement du chemin et renvoi de SUCCESS + =========================================================*/ + $this->path = [ + 'module' => $module, + 'method' => $method + ]; + + return true; + } + + + + + + + /* RETOURNE SI ON A LA PERMISSION D'EXECUTER CETTE METHODE + * + * @return permission Retourne si on a les droits ou pas pour executer cette methode + * + */ + private function checkPermission(){ + /* [1] On recupere les informations utiles + =========================================================*/ + // On recupere le nom de la methode + $method = $this->modules[$this->path['module']][$this->path['method']]; + + // Si aucune permission n'est definie + if( !isset($method['permissions']) ) return true; + + + + /* [2] Vérification des permissions et de l'authentification + =========================================================*/ + if(!empty($method['permissions'])){ + $granted = Authentification::permission($method['permissions']); + + /* (1) On retourne FAUX si aucun droit n'a ete trouve */ + if( $granted->get() !== Err::Success ){ + $this->error = $granted; + return false; + } + } + + + + /* On retourne VRAI si la permission est ok */ + return true; + } + + + + + /* VERIFICATION DU TYPE DES PARAMETRES ENVOYES + * + * @params Tableau associatif contenant les parametres + * @params peut se voir rajouter les paramètres optionnels s'ils ne sont pas renseignés (initialisés à NULL) + * + * @return correct Retourne si oui ou non les parametres ont le bon type + * + */ + private function checkParams(&$params){ + /* [1] On verifie qu'il ne manque aucun parametre + =========================================================*/ + // Si @params n'est pas un tableau + if( !is_array($params) ){ + $this->error->set(Err::ConfigError); + return false; + } + + $method = $this->modules[$this->path['module']][$this->path['method']]; + + + /* [2] Si le type est defini, pour chaque param, on teste + =========================================================*/ + foreach($method['parameters'] as $name=>$paramsdata){ + /* (1) On récupère si le paramètre est optionnel ou pas */ + $optional = isset($paramsdata['optional']) && $paramsdata['optional'] === true; + + /* (2) Récupère si le paramètre est un fichier et définit comme de type 'FILE' */ + $isFile = isset($paramsdata['type']) && $paramsdata['type'] == 'FILE' && isset($_FILES[$name]); + + /* (3) Si le paramètre est obligatoire et qu'il n'est pas donné -> erreur */ + if( !isset($params[$name]) && !$optional && !$isFile ){ + $this->error->set(Err::MissingParam, $name); + return false; + } + + /* (4) Si le type n'est pas defini, on a pas besoin de le vérifier */ + if( !isset($paramsdata['type']) ) + continue; + + /* (5) Si le paramètre est optionnel et n'est pas donné */ + if( $isFile || $optional && (!isset($params[$name]) || is_null($params[$name])) ){ + // On le crée le param optionnel avec la valeur NULL + $params[$name] = null; + + // On donne une référence vers le fichier, si c'en est un + if( $isFile ) + $params[$name] = &$_FILES[$name]; + + continue; // On passe au paramètre suivant + + + /* (6) Si le paramètre est renseigné */ + }else + // Si la verification est fausse, on retourne faux + if( !Checker::run($paramsdata['type'], $params[$name]) ){ + $this->error->set(Err::WrongParam, $name, $paramsdata['type']); + return false; + } + + } + + /* [3] Gestion du retour, si tout s'est bien passe + =========================================================*/ + return true; + } + + + + + + /* AJOUT DES OPTIONS A PARTIR DE LA CONFIGURATION + * + */ + private function buildOptions(){ + /* [0] On récupère les options de la méthode en cours + =========================================================*/ + $method = $this->modules[$this->path['module']][$this->path['method']]; + + /* (1) Si 'option' n'est pas défini (ou incorrect), on met les valeurs par défaut */ + if( !isset($method['options']) || !is_array($method['options']) ) + return true; + + /* (2) Par défaut on définit les options par défaut */ + $this->options = self::$default_options; + + + /* (3) On récupère les options données */ + $options = $method['options']; + + + /* [1] Gestion des différentes options + =========================================================*/ + foreach($options as $option=>$value){ + /* (1) On ne prend en compte l'option que si elle est dans les options par défaut */ + if( !isset(self::$default_options[$option]) ) + continue; + + /* (2) Le type de la valeur doit être le même que celui de la valeur par défaut */ + if( gettype($value) != gettype(self::$default_options[$option]) ) + continue; + + /* (3) Si tout est bon, on définit la valeur */ + $this->options[$option] = $value; + } + + return true; + + } + + + + + + /* RENVOI LE CHEMIN D'AMORCAGE DE LA METHODE + * + * @return method Retourne le chemin d'amorcage de la method + * + */ + private function getModuleMethod(){ + /* (1) On essaie de trouver le bon nom */ + return preg_replace('/\w+::/i', '', $this->path['method']); + } + + + } + +?> diff --git a/src/modules/api/2.0/core/Response.php b/src/modules/api/2.0/core/Response.php new file mode 100644 index 0000000..840f91b --- /dev/null +++ b/src/modules/api/2.0/core/Response.php @@ -0,0 +1,122 @@ + Erreur passee par la requete (si existe) + * + */ + public function __construct($error=null){ + if( !( $error instanceof Error ) ) + $error = new Error(Err::Success); + + $this->data = []; + $this->error = $error; + } + /* AJOUTE UNE DONNEE A LA REPONSE + + + * + * @key Le nom de la valeur a ajouter + * @value La valeur a ajouter + * + */ + public function append($key, $value){ + // Ajoute une entree pour la cle @key et de valeur @value + $this->data[$key] = $value; + + return $this; + } + + + /* AJOUTE TOUTES LES DONNEES A LA REPONSE + * + * @dataset Le tableau associatif correspondant a la reponse + * + */ + public function appendAll($dataset){ + // Si ce n'est pas un tableau, on ne fais rien + if( !is_array($dataset) ) + return $this; + + // Si une valeur contient une erreur + if( array_key_exists('error', $dataset) && $dataset['error'] instanceof Error){ + // On definit cette erreur + $this->error = $dataset['error']; + // On enleve cette entree des donnees + unset($dataset['error']); + } + + // Ajoute une entree pour la cle @key et de valeur @value + $this->data = $dataset; + + return $this; + } + /* RECUPERE UNE DONNEE DE LA REPONSE + + + * + * @key Le nom de la valeur a recuperer + * + * @return value La valeur a cette cle + * @return error Retourne NULL si aucune valeur pour cette cle + * + */ + public function get($key){ + // Si la valeur de cle @key n'existe pas, on retourne NULL + if( !isset($this->data[$key]) ) + return null; + + // Sinon, on retourne la valeur associee + return $this->data[$key]; + } + + + /* RECUPERE TOUTES LES DONNEES DE LA REPONSE + * + * @return data Les donnees de la reponse + * + */ + public function getAll(){ + // Sinon, on retourne la valeur associee + return $this->data; + } + + + /* SERIALISATION A PARTIR DES DONNEES + * + * @return json Retourne les donnees serialisees + * + */ + public function serialize(){ + + // Code Http + $this->error->setHttpCode(); + + // Type de contenu + header('Content-Type: application/json; charset=utf-8'); + + // On rajoute l'erreur au message + $returnData = array_merge([ + 'error' => $this->error->get(), + 'ErrorDescription' => $this->error->explicit() + ], + $this->data + ); + + return json_encode($returnData); + + } + } + +?> diff --git a/src/modules/api/2.0/module/RESTexample.php b/src/modules/api/2.0/module/RESTexample.php new file mode 100644 index 0000000..decfdec --- /dev/null +++ b/src/modules/api/2.0/module/RESTexample.php @@ -0,0 +1,116 @@ + new Error(Err::ModuleError)]; // or other `Err` constant + + return ['created_id' => $output_created_id]; + break; + + case 'GET': + // GET all/an article with the variable: + $URL_0; // id of article ; if not given -> null + + // ... + // process to get articles and get $output_get_articles + // ... + + if( !$success ) + return ['error' => new Error(Err::ModuleError)]; // or other `Err` constant + + return ['articles' => $output_get_articles]; + break; + + case 'VIEW': + // VIEW a specific article (download json file) with the variable: + $URL_0; // id of article ; if not given -> null + + // ... + // process to get articles and get $output_get_articles + // ... + + if( !$success ) + return ['error' => new Error(Err::ModuleError)]; // or other `Err` constant + + // will download, but if AJAX will give a `link` to the file + return [ + 'headers' => [ + 'Content-Type' => 'application/json; charset=utf-8', + 'Content-Disposition' => 'attachment; filename=export'.date('_d_m_Y', time()).'.json', + 'Pragma' => 'no-cache', + 'Expires' => '0' + ], + 'body' => json_encode($output_get_articles) + ]; + break; + + case 'PUT': + // UPDATE an article with new content with variables: + $URL_0; // id of article to update + $title; // new article's title + $content; // new article's content + + // ... + // process to get $output_updated_article + // ... + + if( !$success ) + return ['error' => new Error(Err::ModuleError)]; // or other `Err` constant + + return ['article' => $output_updated_article]; + break; + + case 'DELETE': + // DELETEs an article with the variable: + $URL_0; // id of the article to remove + + // ... + // process to delete article + // ... + + if( !$success ) + return ['error' => new Error(Err::ModuleError)]; // or other `Err` constant + + return []; // returns success + break; + + // if no match -> error + default: + return ['error' => new Error(Err::UnknownHttpMethod)]; + break; + } + + } + + +} diff --git a/src/modules/database/1/core/Database.php b/src/modules/database/1.0/core/Database.php similarity index 100% rename from src/modules/database/1/core/Database.php rename to src/modules/database/1.0/core/Database.php diff --git a/src/modules/database/1/core/Repo.php b/src/modules/database/1.0/core/Repo.php similarity index 100% rename from src/modules/database/1/core/Repo.php rename to src/modules/database/1.0/core/Repo.php diff --git a/src/modules/database/2.0/core/DatabaseDriver.php b/src/modules/database/2.0/core/DatabaseDriver.php new file mode 100644 index 0000000..5388dce --- /dev/null +++ b/src/modules/database/2.0/core/DatabaseDriver.php @@ -0,0 +1,180 @@ + Database Server's host + * @dbname Database name + * @username Database username + * @password Database password + * + */ + private function __construct($host, $dbname, $username, $password){ + /* (2) Stores configuration */ + $this->host = $host; + $this->dbname = $dbname; + $this->username = $username; + $this->password = $password; + + try{ + + $this->pdo = new \PDO('mysql:host='.$this->host.';dbname='.$this->dbname, $this->username, $this->password); + $this->pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC); + + // On signale que tout s'est bien passe + $this->error = new Error(Err::Success); + + }catch(Exception $e){ + // On signale qu'il y a une erreur + $this->error = new Error(Err::PDOConnection); + } + } + + + + /************************************************ + **** Multiton Management (static) **** + ************************************************/ + + /* ADDS A NEW CONNECTION + * + * @label [optional] Database Label + * + * @return status If added successfully + * + */ + private static function add($label=null){ + $conf = self::conf(); + + /* [1] Default values + =========================================================*/ + /* (1) If label isn't given */ + is_null($label) && ($label = 'default'); + + /* (2) If label and no path */ + if( $label !== 'default' && !isset($conf[$label]) ) + return false; + + + /* [3] Instanciates the driver + =========================================================*/ + try{ + + /* (1) If local -> instanciates with local configuration */ + if( !checkdnsrr($_SERVER['SERVER_NAME'], 'NS') ) + self::$instance[$label] = new DatabaseDriver($conf[$label]['local']['host'], $conf[$label]['local']['dbname'], $conf[$label]['local']['user'], $conf[$label]['local']['password']); + /* (2) If Remote -> instanciates with Remote configuration */ + else + self::$instance[$label] = new DatabaseDriver($conf[$label]['remote']['host'], $conf[$label]['remote']['dbname'], $conf[$label]['remote']['user'], $conf[$label]['remote']['password']); + + return true; + + }catch(\Exception $e){ + + /* (3) If fails */ + return false; + + } + + } + + + /* GET A DATABASE DRIVER INSTANCE + * + * @label [optional] Driver's label + * + * @return driver Multiton + * + */ + public static function get($label=null){ + $conf = self::conf(); + + /* [1] Checks arguments + =========================================================*/ + /* (1) Label default value */ + is_null($label) && ($label = 'default'); + + /* (2) If no label, or unknown label */ + if( is_null($label) || !isset(self::$instance[$label]) ){ + + /* (2.1) Try to add the configuration if exists */ + if( isset($conf[$label]) ){ + self::add($label); + return self::get($label); + } + + + throw new \Exception('Database @label is incorrect.'); + } + + + /* [2] Returns instance + =========================================================*/ + return self::$instance[$label]; + } + + + /** retourne la connection statique + * @param null $label + * @return \PDO + */ + public static function getPDO($label=null){ + $instance = self::get($label); + + return $instance->pdo; + } + + + public function getConfig(){ + return [ + 'host' => $this->host, + 'dbname' => $this->dbname, + 'username' => $this->username + ]; + } + + + + } +?> diff --git a/src/modules/database/2.0/core/Repo.php b/src/modules/database/2.0/core/Repo.php new file mode 100644 index 0000000..41a5bef --- /dev/null +++ b/src/modules/database/2.0/core/Repo.php @@ -0,0 +1,193 @@ + Chemin de delegation ("repo/methode") + * @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, $params=null){ + + // Si pas parametre manquant, on quitte + if( $path == null ){ + $this->error = new Error(Err::MissingPath); + return false; + } + + /* [0] On met a jour la configuration + =========================================================*/ + // Modules specifies + $this->repositories = json_decode( file_get_contents(self::config_path()), true ); + + // Gestion de l'erreur de parsage + if( $this->repositories == null ){ + $this->error = new Error(Err::ParsingFailed, 'json'); + return false; + } + + + + /* [1] Verification des types des parametres + =========================================================*/ + // Type de @path + if( !is_string($path) ){ // Si le type est incorrect + $this->error = new Error(Err::WrongPathRepo); + return false; // On retourne FALSE, si erreur + } + + // Type de @params (optionnel) + $params = (is_array($params)) ? $params : []; + + + /* [2] Verification du chemin (existence repo+methode) + =========================================================*/ + if( !$this->checkPath($path) ) // Verification de la coherence du chemin + attribution + return false; + // Gestion d'erreur interne + + + /* [3] Construction de l'objet + =========================================================*/ + $this->params = $params; + $this->error = new Error(Err::Success); + + /* [4] Enregistrement de la reponse + =========================================================*/ + $this->answer = $this->dispatch(); + + + return true; // On retourne que tout s'est bien passe + + } + + + + + public function answer(){ + if( $this->error->get() !== Err::Success ) + return false; + + return $this->answer; + } + + + + /* EXECUTE LE TRAITEMENT ASSOCIE ET REMPLIE LA REPONSE + * + * @return answer Retourne une reponse, si tout s'est bien passe + * + */ + private function dispatch(){ + /* [1] On verifie qu'aucune erreur n'a ete signalee + =========================================================*/ + if( $this->error->get() !== Err::Success ) // si il y a une erreur + return false; // on la passe a la reponse + + + /* [2] On verifie que la methode est amorcable + =========================================================*/ + if( !is_callable($this->getFunctionCaller()) ){ + $this->error = new Error(Err::UncallableMethod, $this->path['method']); + return false; + } + + + /* [3] On amorce la methode + =========================================================*/ + return call_user_func_array( $this->getFunctionCaller(), $this->params ); + } + + + + /* VERIFICATION DU FORMAT ET DE LA COHERENCE DU CHEMIN SPECIFIE + * + * @path String correspondant au chemin de delegation ("repo/methode") + * + * @return validity Retourne si oui ou non l'objet est correct + * + */ + private function checkPath($path){ + /* [1] Verification format general + =========================================================*/ + if( !preg_match('#^([\w_-]+)/([\w_-]+)$#i', $path, $matches) ){ // Si mauvais format + $this->error = new Error(Err::WrongPathRepo); + return false; + } + + // On recupere les donnes de la regex + $repository = $matches[1]; + $method = $matches[2]; + + /* [2] Verification de l'existence du repo (conf) + =========================================================*/ + if( !array_key_exists($repository, $this->repositories) ){ // Si le repo n'est pas specifie dans la conf + $this->error = new Error(Err::UnknownRepo, $repository); + return false; // On retourne FALSE, si erreur + } + + /* [3] Verification de l'existence de la methode (conf) + =========================================================*/ + if( array_search($method, $this->repositories[$repository]) === false ){ // Si la methode n'est pas specifie dans la conf + $this->error = new Error(Err::UnknownMethod, $method); + return false; // On retourne FALSE, si erreur + } + + + + /* [4] Enregistrement du chemin et renvoi de SUCCESS + =========================================================*/ + $this->path = [ + 'repo' => $repository, + 'method' => $method + ]; + + return true; + } + + + + + + /* RENVOI LE CHEMIN D'AMORCAGE DE LA METHODE + * + * @return path Retourne le chemin d'amorcage de la requete + * + */ + private function getFunctionCaller(){ + return [ '\\database\\repo\\'.$this->path['repo'], $this->path['method'] ]; + } + + + } + +?> diff --git a/src/modules/error/1/core/Error.php b/src/modules/error/1.0/core/Error.php similarity index 100% rename from src/modules/error/1/core/Error.php rename to src/modules/error/1.0/core/Error.php diff --git a/src/modules/error/2.0/Err.php b/src/modules/error/2.0/Err.php new file mode 100644 index 0000000..bd7a56c --- /dev/null +++ b/src/modules/error/2.0/Err.php @@ -0,0 +1,109 @@ + diff --git a/src/modules/error/2.0/Error.php b/src/modules/error/2.0/Error.php new file mode 100644 index 0000000..70f9944 --- /dev/null +++ b/src/modules/error/2.0/Error.php @@ -0,0 +1,191 @@ + Const error + * @arg1 [OPT] Argument 1 + * @arg2 [OPT] Argument 2 + * @arg... [OPT] Argument ... + * + * @return instance Error instance + * + */ + public function __construct($const){ + call_user_func_array([$this, 'set'], func_get_args()); + } + + /* ERROR GETTER + * + * @return Err Error + * + */ + public function get(){ return $this->error; } + + /* ERROR SETTER + * + * @error Const error + * @arg1 [OPT] Argument 1 + * @arg2 [OPT] Argument 2 + * @arg... [OPT] Argument ... + * + * @return instance Error instance + * + */ + public function set($const){ + /* [1] On découpe les arguments + =========================================================*/ + /* (1) On récupère l'erreur */ + $this->error = !is_numeric($const) ? Err::UnknownError : $const; + + /* (2) On récupère les arguments */ + $this->arguments = array_slice(func_get_args(), 1); + } + + + /* EXPLICITE UN CODE D'ERREUR + * + * @return explicit Description explicite du code d'erreur + * + */ + public function explicit(){ + switch($this->error){ + case Err::Success: return $this->Success(); break; + case Err::ParsingFailed: return $this->ParsingFailed(); break; + case Err::UnreachableResource: return $this->UnreachableResource(); break; + case Err::UploadError: return $this->UploadError(); break; + case Err::FormatError: return $this->FormatError(); break; + case Err::TokenError: return $this->TokenError(); break; + case Err::PermissionError: return $this->PermissionError(); break; + case Err::DisabledModule: return $this->DisabledModule(); break; + case Err::MissingPath: return $this->MissingPath(); break; + case Err::WrongPathModule: return $this->WrongPathModule(); break; + case Err::UnknownModule: return $this->UnknownModule(); break; + case Err::UnknownMethod: return $this->UnknownMethod(); break; + case Err::UncallableModule: return $this->UncallableModule(); break; + case Err::UncallableMethod: return $this->UncallableMethod(); break; + case Err::UnknownHttpMethod: return $this->UnknownHttpMethod(); break; + case Err::ConfigError: return $this->ConfigError(); break; + case Err::MissingParam: return $this->MissingParam(); break; + case Err::WrongParam: return $this->WrongParam(); break; + case Err::ModuleError: return $this->ModuleError(); break; + case Err::PDOConnection: return $this->PDOConnection(); break; + case Err::WrongPathRepo: return $this->WrongPathRepo(); break; + case Err::UnknownRepo: return $this->UnknownRepo(); break; + case Err::RepoError: return $this->RepoError(); break; + case Err::UnknownTable: return $this->UnknownTable(); break; + case Err::NotAllowedSchema: return $this->NotAllowedSchema(); break; + case Err::NoMatchFound: return $this->NoMatchFound(); break; + case Err::UnknownTemplate: return $this->UnknownTemplate(); break; + case Err::UnknownAddress: return $this->UnknownAddress(); break; + case Err::UnknownError: return $this->UnknownError(); break; + + default: return $this->UnknownDebugError(); break; + } + } + + + private function Success(){ + return 'all right'; + }private function ParsingFailed(){ + if( count($this->arguments) > 0 ) + return $this->arguments[0].' parsing failed'; + else + return 'parsing failed'; + }private function UnreachableResource(){ + return 'unreachable resource'; + }private function UploadError(){ + return 'upload error'; + }private function FormatError(){ + return 'format error'; + }private function TokenError(){ + return 'bad or expired token'; + }private function PermissionError(){ + return 'permission error'; + }private function DisabledModule(){ + return 'disabled module'; + }private function MissingPath(){ + return 'missing path'; + }private function WrongPathModule(){ + return 'wrong module\'s path'; + }private function UnknownModule(){ + if( count($this->arguments) > 0 ) + return 'unknown module \''.$this->arguments[0].'\''; + else + return 'unknown module'; + }private function UnknownMethod(){ + if( count($this->arguments) > 0 ) + return 'unknown method \''.$this->arguments[0].'\''; + else + return 'unknown method'; + }private function UncallableModule(){ + if( count($this->arguments) > 0 ) + return 'uncallable module \''.$this->arguments[0].'\''; + else + return 'uncallable module'; + }private function UncallableMethod(){ + if( count($this->arguments) > 0 ) + return 'uncallable method \''.$this->arguments[0].'\''; + else + return 'uncallable method'; + }private function UnknownHttpMethod(){ + return 'unknown HTTP method'; + }private function ConfigError(){ + return 'configuration error'; + }private function MissingParam(){ + if( count($this->arguments) > 0 ) + return 'missing param \''.$this->arguments[0].'\''; + else + return 'missing param'; + }private function WrongParam(){ + if( count($this->arguments) > 0 ) + if( count($this->arguments) > 1 ) + return 'wrong param \''.$this->arguments[0].'\' expected to be of type \''.$this->arguments[1].'\''; + else + return 'wrong param \''.$this->arguments[0].'\''; + else + return 'wrong param'; + }private function ModuleError(){ + return 'module error'; + }private function PDOConnection(){ + return 'database error'; + }private function WrongPathRepo(){ + return 'wrong repository\'s path'; + }private function UnknownRepo(){ + return 'unknown repository'; + }private function RepoError(){ + return 'repository error'; + }private function UnknownTable(){ + return 'unknown table'; + }private function NotAllowedSchema(){ + return 'schema browsing not allowed'; + }private function NoMatchFound(){ + return 'no match found'; + }private function UnknownTemplate(){ + return 'unknown template'; + }private function UnknownAddress(){ + return 'unknown'; + }private function UnknownError(){ + return 'unknown error'; + }private function UnknownDebugError(){ + return 'unknown debug error'; + } + + + public function setHttpCode(){ + http_response_code( $this->error == Err::Success ? 200 : 417 ); + } + + } + + +?> diff --git a/src/modules/lightdb/1/core/lightdb.php b/src/modules/lightdb/1.0/core/lightdb.php similarity index 100% rename from src/modules/lightdb/1/core/lightdb.php rename to src/modules/lightdb/1.0/core/lightdb.php diff --git a/src/modules/orm/0.8/core/Rows.php b/src/modules/orm/0.8-1/core/Rows.php similarity index 100% rename from src/modules/orm/0.8/core/Rows.php rename to src/modules/orm/0.8-1/core/Rows.php diff --git a/src/modules/orm/0.8/core/SQLBuilder.php b/src/modules/orm/0.8-1/core/SQLBuilder.php similarity index 100% rename from src/modules/orm/0.8/core/SQLBuilder.php rename to src/modules/orm/0.8-1/core/SQLBuilder.php diff --git a/src/modules/orm/0.8/core/Table.php b/src/modules/orm/0.8-1/core/Table.php similarity index 100% rename from src/modules/orm/0.8/core/Table.php rename to src/modules/orm/0.8-1/core/Table.php diff --git a/src/modules/orm/0.8-2/core/Rows.php b/src/modules/orm/0.8-2/core/Rows.php new file mode 100644 index 0000000..8772ee0 --- /dev/null +++ b/src/modules/orm/0.8-2/core/Rows.php @@ -0,0 +1,1072 @@ +__'; + 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) + + /* Attributs */ + 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 + * + */ + 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 l'ordonnancement' */ + $this->orderby = []; + + /* (5) On initialise le caractère 'unique' du résultat */ + $this->unique = false; + + /* (6) 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; + } + + + + + + /* 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){ + + $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 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).';'; + + /* (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/src/modules/orm/0.8-2/core/SQLBuilder.php b/src/modules/orm/0.8-2/core/SQLBuilder.php new file mode 100644 index 0000000..7a92c11 --- /dev/null +++ b/src/modules/orm/0.8-2/core/SQLBuilder.php @@ -0,0 +1,428 @@ + Liste de champs : [table => field => [func, alias] ] + * + * @return sql Renvoie un tableau formatté + * + */ + public static function SELECT($sqlFields){ + return $sqlFields; + } + + + + + + + + /* CONSTRUIT LA REQUETE FORMATTEE "ORDER BY" AVEC UNE LISTE DE CHAMPS + * + * @tables Liste de champs : [table => fields] + * + * @return sql Renvoie un tableau formatté + * + */ + public static function ORDERBY($tables){ + return $tables; + } + + + + + + + + /* 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; + + + /* (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; + + /* (9) Clause ORDER BY + ---------------------------------------------------------*/ + case 'ORDERBY': + + // si aucun ORDER BY, on quitte + if( count($statements) == 0 ) + continue; + + $sql .= 'ORDER BY '; + + $c = 0; + foreach($statements as $table=>$fields) + foreach($fields as $field=>$order){ + + if( $c > 0 ) $sql .= ', '; + + $sql .= "$table.$field ". substr($order, 2, -2); + + $c++; + } + + $sql .= "\n"; + break; + } + + + } + + + + + + + + /* [2] On retourne le résultat + =========================================================*/ + return $sql; + } + + + + } + + +?> diff --git a/src/modules/orm/0.8-2/core/Table.php b/src/modules/orm/0.8-2/core/Table.php new file mode 100644 index 0000000..317a3a2 --- /dev/null +++ b/src/modules/orm/0.8-2/core/Table.php @@ -0,0 +1,194 @@ + 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 `user` ***/ + // ORM::Table('user'); + + + /**** USE CASE :: WHERE ****/ + // WHERE `username` = 'someUsername' + // ORM::Table('user')->whereUsername('someUsername'); + // EQUIVALENT TO + // ORM::Table('user')->whereUsername('someUsername', Rows::COND_EQUAL); + + // WHERE `id_user` < 100 + // ORM::Table('user')->whereIdUser(100, Rows::COND_INF); + + // WHERE `id_user` <= 100 + // ORM::Table('user')->whereIdUser(100, Rows::COND_INFEQ); + + // WHERE `id_user` > 10 + // ORM::Table('user')->whereIdUser(10, Rows::COND_SUP); + + // WHERE `id_user` >= 10 + // ORM::Table('user')->whereIdUser(10, Rows::COND_SUPEQ); + + // WHERE `id_user` in (1, 2, 3, 8) + // ORM::Table('user')->whereIdUser([1, 2, 3, 8], Rows::COND_IN); + + // WHERE `id_user` LIKE 'John %' + // ORM::Table('user')->whereIdUser('John %', Rows::COND_LIKE); + + + /*** USE CASE :: ORDER BY ****/ + // ORDER BY `a` ASC, `b` DESC + // Table::get('someTable') + // ->orderby('a', Rows::ORDER_ASC) + // ->orderby('b', Rows::ORDER_DESC); + // + // Note: `Rows::ORDER_ASC` is set by default if the given FLAG is invalid + + + /**** USE CASE :: SELECT ****/ + // SELECT id_user, username + // Table::get('user') + // ->select('id_user') + // ->select('username'); + + + /**** USE CASE :: AGGREGATION FUNCTIONS ****/ + // SELECT COUNT(`count`) + // Table::get('user')->select('count', Rows::SEL_COUNT) + + // SELECT SUM(distinct `count`) + // Table::get('user')->select('count', Rows::SEL_SUM, Rows::SEL_DISTINCT); + + // SELECT AVG(`count`) + // Table::get('user')->select('count', Rows::SEL_AVG); + + // SELECT MAX(`id_user`) + // Table::get('user')->select('id_user', Rows::SEL_MAX); + + // SELECT MIN(`id_user`) + // Table::get('user')->select('id_user', Rows::SEL_MIN); + + // SELECT GROUP_CONCAT(`count`) + // Table::get('user')->select('count', Rows::SEL_CONCAT); + + + + /**** USE CASE :: FETCH ****/ + // SELECT ... FROM ... WHERE ... ORDERBY ... LIMIT ... + // Table::get('user') + // ->select('id_user') + // ->fetch(); + + // SELECT UNIQUE ... FROM ... WHERE ... ORDERBY ... LIMIT ... + // Table::get('user') + // ->select('id_user') + // ->unique->fetch(); + + + /**** USE CASE :: TABLE JOIN ****/ + // WHERE `user`.`id_user` = `user_merge`.`id_user` + // Table::get('user_merge')->join( + // Table::get('user')->whereIdUser(1, Rows::COND_SUP) + // ); diff --git a/src/modules/router/1/core/Route.php b/src/modules/router/1.0/core/Route.php similarity index 100% rename from src/modules/router/1/core/Route.php rename to src/modules/router/1.0/core/Route.php diff --git a/src/modules/router/1/core/Router.php b/src/modules/router/1.0/core/Router.php similarity index 100% rename from src/modules/router/1/core/Router.php rename to src/modules/router/1.0/core/Router.php