Move to raspisms dir

This commit is contained in:
osaajani 2020-02-18 04:29:48 +01:00
parent 34a6f7de65
commit 40fccf133c
278 changed files with 109 additions and 2020 deletions

View file

@ -1,124 +0,0 @@
<?php
namespace descartes;
/**
* Cette classe sert de mère à tous les controlleurs destiné à la création d'API. Elle hérite de toutes les infos d'un controller standard, mais elle embarque en plus des mécanismes propres aux api REST, etc.
*/
class ApiController extends Controller
{
/**
* Cette fonction construit la classe, elle prend en paramètre obligatoire le type de méthode (PUT, GET, POST, etc) avec laquel on a appelée la page
*/
public function __construct()
{
//On va choisir le type à employer
$method = $_SERVER['REQUEST_METHOD'];
switch (mb_convert_case($method, MB_CASE_LOWER))
{
case 'delete' :
$this->method = 'DELETE';
break;
case 'patch' :
$this->method = 'PATCH';
break;
case 'post' :
$this->method = 'POST';
break;
case 'put' :
$this->method = 'PUT';
break;
default :
$this->method = 'GET';
}
}
/**
* Cette fonction permet d'effectuer n'importe quelle opération sur le header en retournant le controller en cours
* @param string $key : La clef du header
* @param string $value : La valeur à donner
* @return ApiController : On retourne l'API controlleur lui meme pour pouvoir chainer
*/
public function set_header ($key, $value)
{
header($key . ': ' . $value);
return $this;
}
/**
* Cette fonction permet de définir un code de retour HTTP
* @param int $code : Le numéro du code de retour HTTP
* @return ApiController : On retourne l'API controlleur lui meme pour pouvoir chainer
*/
public function set_http_code ($code)
{
http_response_code($code);
return $this;
}
/**
* Cette fonction permet de définir un code de retour automatiquement selon le contexte du controller (c'est à dire si on l'appel en POST, en GET, etc.)
* @param boolean $success : Si la requete est un succes ou non (par défaut à true)
* @return ApiController : On retourne l'API controlleur lui meme pour pouvoir chainer
*/
public function auto_http_code ($success = true)
{
$response_codes = array(
'GET' => array(
'success' => 200,
'fail' => 404,
),
'DELETE' => array(
'success' => 204,
'fail' => 400,
),
'PATCH' => array(
'success' => 204,
'fail' => 400,
),
'POST' => array(
'success' => 201,
'fail' => 400,
),
'PUT' => array(
'success' => 204,
'fail' => 400,
),
);
$key = $success ? 'success' : 'fail';
return $this->set_http_code($response_codes[$this->method][$key]);
}
/**
* Cette fonction permet de faire un retour sous forme de json
* @param array $datas : Les données à retourner sous forme de json
* @param boolean $secure : Défini si l'affichage doit être sécurisé contre les XSS, par défaut true
* @return ApiController : On retourne l'API controlleur lui meme pour pouvoir chainer
*/
public function json ($datas, $secure = true)
{
header('Content-Type: application/json');
if ($secure)
{
echo htmlspecialchars(json_encode($datas), ENT_NOQUOTES);
}
else
{
echo json_encode($datas);
}
return $this;
}
/**
* Cette fonction permet de fixer la location renvoyée en HTTP
* @param string $location : La location à renvoyer
* @return ApiController : On retourne l'API controlleur lui meme pour pouvoir chainer
*/
public function set_location ($location)
{
header('Location: ' . $location);
return $this;
}
}

View file

@ -1,243 +0,0 @@
<?php
namespace descartes;
/**
* Class to route console query
*/
class Console
{
/**
* Check if a command explicitly ask for help
* @param array $command : Command called
* @param boolean : True if need help, false else
*/
private static function is_asking_for_help (array $command) : bool
{
return (isset($command[1]) && $command[1] == '--help');
}
/**
* Search name of controller to call and verify if it exist
* @param array $command : Command called
* @return string | bool : False if controller didn't exist. Name of controller if find
*/
private static function extract_controller (array $command)
{
//If we need help, we remove help flag
if (self::is_asking_for_help($command))
{
unset($command[1]);
$command = array_values($command);
}
//If no controller found
if (!isset($command[1]))
{
return false;
}
$controller = $command[1];
$controller = str_replace('/', '\\', $controller);
$ends_with = mb_substr($controller, -4);
if ($ends_with === '.php')
{
$controller = mb_substr($controller, 0, mb_strlen($controller) - 4);
}
if (!class_exists($controller))
{
return false;
}
return $controller;
}
/**
* Search name of the method to call, an verify it exist and is available
* @param array $command : Command called
* @param string $controller : Name of controller of the method
* @return string | bool : False if method not found, not exist or not available, method name else
*/
private static function extract_method (array $command, string $controller)
{
//Remove help flag if needed
if (self::is_asking_for_help($command))
{
unset($command[1]);
$command = array_values($command);
}
//If no method passed
if (!isset($command[2]))
{
return false;
}
$method = $command[2];
if (!method_exists($controller, $method))
{
return false;
}
return $method;
}
/**
* Extract params from the command
* @param array $command : La commande à analyser
* @return mixed : Array of params (format name => value).
* @return array | bool : An array with parameters in order we want theme to be passed to method. False if a need parameter is missing
*/
private static function extract_params (array $command, string $controller, string $method)
{
//Remove invocation, controller and method from command
unset($command[0], $command[1], $command[2]);
$command = array_values($command);
$params = [];
foreach ($command as $param)
{
$param = explode('=', $param, 2);
$name = str_replace('--', '', $param[0]);
$value = $param[1];
$params[$name] = $value;
}
$reflection = new \ReflectionMethod($controller, $method);
$method_arguments = [];
foreach ($reflection->getParameters() as $parameter)
{
if (!array_key_exists($parameter->getName(), $params) && !$parameter->isDefaultValueAvailable())
{
return false;
}
if ($parameter->isDefaultValueAvailable())
{
$method_arguments[$parameter->getName()] = $parameter->getDefaultValue();
}
if (!array_key_exists($parameter->getName(), $params))
{
continue;
}
//On ajoute la variable dans le tableau des arguments de la méthode
$method_arguments[$parameter->getName()] = $params[$parameter->getName()];
}
return $method_arguments;
}
/**
* Generate help text
* @param array $command : Called command
* @param ?string $controller : Name of the controller we want help for, null if not given
* @param ?string $method : Name of the method we want help for, null if not giver
* @param boolean $missing_arguments : If there is required arguments missing, false by default
* @param string : Le texte d'aide à afficher
*/
private static function generate_help_text (array $command, ?string $controller = null, ?string $method = null, bool $missing_arguments = false) : string
{
$retour = '';
$retour .= "Help : \n";
//Si pas de controlleur, on sort l'aide par défaut
if (!$controller)
{
$retour .= "You havn't supplied a Controller to call. To see help of a Controller : " . $command[0] . " --help <name controller> <name method>\n";
return $retour;
}
if ($missing_arguments)
{
$retour .= "Some required arguments are missing. \n";
}
if (!$method)
{
$retour .= 'Help of Controller ' . $controller . "\n" .
"Methods : \n";
$reflection = new \ReflectionClass($controller);
$reflection_methods = $reflection->getMethods();
}
else
{
$reflection_methods = [new \ReflectionMethod($controller, $method)];
$retour .= 'Help of Controller ' . $controller . ' and method ' . $method . "\n";
}
foreach ($reflection_methods as $reflection_method)
{
$retour .= " " . $reflection_method->getName();
foreach ($reflection_method->getParameters() as $parameter)
{
$retour .= ' --' . $parameter->getName() . "=<value" . ($parameter->isDefaultValueAvailable() ? ' (by default, ' . gettype($parameter->getDefaultValue()) . ':' . str_replace(PHP_EOL, '', print_r($parameter->getDefaultValue(), true)) . ')': '') . ">";
}
$retour .= "\n";
}
return $retour;
}
/**
* This method call controller and method from command
* @param array $command : Command to call
* @param $args : Arguments to pass to Controller constructor
* @return bool : False on error, true else
*/
public static function execute_command (array $command, ...$args)
{
$controller = self::extract_controller($command);
if (!$controller)
{
echo self::generate_help_text($command);
return true;
}
$method = self::extract_method($command, $controller);
if (!$method)
{
echo self::generate_help_text($command, $controller);
return true;
}
$params = self::extract_params($command, $controller, $method);
if ($params === false)
{
echo self::generate_help_text($command, $controller, $method, true);
return true;
}
$asking_for_help = self::is_asking_for_help($command);
if ($asking_for_help)
{
echo self::generate_help_text($command, $controller, $method);
return true;
}
$reflection = new \ReflectionClass($controller);
$reflection_method = $reflection->getMethod($method);
if (!$reflection_method->isStatic())
{
$controller = new $controller(...$args);
}
return call_user_func_array([$controller, $method], $params);
}
}

View file

@ -1,93 +0,0 @@
<?php
namespace descartes;
/**
* This class is the parent of all controllers
*/
class Controller
{
/**
* Render a template for displaying
* @param string $template : Template name without extension, possibly preceed with a directory name
* @param array|null $variables : Array of variables to transfer to the template, format name => value
* @param string $dir_template_path : Template directory path, if not defined we use PWD_TEMPLATES constant
* @param string $extension : The extension of the template file, if not defined we use ".php"
* @return true
*/
protected function render (string $template, ?array $variables = array(), string $dir_template_path = PWD_TEMPLATES, string $template_extension = '.php') : bool
{
foreach ($variables as $clef => $value)
{
$$clef = $value;
}
$template_path = $dir_template_path . '/' . $template . $template_extension;
if (!is_readable($template_path))
{
throw new DescartesTemplateNotReadableException('Template ' . $template_path . ' is not readable.');
}
require $template_path;
return true;
}
/**
* Print text after escaping it for security
*
* @param string $text : Texte to print
* @param bool $nl2br : If true, replace "\n" by "<br/>", false by default
* @param bool $escape_quotes : If true, turn quotes and double quotes into html entities, true by default
* @param bool $echo : If true, print text, else return it without printing
*
* @return string : modified text
*/
public static function s (string $text, bool $nl2br = false, bool $escape_quotes = true, bool $echo = true) : string
{
$text = $escape_quotes ? htmlspecialchars($text, ENT_QUOTES) : htmlspecialchars($text, ENT_NOQUOTES);
$text = $nl2br ? nl2br($text) : $text;
if ($echo)
{
echo $text;
}
return $text;
}
/**
* Verify if we received a valid csrf. Always return true if not in http context
* @param string|null $csrf : CSRF token to verify, if ommited, we use $_GET['csrf'], then $_POST['csrf']
* @param string|null $reference_csrf : CSRF token generated by system, if ommited we use $_SESSION['csrf']
* @return bool : True if valid, false else
*/
public static function verify_csrf (?string $csrf = null, ?string $reference_csrf = null) : bool
{
$csrf = $csrf ?? $_POST['csrf'] ?? $_GET['csrf'];
$reference_csrf = $reference_csrf ?? $_SESSION['csrf'];
if (http_response_code() === FALSE)
{
return true;
}
if ($csrf != $_SESSION['csrf'])
{
return false;
}
return true;
}
/**
* Redirect to an url
* @param string $url : URL to redirect to
* @param int $http_code : Http code to send (default 302)
* @return null
*/
public static function redirect (string $url, int $http_code = 302)
{
header('Location: ' . $url, true, $http_code);
return null;
}
}

View file

@ -1,9 +0,0 @@
<?php
namespace descartes;
/**
* Cette classe sert de mère à tous les controlleurs internes
*/
class InternalController extends Controller
{
}

View file

@ -1,470 +0,0 @@
<?php
namespace descartes;
/**
* Cette classe sert de mère à tous les modèles, elle permet de gérer l'ensemble des fonction necessaires aux requetes en base de données
* @param $pdo : Une instance de \PDO
*/
class Model
{
//Les variables internes au Model
var $pdo;
//Les constantes des différents types de retours possibles
const NO = 0; //Pas de retour
const FETCH = 1; //Retour de type fetch
const FETCHALL = 2; //Retour de type fetchall
const ROWCOUNT = 3; //Retour de type rowCount()
/**
* Model constructor
* @param \PDO $pdo : \PDO connect to use
*/
public function __construct(\PDO $pdo)
{
$this->pdo = $pdo;
}
/**
* Cette fonction permet créer une connexion à une base SQL via \PDO
* @param string $host : L'host à contacter
* @param string $dbname : Le nom de la base à contacter
* @param string $user : L'utilisateur à utiliser
* @param string $password : Le mot de passe à employer
* @return mixed : Un objet \PDO ou false en cas d'erreur
*/
public static function _connect ($host, $dbname, $user, $password, ?string $charset = 'UTF8', ?array $options = null)
{
$options = $options ?? [
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION
];
// On se connecte à MySQL
$pdo = new \PDO('mysql:host=' . $host . ';dbname=' . $dbname . ';charset=' . $charset , $user, $password, $options);
if ($pdo === false)
{
throw new DescartesExceptionDatabaseConnection('Cannot connect to database ' . $dbname . '.');
}
return $pdo;
}
/**
* Run a query and return result
* @param string $query : Query to run
* @param array $datas : Datas to pass to query
* @param const $return_type : Type of return, by default all results, see Model constants
* @param const $fetch_mode : Format of result from db, by default array, FETCH_ASSOC
* @param boolean $debug : If we must return debug info instead of data, by default false
* @return mixed : Result of query, depend of $return_type | null | array | object | int
*/
protected function _run_query (string $query, array $datas = array(), int $return_type = self::FETCHALL, int $fetch_mode = \PDO::FETCH_ASSOC, bool $debug = false)
{
try
{
$query = $this->pdo->prepare($query);
$query->setFetchMode($return_type);
$query->execute($datas);
if ($debug)
{
return $query->errorInfo();
}
switch ($return_type)
{
case self::NO :
$return = NULL;
break;
case self::FETCH :
$return = $query->fetch();
break;
case self::FETCHALL :
$return = $query->fetchAll();
break;
case self::ROWCOUNT :
$return = $query->rowCount();
break;
default :
$return = $query->fetchAll();
}
return $return;
}
catch (\PDOException $e)
{
$error = $query->errorInfo();
//Get query string and params
ob_start();
$query->debugDumpParams();
$debug_string = ob_get_clean();
throw new \descartes\exceptions\DescartesExceptionSqlError(
'SQL Error : ' . "\n" .
'SQLSTATE : ' . $error[0] . "\n" .
'Driver Error Code : ' . $error[1] . "\n" .
'Driver Error Message : ' . $error[2] . "\n" .
'SQL QUERY DEBUG :' . "\n" .
'-----------------' . "\n" .
$debug_string . "\n" .
'-----------------' . "\n"
);
}
}
/**
* Return last inserted id
* return int : Last inserted id
*/
protected function _last_id() : int
{
return $this->pdo->lastInsertId();
}
/*
Fonctions d'execution des requetes ou de génération
*/
/**
* Generate IN query params and values
* @param string $values : Values to generate in array from
* @return array : Array ['QUERY' => string 'IN(...)', 'PARAMS' => [parameters to pass to execute]]
*/
protected function _generate_in_from_array ($values)
{
$return = array(
'QUERY' => '',
'PARAMS' => array(),
);
$flags = array();
$values = count($values) ? $values : array();
foreach ($values as $key => $value)
{
$key = preg_replace('#[^a-zA-Z0-9_]#', '', $key);
$return['PARAMS']['in_value_' . $key] = $value;
$flags[] = ':in_value_' . $key;
}
$return['QUERY'] .= ' IN(' . implode(', ', $flags) . ')';
return $return;
}
/**
* Evaluate a condition to generate query string and params array for
* @param string $fieldname : fieldname possibly preceed by '<, >, <=, >=, ! or ='
* @param $value : value of field
* @return array : array with QUERY and PARAMS
*/
protected function _evaluate_condition (string $fieldname, $value) : array
{
$first_char = mb_substr($fieldname, 0, 1);
$second_char = mb_substr($fieldname, 1, 1);
switch(true)
{
//Important de traiter <= & >= avant < & >
case ('<=' == $first_char . $second_char) :
$true_fieldname = mb_substr($fieldname, 2);
$operator = '<=';
break;
case ('>=' == $first_char . $second_char) :
$true_fieldname = mb_substr($fieldname, 2);
$operator = '>=';
break;
case ('!=' == $first_char . $second_char) :
$true_fieldname = mb_substr($fieldname, 2);
$operator = '!=';
break;
case ('!' == $first_char) :
$true_fieldname = mb_substr($fieldname, 1);
$operator = '!=';
break;
case ('<' == $first_char) :
$true_fieldname = mb_substr($fieldname, 1);
$operator = '<';
break;
case ('>' == $first_char) :
$true_fieldname = mb_substr($fieldname, 1);
$operator = '>';
break;
case ('=' == $first_char) :
$true_fieldname = mb_substr($fieldname, 1);
$operator = '=';
break;
default :
$true_fieldname = $fieldname;
$operator = '=';
}
//Protect against injection in fieldname
$true_fieldname = preg_replace('#[^a-zA-Z0-9_]#', '', $true_fieldname);
$query = '`' . $true_fieldname . '` ' . $operator . ' :where_' . $true_fieldname;
$param = ['where_' . $true_fieldname => $value];
return ['QUERY' => $query, 'PARAM' => $param];
}
/**
* Get from table, posssibly with some conditions
* @param string $table : table name
* @param array $conditions : Where conditions to use, format 'fieldname' => 'value', fieldname can be preceed by operator '<, >, <=, >=, ! or = (by default)' to adapt comparaison operator
* @param ?string $order_by : name of column to order result by, null by default
* @param string $desc : L'ordre de tri (asc ou desc). Si non défini, ordre par défaut (ASC)
* @param string $limit : Le nombre maximum de résultats à récupérer (par défaut pas le limite)
* @param string $offset : Le nombre de résultats à ignorer (par défaut pas de résultats ignorés)
* @return mixed : False en cas d'erreur, sinon les lignes retournées
*/
protected function _select (string $table, array $conditions = [], ?string $order_by = null, bool $desc = false, ?int $limit = null, ?int $offset = null)
{
try
{
$wheres = array();
$params = array();
foreach ($conditions as $label => $value)
{
$condition = $this->_evaluate_condition($label, $value);
$wheres[] = $condition['QUERY'];
$params = array_merge($params, $condition['PARAM']);
}
$query = "SELECT * FROM `" . $table . "` WHERE 1 " . (count($wheres) ? 'AND ' : '') . implode(' AND ', $wheres);
if ($order_by !== null)
{
$query .= ' ORDER BY ' . $order_by;
if ($desc)
{
$query .= ' DESC';
}
}
if ($limit !== null)
{
$query .= ' LIMIT :limit';
if ($offset !== null)
{
$query .= ' OFFSET :offset';
}
}
$query = $this->pdo->prepare($query);
if ($limit !== null)
{
$query->bindParam(':limit', $limit, \PDO::PARAM_INT);
if ($offset !== null)
{
$query->bindParam(':offset', $offset, \PDO::PARAM_INT);
}
}
foreach ($params as $label => &$param)
{
$query->bindParam(':' . $label, $param);
}
$query->setFetchMode(\PDO::FETCH_ASSOC);
$query->execute();
return $query->fetchAll();
}
catch (\PDOException $e)
{
$error = $query->errorInfo();
//Get query string and params
ob_start();
$query->debugDumpParams();
$debug_string = ob_get_clean();
throw new \descartes\exceptions\DescartesExceptionSqlError(
'SQL Error : ' . "\n" .
'SQLSTATE : ' . $error[0] . "\n" .
'Driver Error Code : ' . $error[1] . "\n" .
'Driver Error Message : ' . $error[2] . "\n" .
'SQL QUERY DEBUG :' . "\n" .
'-----------------' . "\n" .
$debug_string . "\n" .
'-----------------' . "\n"
);
}
}
/**
* Get one line from table, posssibly with some conditions
* see get
*/
protected function _select_one (string $table, array $conditions = [], ?string $order_by = null, bool $desc = false, ?int $limit = null, ?int $offset = null)
{
$result = $this->_select($table, $conditions, $order_by, $desc, $limit, $offset);
if (empty($result[0]))
{
return $result;
}
return $result[0];
}
/**
* Count line from table, posssibly with some conditions
* @param array $conditions : conditions of query Les conditions pour la mise à jour sous la forme "label" => "valeur". Un operateur '<, >, <=, >=, !' peux précder le label pour modifier l'opérateur par défaut (=)
*/
protected function _count (string $table, array $conditions = []) : int
{
try
{
$wheres = array();
$params = array();
foreach ($conditions as $label => $value)
{
$condition = $this->_evaluate_condition($label, $value);
$wheres[] = $condition['QUERY'];
$params = array_merge($params, $condition['PARAM']);
}
$query = "SELECT COUNT(*) as `count` FROM `" . $table . "` WHERE 1 " . (count($wheres) ? 'AND ' : '') . implode(' AND ', $wheres);
$query = $this->pdo->prepare($query);
foreach ($params as $label => &$param)
{
$query->bindParam(':' . $label, $param);
}
$query->setFetchMode(\PDO::FETCH_ASSOC);
$query->execute();
return $query->fetch()['count'];
}
catch (\PDOException $e)
{
$error = $query->errorInfo();
//Get query string and params
ob_start();
$query->debugDumpParams();
$debug_string = ob_get_clean();
throw new \descartes\exceptions\DescartesExceptionSqlError(
'SQL Error : ' . "\n" .
'SQLSTATE : ' . $error[0] . "\n" .
'Driver Error Code : ' . $error[1] . "\n" .
'Driver Error Message : ' . $error[2] . "\n" .
'SQL QUERY DEBUG :' . "\n" .
'-----------------' . "\n" .
$debug_string . "\n" .
'-----------------' . "\n"
);
}
}
/**
* Update data from table with some conditions
* @param string $table : table name
* @param array $datas : new data to set
* @param array $conditions : conditions of update, Les conditions pour la mise à jour sous la forme "label" => "valeur". Un operateur '<, >, <=, >=, !' peux précder le label pour modifier l'opérateur par défaut (=)
* @param array $conditions : conditions to use, format 'fieldname' => 'value', fieldname can be preceed by operator '<, >, <=, >=, ! or = (by default)' to adapt comparaison operator
* @return mixed : Number of line modified
*/
protected function _update (string $table, array $datas, array $conditions = array()) : int
{
$params = array();
$sets = array();
foreach ($datas as $label => $value)
{
$label = preg_replace('#[^a-zA-Z0-9_]#', '', $label);
$params['set_' . $label] = $value;
$sets[] = '`' . $label . '` = :set_' . $label . ' ';
}
$wheres = array();
foreach ($conditions as $label => $value)
{
$condition = $this->_evaluate_condition($label, $value);
$wheres[] = $condition['QUERY'];
$params = array_merge($params, $condition['PARAM']);
}
$query = "UPDATE `" . $table . "` SET " . implode(', ', $sets) . " WHERE 1 " . (count($wheres) ? " AND " : "") . implode(' AND ', $wheres);
return $this->_run_query($query, $params, self::ROWCOUNT);
}
/**
* Delete from table according to certain conditions
* @param string $table : Table name
* @param array $conditions : conditions to use, format 'fieldname' => 'value', fieldname can be preceed by operator '<, >, <=, >=, ! or = (by default)' to adapt comparaison operator
* @return mixed : Number of line deleted
*/
protected function _delete (string $table, array $conditions = []) : int
{
//On gère les conditions
$wheres = array();
$params = array();
foreach ($conditions as $label => $value)
{
$condition = $this->_evaluate_condition($label, $value);
$wheres[] = $condition['QUERY'];
$params = array_merge($params, $condition['PARAM']);
}
$query = "DELETE FROM `" . $table . "` WHERE 1 " . (count($wheres) ? " AND " : "") . implode(' AND ', $wheres);
return $this->_run_query($query, $params, self::ROWCOUNT);
}
/**
* Insert new line into table
* @param string $table : table name
* @param array $datas : new datas
* @return mixed : null on error, number of line inserted else
*/
protected function _insert (string $table, array $datas) : ?int
{
$params = array();
$field_names = array();
foreach ($datas as $field_name => $value)
{
//Protect against injection in fieldname
$field_name = preg_replace('#[^a-zA-Z0-9_]#', '', $field_name);
$params[$field_name] = $value;
$field_names[] = $field_name;
}
$query = "INSERT INTO `" . $table . "` (`" . implode('`, `', $field_names) . "`) VALUES(:" . implode(', :', $field_names) . ")";
//On retourne le nombre de lignes insérées
return $this->_run_query($query, $params, self::ROWCOUNT);
}
}

View file

@ -1,340 +0,0 @@
<?php
namespace descartes;
/**
* Cette classe gère l'appel des ressources
*/
class Router
{
/**
* Generate an url for a page of the app
* @param string $controller : Name of controller we want url for
* @param string $method : Name of method we want url for
* @param array $params : Parameters we want to transmit to controller method
* @param array $get_params : Get parameters we want to add to url
* @return string : the generated url
*/
public static function url (string $controller, string $method, array $params = [], array $get_params = []) : string
{
$url = HTTP_PWD;
if (!array_key_exists($controller, ROUTES))
{
throw new \descartes\exceptions\DescartesExceptionRouterUrlGenerationError('Try to generate url for controller ' . $controller . ' that did not exist.');
}
if (!array_key_exists($method, ROUTES[$controller]))
{
throw new \descartes\exceptions\DescartesExceptionRouterUrlGenerationError('Try to generate url for method ' . $controller . '::' . $method . ' that did not exist.');
}
$get_params = http_build_query($get_params);
$routes = ROUTES[$controller][$method];
if (!is_array($routes))
{
$routes = [$routes];
}
foreach ($routes as $route)
{
foreach ($params as $name => $value)
{
$find_flag = mb_strpos($route, '{' . $name . '}');
if ($find_flag === false)
{
continue 2;
}
$route = str_replace('{' . $name . '}', $value, $route);
}
$remain_flag = mb_strpos($route, '{');
if ($remain_flag)
{
continue;
}
return $url . $route . ($get_params ? '?' . $get_params : '');
}
throw new \descartes\exceptions\DescartesExceptionRouterUrlGenerationError('Cannot find any route for ' . $controller . '::' . $method . ' with parameters ' . print_r($params, true));
}
/**
* Clean url to remove anything before app root, and ancre, and parameters
* @param string $url : url to clean
* @return string : cleaned url
*/
protected static function clean_url (string $url)
{
$to_remove = parse_url(HTTP_PWD, PHP_URL_PATH);
$url = mb_strcut($url, mb_strlen($to_remove));
$url = parse_url($url, PHP_URL_PATH);
return $url;
}
/**
* Find controller and method to call for an url
* @param array $routes : Routes of the app
* @param string $url : url to find route for
* @return array|bool : An array, ['controller' => name of controller to call, 'method' => name of method to call, 'route' => 'route matching the url', 'route_regex' => 'the regex to extract data from url'], false on error
*/
protected static function map_url (array $routes, string $url)
{
foreach ($routes as $controller => $controller_routes)
{
foreach ($controller_routes as $method => $method_routes)
{
if (!is_array($method_routes))
{
$method_routes = [$method_routes];
}
foreach ($method_routes as $route)
{
$route_regex = preg_replace('#\\\{(.+)\\\}#iU', '([^/]+)', preg_quote($route, '#'));
$route_regex = preg_replace('#/$#', '/?', $route_regex);
$match = preg_match('#^' . $route_regex . '$#U', $url);
if (!$match)
{
continue;
}
return [
'controller' => $controller,
'method' => $method,
'route' => $route,
'route_regex' => $route_regex,
];
}
}
}
return false;
}
/**
* Get data from url and map it with route flags
* @param string $url : A clean url to extract data from (see clean_url)
* @param string $route : Route we must extract flag from
* @param string $route_regex : Regex to extract data from url
* @return array : An array with flagname and values, flag => value
*/
protected static function map_params_from_url (string $url, string $route, string $route_regex)
{
$flags = [];
preg_match_all('#\\\{(.+)\\\}#iU', preg_quote($route, '#'), $flags);
$flags = $flags[1];
$values = [];
if (!preg_match('#^' . $route_regex . '$#U', $url, $values))
{
return false;
}
unset($values[0]);
$values = array_map('rawurldecode', $values);
//On retourne les valeurs associées aux flags
return array_combine($flags, $values);
}
/**
* Compute a controller name to return is real path with namespace
* @param string $controller : The controller name
* @return string|bool : False if controller does not exist, controller name with namespace else
*/
protected static function compute_controller (string $controller)
{
$controller = str_replace('/', '\\', PWD_CONTROLLER . '/publics/') . $controller;
$controller = mb_strcut($controller, mb_strlen(PWD));
if (!class_exists($controller))
{
return false;
}
return $controller;
}
/**
* Compute a method to find his real name and check its available
* @param string $controller : Full namespace of controller to call
* @param string $method : The method to call
* @return string | bool : False if method unavaible, its realname else
*/
protected static function compute_method (string $controller, string $method)
{
if (is_subclass_of($controller, 'descartes\ApiController'))
{
//On va choisir le type à employer
$http_method = $_SERVER['REQUEST_METHOD'];
switch (mb_convert_case($http_method, MB_CASE_LOWER))
{
case 'delete' :
$prefix_method = 'delete_';
break;
case 'patch' :
$prefix_method = 'patch_';
break;
case 'post' :
$prefix_method = 'post_';
break;
case 'put' :
$prefix_method = 'put_';
break;
default :
$prefix_method = 'get_';
}
//If we dont match prefix with request method, return error
if (mb_substr($method, 0, mb_strlen($prefix_method)) != $prefix_method)
{
return false;
}
}
if (!method_exists($controller, $method))
{
return false;
}
if (!is_callable($controller, $method))
{
return false;
}
return $method;
}
/**
* Type, order and check params we must pass to method
* @param string $controller : Full namespace of controller
* @param string $method : Name of method
* @param array $params : Parameters to compute, format name => value
* @return array : Array ['success' => false, 'message' => error message] on error, and ['success' => true, 'method_arguments' => array of method arguments key=>val] on success
*/
protected static function compute_params (string $controller, string $method, array $params) : array
{
$reflection = new \ReflectionMethod($controller, $method);
$method_arguments = [];
foreach ($reflection->getParameters() as $parameter)
{
if (!array_key_exists($parameter->getName(), $params) && !$parameter->isDefaultValueAvailable())
{
return ['success' => false, 'message' => 'Try to call ' . $controller . '::' . $method . ' but ' . $parameter->getName() . ' is missing.'];
}
if ($parameter->isDefaultValueAvailable())
{
$method_arguments[$parameter->getName()] = $parameter->getDefaultValue();
}
if (!array_key_exists($parameter->getName(), $params))
{
continue;
}
$type = $parameter->getType();
$type = $type ?? false;
if ($type)
{
switch ($type)
{
case 'bool' :
$params[$parameter->getName()] = (bool) $params[$parameter->getName()];
break;
case 'int' :
$params[$parameter->getName()] = (int) $params[$parameter->getName()];
break;
case 'float' :
$params[$parameter->getName()] = (float) $params[$parameter->getName()];
break;
case 'string' :
$params[$parameter->getName()] = (string) $params[$parameter->getName()];
break;
default :
return ['success' => false, 'message' => 'Method ' . $controller . '::' . $method . ' use an invalid type for param ' . $parameter->getName() . '. Only bool, int float and string are supported.'];
break;
}
}
$method_arguments[$parameter->getName()] = $params[$parameter->getName()];
}
return ['success' => true, 'method_arguments' => $method_arguments];
}
/**
* Throw a 404 exception
*/
public static function error_404 () : void
{
throw new \descartes\exceptions\DescartesException404();
}
/**
* Route a query
* @param array $routes : Routes of app
* @param string $url : Url call
* @param mixed $args : Args we want to pass to Controller constructor
*/
public static function route (array $routes, string $url, ...$args) : void
{
$url = static::clean_url($url);
$computed_url = static::map_url($routes, $url);
if (!$computed_url)
{
static::error_404();
}
$params = static::map_params_from_url($url, $computed_url['route'], $computed_url['route_regex']);
$controller = static::compute_controller($computed_url['controller']);
if (!$controller)
{
throw new \descartes\exceptions\DescartesExceptionRouterInvocationError('Try to call controller ' . $computed_url['controller'] . ' that did not exists.');
}
$method = static::compute_method($controller, $computed_url['method']);
if (!$method)
{
throw new \descartes\exceptions\DescartesExceptionRouterInvocationError('Try to call the method ' . $computed_url['method'] . ' that did not exists from controller ' . $controller . '.');
}
$compute_params_result = static::compute_params($controller, $method, $params);
if (!$compute_params_result['success'])
{
throw new \descartes\exceptions\DescartesExceptionRouterInvocationError($compute_params_result['message']);
}
$method_arguments = $compute_params_result['method_arguments'];
$controller = new $controller(...$args);
call_user_func_array([$controller, $method], $method_arguments);
}
}

View file

@ -1,24 +0,0 @@
<?php
/**
* Cette fonction permet le chargement automatique des classes. Cela permet de ne pas avoir à instancier chaque classe.
* Dans l'ordre, on essaye de charger les classes suivantes :
* - Classes du framework (app)
* - Classes personnalisées (toutes depuis la racine du framework)
*/
/**
* Cette fonction inclus le fichier de class
* @param string $class = Nom de la classe a aller chercher
*/
function autoloader($class)
{
$class = str_replace('\\', '/', $class); #Gestion des namespaces
if (file_exists(PWD . '/' . $class . '.php'))
{
require_once(PWD . '/' . $class . '.php');
}
}
spl_autoload_register('autoloader');

View file

@ -1,61 +0,0 @@
<?php
namespace descartes;
/*
* Define Descartes env
*/
$http_dir_path = '/raspisms'; //Path we need to put after servername in url to access app
$http_protocol = (isset($_SERVER['HTTPS']) ? 'https' : 'http') . '://';
$http_server_name = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost';
$http_server_port = isset($_SERVER['SERVER_PORT']) ? ($_SERVER['SERVER_PORT'] == 80) ? '' : ':' . $_SERVER['SERVER_PORT'] : '';
$https = $_SERVER['HTTPS'] ?? false;
if ( !isset($_SERVER['SERVER_PORT']) || ($_SERVER['SERVER_PORT'] == 80 && !$https) || ($_SERVER['SERVER_PORT'] === 443 && $https) )
{
$http_server_port = '';
}
else
{
$http_server_port = ':' . $_SERVER['SERVER_PORT'];
}
$pwd = substr(__DIR__, 0, strrpos(__DIR__, '/'));
$http_pwd = $http_protocol . $http_server_name . $http_server_port . $http_dir_path;
$env = [
//Global http and file path
'HTTP_DIR_PATH' => $http_dir_path,
'HTTP_PROTOCOL' => $http_protocol,
'HTTP_SERVER_NAME' => $http_server_name,
'HTTP_SERVER_PORT' => $http_server_port,
'PWD' => $pwd,
'HTTP_PWD' => $http_pwd,
//path of back resources
'PWD_CONTROLLER' => $pwd . '/controllers', //Controllers dir
'PWD_MODEL' => $pwd . '/models', //Models dir
'PWD_TEMPLATES' => $pwd . '/templates', //Templates dir
//path of front resources
'PWD_ASSETS' => $pwd . '/assets', //Assets dir
'HTTP_PWD_ASSETS' => $http_pwd . '/assets', //HTTP path of asset dir
//images
'PWD_IMG' => $pwd . '/assets' . '/img',
'HTTP_PWD_IMG' => $http_pwd . '/assets' . '/img',
//css
'PWD_CSS' => $pwd . '/assets' . '/css',
'HTTP_PWD_CSS' => $http_pwd . '/assets' . '/css',
//javascript
'PWD_JS' => $pwd . '/assets' . '/js',
'HTTP_PWD_JS' => $http_pwd . '/assets' . '/js',
//fonts
'PWD_FONT' => $pwd . '/assets' . '/font',
'HTTP_PWD_FONT' => $http_pwd . '/assets' . '/font',
];

View file

@ -1,5 +0,0 @@
<?php
namespace descartes\exceptions;
class DescartesException404 extends \Exception {};

View file

@ -1,5 +0,0 @@
<?php
namespace descartes\exceptions;
class DescartesExceptionConsoleInvocationError extends \Exception {};

View file

@ -1,5 +0,0 @@
<?php
namespace descartes\exceptions;
class DescartesExceptionRouterInvocationError extends \Exception {};

View file

@ -1,5 +0,0 @@
<?php
namespace descartes\exceptions;
class DescartesExceptionRouterUrlGenerationError extends \Exception {};

View file

@ -1,5 +0,0 @@
<?php
namespace descartes\exceptions;
class DescartesExceptionSqlError extends \Exception {};

View file

@ -1,5 +0,0 @@
<?php
namespace descartes\exceptions;
class DescartesExceptionTemplateNotReadable extends \Exception {};

View file

@ -1,62 +0,0 @@
<?php
namespace descartes;
function define_array ($array)
{
foreach ($array as $key => $value)
{
if (defined(mb_strtoupper($key)))
{
continue;
}
define(mb_strtoupper($key), $value);
}
}
function load_env ()
{
### DESCARTES ENV ###
$environment = [];
$env = [];
require_once(__DIR__ . '/env.php');
$environment = array_merge($environment, $env);
//Load descartes override env
if (file_exists(__DIR__ . '/../env.descartes.php'))
{
require_once(__DIR__ . '/../env.descartes.php');
$environment = array_merge($environment, $env);
}
//Define all Descartes constants
define_array($environment);
### GLOBAL ENV ###
$environment = [];
$env = [];
if (file_exists(__DIR__ . '/../env.php'))
{
require_once(__DIR__ . '/../env.php');
$environment = array_merge($environment, $env);
}
define_array($environment);
### SPECIFIC ENV ###
$environment = [];
$env = [];
if (defined('ENV') && file_exists(__DIR__ . '/../env.' . ENV . '.php'))
{
require_once(__DIR__ . '/../env.' . ENV . '.php');
$environment = array_merge($environment, $env);
}
define_array($environment);
}
load_env();

View file

@ -1,16 +0,0 @@
<?php
###############
# ENVIRONMENT #
###############
require_once(__DIR__ . '/load-environment.php');
############
# AUTOLOAD #
############
require_once(PWD . '/descartes/autoload.php');
require_once(PWD . '/vendor/autoload.php');
###########
# ROUTING #
###########
require_once(PWD . '/routes.php'); //Include routes