fixed api:2.0/2.2 + new version router:2.0 self-managed
This commit is contained in:
parent
b98a8419d4
commit
6cd977ef90
|
@ -55,7 +55,8 @@
|
|||
"1.0": []
|
||||
},
|
||||
"router": {
|
||||
"1.0": []
|
||||
"1.0": [],
|
||||
"2.0": []
|
||||
}
|
||||
},
|
||||
"installed": {
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
|
||||
"methods": [ "GET", "POST", "PUT", "DELETE" ],
|
||||
|
||||
|
||||
"routes": {
|
||||
|
||||
"/{page}/": {
|
||||
"methods": ["GET"],
|
||||
"controller": "page:load",
|
||||
"arguments": {
|
||||
"page": "[a-z]+"
|
||||
}
|
||||
},
|
||||
|
||||
"/api/{module}/{method}{url_arguments}": {
|
||||
"controller": "api:call",
|
||||
"arguments": {
|
||||
"module": "[a-zA-Z_]+",
|
||||
"method": "[a-zA-Z_]+",
|
||||
"url_arguments": "(\\/[\\w:-]+)*\\/?"
|
||||
}
|
||||
},
|
||||
|
||||
"/{any}": {
|
||||
"methods": ["GET"],
|
||||
"controller": "redirect:homepage",
|
||||
"arguments": {
|
||||
"any": ".+"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
RewriteEngine on
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
|
||||
RewriteRule ^(.*)$ index.php?url=/$1 [QSA,L]
|
||||
|
|
|
@ -370,7 +370,8 @@
|
|||
$method = $this->modules[$this->path['module']][$this->path['method']];
|
||||
|
||||
// Si aucune permission n'est definie
|
||||
if( !isset($method['permissions']) ) return true;
|
||||
if( !isset($method['permissions']) || !is_array($method['permissions']) || count($method['permissions']) < 1 )
|
||||
return true;
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -2,59 +2,21 @@
|
|||
|
||||
namespace api\core;
|
||||
|
||||
use \database\core\Repo;
|
||||
use \error\core\Error;
|
||||
use \error\core\Err;
|
||||
use \error\core\Error;
|
||||
|
||||
class Authentification
|
||||
{
|
||||
class Authentification{
|
||||
|
||||
private static $instance;
|
||||
|
||||
private $userType;
|
||||
private $userId;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$token = !empty($_SERVER['PHP_AUTH_DIGEST']) ? $_SERVER['PHP_AUTH_DIGEST'] : false;
|
||||
|
||||
$user = new Repo("AuthentificationRepo/identifyUserByToken", [$token]);
|
||||
$user = $user->answer();
|
||||
|
||||
if( $user != false ){
|
||||
$this->userType = $user["Type"];
|
||||
$this->userId = $user["Id"];
|
||||
}
|
||||
|
||||
new Repo("AuthentificationRepo/updateToken",[$token]);
|
||||
|
||||
new Repo("AuthentificationRepo/purgeOldTokens",[]);
|
||||
|
||||
self::$instance = $this;
|
||||
/* VERIFICATION DES ACCES EN FONCTION DE PERMISSIONS ATTENDUES
|
||||
*
|
||||
* @expected<array> Liste des permissions attendues
|
||||
*
|
||||
* @return status<Boolean> Si FALSE, pas la permission, sinon TRUE
|
||||
*
|
||||
*/
|
||||
public static function permission($expected){
|
||||
return new Error(Err::Success);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -427,20 +427,19 @@
|
|||
$method = $this->modules[$this->path['module']][$this->path['method']];
|
||||
|
||||
// Si aucune permission n'est definie
|
||||
if( !isset($method['permissions']) ) return true;
|
||||
if( !isset($method['permissions']) || !is_array($method['permissions']) || count($method['permissions']) < 1 )
|
||||
return true;
|
||||
|
||||
|
||||
|
||||
/* [2] Vérification des permissions et de l'authentification
|
||||
=========================================================*/
|
||||
if(!empty($method['permissions'])){
|
||||
$granted = Authentification::permission($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;
|
||||
}
|
||||
/* (1) On retourne FAUX si aucun droit n'a ete trouve */
|
||||
if( $granted->get() !== Err::Success ){
|
||||
$this->error = $granted;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -29,12 +29,13 @@ class RESTexample{
|
|||
|
||||
// ...
|
||||
// process to create article and get $output_created_id
|
||||
$success = true;
|
||||
// ...
|
||||
|
||||
if( !$success )
|
||||
return ['error' => new Error(Err::ModuleError)]; // or other `Err` constant
|
||||
|
||||
return ['created_id' => $output_created_id];
|
||||
return ['created_id' => "Article with title = `$title` and content=`$content`"];
|
||||
break;
|
||||
|
||||
case 'GET':
|
||||
|
@ -43,12 +44,13 @@ class RESTexample{
|
|||
|
||||
// ...
|
||||
// process to get articles and get $output_get_articles
|
||||
$success = true;
|
||||
// ...
|
||||
|
||||
if( !$success )
|
||||
return ['error' => new Error(Err::ModuleError)]; // or other `Err` constant
|
||||
|
||||
return ['articles' => $output_get_articles];
|
||||
return ['articles' => "Article number `$URL_0`: sometitle / somecontent"];
|
||||
break;
|
||||
|
||||
case 'VIEW':
|
||||
|
@ -57,6 +59,7 @@ class RESTexample{
|
|||
|
||||
// ...
|
||||
// process to get articles and get $output_get_articles
|
||||
$success = true;
|
||||
// ...
|
||||
|
||||
if( !$success )
|
||||
|
@ -70,7 +73,7 @@ class RESTexample{
|
|||
'Pragma' => 'no-cache',
|
||||
'Expires' => '0'
|
||||
],
|
||||
'body' => json_encode($output_get_articles)
|
||||
'body' => "Article number `$URL_0`: sometitle / somecontent"
|
||||
];
|
||||
break;
|
||||
|
||||
|
@ -82,12 +85,13 @@ class RESTexample{
|
|||
|
||||
// ...
|
||||
// process to get $output_updated_article
|
||||
$success = true;
|
||||
// ...
|
||||
|
||||
if( !$success )
|
||||
return ['error' => new Error(Err::ModuleError)]; // or other `Err` constant
|
||||
|
||||
return ['article' => $output_updated_article];
|
||||
return ['article' => "Article number `$URL_0`: $title / $content"];
|
||||
break;
|
||||
|
||||
case 'DELETE':
|
||||
|
@ -96,12 +100,13 @@ class RESTexample{
|
|||
|
||||
// ...
|
||||
// process to delete article
|
||||
$success = true;
|
||||
// ...
|
||||
|
||||
if( !$success )
|
||||
return ['error' => new Error(Err::ModuleError)]; // or other `Err` constant
|
||||
|
||||
return []; // returns success
|
||||
return ['log' => "Article `$URL_0` successfully deleted"]; // returns success
|
||||
break;
|
||||
|
||||
// if no match -> error
|
||||
|
|
|
@ -2,59 +2,21 @@
|
|||
|
||||
namespace api\core;
|
||||
|
||||
use \database\core\Repo;
|
||||
use \error\core\Error;
|
||||
use \error\core\Err;
|
||||
use \error\core\Error;
|
||||
|
||||
class Authentification
|
||||
{
|
||||
class Authentification{
|
||||
|
||||
private static $instance;
|
||||
|
||||
private $userType;
|
||||
private $userId;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$token = !empty($_SERVER['PHP_AUTH_DIGEST']) ? $_SERVER['PHP_AUTH_DIGEST'] : false;
|
||||
|
||||
$user = new Repo("AuthentificationRepo/identifyUserByToken", [$token]);
|
||||
$user = $user->answer();
|
||||
|
||||
if( $user != false ){
|
||||
$this->userType = $user["Type"];
|
||||
$this->userId = $user["Id"];
|
||||
}
|
||||
|
||||
new Repo("AuthentificationRepo/updateToken",[$token]);
|
||||
|
||||
new Repo("AuthentificationRepo/purgeOldTokens",[]);
|
||||
|
||||
self::$instance = $this;
|
||||
/* VERIFICATION DES ACCES EN FONCTION DE PERMISSIONS ATTENDUES
|
||||
*
|
||||
* @expected<array> Liste des permissions attendues
|
||||
*
|
||||
* @return status<Boolean> Si FALSE, pas la permission, sinon TRUE
|
||||
*
|
||||
*/
|
||||
public static function permission($expected){
|
||||
return new Error(Err::Success);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
class Request{
|
||||
|
||||
// Constantes
|
||||
public static function config_path(){ return __ROOT__.'/config/modules.json'; }
|
||||
public static $default_options = [
|
||||
private static function config_path(){ return __ROOT__.'/config/modules.json'; }
|
||||
private static $default_options = [
|
||||
'download' => false
|
||||
];
|
||||
|
||||
|
@ -296,7 +296,7 @@
|
|||
|
||||
/* DESERIALISATION A PARTIR DE L'URL ET DES DONNEES POST (OPT)
|
||||
*
|
||||
* @url<String> Contenu de l'url après api/ (si existe)
|
||||
* @url<String> Contenu de l'url formatté (commence à "/module/methode")
|
||||
* @post<Array> [opt] Tableau des donnes
|
||||
*
|
||||
* @return instance<Request> Retourne un objet de type <Request>
|
||||
|
@ -315,7 +315,7 @@
|
|||
/* [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);
|
||||
$pathInUrl = is_string($url) && preg_match('#^/?([\w_-]+/[\w_-]+)(?:/?|/((?:\w+/)*(?:\w+/?)))$#', $url, $urlMatches);
|
||||
|
||||
/* (2) On récupère le @path + les arguments dans l'URL */
|
||||
if( $pathInUrl ){
|
||||
|
@ -435,24 +435,24 @@
|
|||
$method = $this->modules[$this->path['module']][$this->path['method']];
|
||||
|
||||
// Si aucune permission n'est definie
|
||||
if( !isset($method['permissions']) ) return true;
|
||||
if( !isset($method['permissions']) || !is_array($method['permissions']) || count($method['permissions']) < 1 )
|
||||
return true;
|
||||
|
||||
|
||||
|
||||
/* [2] Vérification des permissions et de l'authentification
|
||||
=========================================================*/
|
||||
if(!empty($method['permissions'])){
|
||||
$granted = Authentification::permission($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;
|
||||
}
|
||||
/* (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;
|
||||
}
|
||||
|
|
|
@ -29,12 +29,13 @@ class RESTexample{
|
|||
|
||||
// ...
|
||||
// process to create article and get $output_created_id
|
||||
$success = true;
|
||||
// ...
|
||||
|
||||
if( !$success )
|
||||
return ['error' => new Error(Err::ModuleError)]; // or other `Err` constant
|
||||
|
||||
return ['created_id' => $output_created_id];
|
||||
return ['created_id' => "Article with title = `$title` and content=`$content`"];
|
||||
break;
|
||||
|
||||
case 'GET':
|
||||
|
@ -43,12 +44,13 @@ class RESTexample{
|
|||
|
||||
// ...
|
||||
// process to get articles and get $output_get_articles
|
||||
$success = true;
|
||||
// ...
|
||||
|
||||
if( !$success )
|
||||
return ['error' => new Error(Err::ModuleError)]; // or other `Err` constant
|
||||
|
||||
return ['articles' => $output_get_articles];
|
||||
return ['articles' => "Article number `$URL_0`: sometitle / somecontent"];
|
||||
break;
|
||||
|
||||
case 'VIEW':
|
||||
|
@ -57,6 +59,7 @@ class RESTexample{
|
|||
|
||||
// ...
|
||||
// process to get articles and get $output_get_articles
|
||||
$success = true;
|
||||
// ...
|
||||
|
||||
if( !$success )
|
||||
|
@ -70,7 +73,7 @@ class RESTexample{
|
|||
'Pragma' => 'no-cache',
|
||||
'Expires' => '0'
|
||||
],
|
||||
'body' => json_encode($output_get_articles)
|
||||
'body' => "Article number `$URL_0`: sometitle / somecontent"
|
||||
];
|
||||
break;
|
||||
|
||||
|
@ -82,12 +85,13 @@ class RESTexample{
|
|||
|
||||
// ...
|
||||
// process to get $output_updated_article
|
||||
$success = true;
|
||||
// ...
|
||||
|
||||
if( !$success )
|
||||
return ['error' => new Error(Err::ModuleError)]; // or other `Err` constant
|
||||
|
||||
return ['article' => $output_updated_article];
|
||||
return ['article' => "Article number `$URL_0`: $title / $content"];
|
||||
break;
|
||||
|
||||
case 'DELETE':
|
||||
|
@ -96,12 +100,13 @@ class RESTexample{
|
|||
|
||||
// ...
|
||||
// process to delete article
|
||||
$success = true;
|
||||
// ...
|
||||
|
||||
if( !$success )
|
||||
return ['error' => new Error(Err::ModuleError)]; // or other `Err` constant
|
||||
|
||||
return []; // returns success
|
||||
return ['log' => "Article `$URL_0` successfully deleted"]; // returns success
|
||||
break;
|
||||
|
||||
// if no match -> error
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace router\controller;
|
||||
|
||||
use \api\core\Request;
|
||||
use \api\core\Response;
|
||||
|
||||
|
||||
class api{
|
||||
|
||||
private $request;
|
||||
private $response;
|
||||
|
||||
/* PRE-CALL
|
||||
*
|
||||
* @url<String> Calling URI
|
||||
*
|
||||
*/
|
||||
public function __construct($matches){
|
||||
/* (1) Rebuild request url */
|
||||
$url = $matches['module'].'/'.$matches['method'].$matches['inurl_arguments'];
|
||||
|
||||
/* (2) Creates request */
|
||||
$this->request = Request::remote($url, $_POST);
|
||||
}
|
||||
|
||||
|
||||
/* CALL
|
||||
*
|
||||
*/
|
||||
public function call(){
|
||||
/* (1) Process response */
|
||||
$this->response = $this->request->dispatch();
|
||||
|
||||
/* (2) Manages result */
|
||||
if( $this->response instanceof Response )
|
||||
echo $this->response->serialize();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* POST-CALL
|
||||
*
|
||||
*/
|
||||
public function __destruct(){
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace router\controller;
|
||||
|
||||
|
||||
class page{
|
||||
|
||||
|
||||
private $pagename;
|
||||
|
||||
/* PRE-CALL
|
||||
*
|
||||
* @url<String> Calling URI
|
||||
*
|
||||
*/
|
||||
public function __construct($pagename){
|
||||
$this->pagename = $pagename;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* CALL
|
||||
*
|
||||
*/
|
||||
public function load(){
|
||||
if( file_exists(__PUBLIC__."/view/".$this->pagename.".php") )
|
||||
include __PUBLIC__."/view/".$this->pagename.".php";
|
||||
else
|
||||
echo "page not found";
|
||||
}
|
||||
|
||||
/* POST-CALL
|
||||
*
|
||||
*/
|
||||
public function __destruct(){
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace router\controller;
|
||||
|
||||
|
||||
class redirect{
|
||||
|
||||
|
||||
/* PRE-CALL
|
||||
*
|
||||
* @url<String> Calling URI
|
||||
*
|
||||
*/
|
||||
public function __construct($url){
|
||||
}
|
||||
|
||||
|
||||
/* CALL
|
||||
*
|
||||
*/
|
||||
public function homepage(){
|
||||
header('Location: /testxfw/homepage/');
|
||||
}
|
||||
|
||||
/* POST-CALL
|
||||
*
|
||||
*/
|
||||
public function __destruct(){
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace router\core;
|
||||
|
||||
|
||||
class ControllerFactory{
|
||||
|
||||
|
||||
/* VERIFIE UN CONTROLLER
|
||||
*
|
||||
* @controller<String> Nom du controller
|
||||
*
|
||||
* @return exists<boolean> Si oui ou non le controller existe
|
||||
*
|
||||
*/
|
||||
public static function checkController($controller){
|
||||
/* (1) Check type + pattern */
|
||||
if( !is_string($controller) || !preg_match('/^[A-Za-z_]\w+$/', $controller) )
|
||||
return false;
|
||||
|
||||
/* (2) On vérifie que la classe existe */
|
||||
if( !file_exists(__BUILD__."/router/controller/$controller.php") )
|
||||
return false;
|
||||
|
||||
/* (3) Sinon il existe */
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* INSTANCIE UN CONTROLLER
|
||||
*
|
||||
* @controller<String> Nom du controller
|
||||
* @arguments<Array> [OPTIONNEL] Arguments à passer au constructeur
|
||||
*
|
||||
* @return instance<Module> Instance du controller en question
|
||||
*
|
||||
*/
|
||||
public static function getController($controller, $arguments=[]){
|
||||
/* (1) On vérifie l'existance du controller */
|
||||
if( !self::checkController($controller) )
|
||||
return false;
|
||||
|
||||
/* (2) On récupère la classe */
|
||||
$class_name = "\\router\\controller\\$controller";
|
||||
|
||||
/* (3) On retourne une instance */
|
||||
return new $class_name($arguments);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
/**************************
|
||||
* Route *
|
||||
* 08-12-2016 *
|
||||
***************************
|
||||
* Designed & Developed by *
|
||||
* xdrm-brackets *
|
||||
***************************
|
||||
* https://xdrm.io/ *
|
||||
**************************/
|
||||
|
||||
use router\core\ControllerFactory;
|
||||
|
||||
namespace router\core;
|
||||
|
||||
class Route{
|
||||
|
||||
/* [1] Attributs
|
||||
=========================================================*/
|
||||
private $pattern;
|
||||
private $controller;
|
||||
private $method;
|
||||
private $matches;
|
||||
|
||||
|
||||
/* [2] Instanciation de la route
|
||||
*
|
||||
* @pattern<String> Pattern correspondant a la route
|
||||
* @controller<String> Controller de la route
|
||||
* @method<String> Methode du controller
|
||||
*
|
||||
* @return instance<Route> Retour de l'instance courante
|
||||
*
|
||||
=========================================================*/
|
||||
public function __construct($pattern=null, $controller=null, $method=null){
|
||||
// Note: all arguments must be verified by 'Router->add' method
|
||||
|
||||
/* (1) Pattern -> regex format */
|
||||
$this->pattern = "/^$pattern$/";
|
||||
|
||||
/* (2) Controller */
|
||||
$this->controller = $controller;
|
||||
|
||||
/* (3) Controller's method */
|
||||
$this->method = $method;
|
||||
|
||||
/* (4) Initialize matches */
|
||||
$this->matches = [];
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* [3] Checks if route matches URL
|
||||
*
|
||||
* @url<String> URL
|
||||
*
|
||||
* @return matches<Boolean> If matches URL
|
||||
*
|
||||
=========================================================*/
|
||||
public function match($url){
|
||||
|
||||
/* (1) If doesn't matches @url -> false */
|
||||
if( !preg_match($this->pattern, $url, $matches) )
|
||||
return false;
|
||||
|
||||
/* (2) Return only named matches */
|
||||
foreach($matches as $name=>$match)
|
||||
if( !is_numeric($name) )
|
||||
$this->matches[$name] = $match;
|
||||
|
||||
/* (4) Add complete URL */
|
||||
$this->matches['__URL__'] = $url;
|
||||
|
||||
/* (5) Return status */
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/* [4] Method call
|
||||
*
|
||||
* @return response<String> Response
|
||||
*
|
||||
=========================================================*/
|
||||
public function call(){
|
||||
/* (1) Instanciate controller */
|
||||
$instance = ControllerFactory::getController($this->controller, $this->matches);
|
||||
|
||||
/* (2) Launch method & catch response */
|
||||
$response = call_user_func([$instance, $this->method]);
|
||||
|
||||
/* (3) Call controller's destructor */
|
||||
$instance = null;
|
||||
|
||||
/* (4) Return response */
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,321 @@
|
|||
<?php
|
||||
|
||||
/**************************
|
||||
* Router *
|
||||
* 08-12-2016 *
|
||||
***************************
|
||||
* Designed & Developed by *
|
||||
* xdrm-brackets *
|
||||
***************************
|
||||
* https://xdrm.io/ *
|
||||
**************************/
|
||||
|
||||
use \router\core\ControllerFactory;
|
||||
|
||||
namespace router\core;
|
||||
|
||||
class Router{
|
||||
|
||||
|
||||
/* [1] Attributes
|
||||
=========================================================*/
|
||||
private $url; // current URL
|
||||
private $cnf; // parsed configuration
|
||||
private $http_methods; // allowed HTTP methods
|
||||
private $routes; // built routes
|
||||
|
||||
|
||||
/* [2] Configuration file
|
||||
=========================================================*/
|
||||
private static function config_path(){ return __CONFIG__.'/routes.json'; }
|
||||
|
||||
// Get random token
|
||||
public static function randBoundary(){ return dechex( random_int((int) 1e10, (int) 1e20) ); }
|
||||
|
||||
|
||||
|
||||
/* [3] Constructor
|
||||
*
|
||||
* @url<String> Current URL
|
||||
*
|
||||
* @return instance<Router> Instance du routeur
|
||||
*
|
||||
=========================================================*/
|
||||
public function __construct($url=null){
|
||||
/* (1) Checks arguments
|
||||
---------------------------------------------------------*/
|
||||
/* (1) Default value if incorrect */
|
||||
$this->url = is_string($url) ? $url : '';
|
||||
|
||||
/* (2) Add first '/' if missing */
|
||||
if( !preg_match('/^\//', $url) )
|
||||
$this->url = '/'.$this->url;
|
||||
|
||||
|
||||
/* (2) Loads configuration
|
||||
---------------------------------------------------------*/
|
||||
/* (1) Tries to load configuration */
|
||||
$this->cnf = self::loadConfig();
|
||||
|
||||
/* (2) If error occurs, throw Exception */
|
||||
if( is_null($this->cnf) )
|
||||
throw new \Exception("[Router] Configuration file error found");
|
||||
|
||||
|
||||
/* (3) Set allowed HTTP methods
|
||||
---------------------------------------------------------*/
|
||||
/* (1) If not defined */
|
||||
if( !isset($this->cnf['methods']) )
|
||||
throw new \Exception('[Router] Configuration file error, \'methods\' clause missing');
|
||||
|
||||
/* (2) Try to clean methods */
|
||||
$this->http_methods = self::cleanMethods($this->cnf['methods']);
|
||||
|
||||
/* (3) Manage error */
|
||||
if( is_null($this->http_methods) )
|
||||
throw new \Exception('[Router] Configuration file error. \'methods\' must be an array of HTTP methods ["GET", "POST", ...]');
|
||||
|
||||
|
||||
/* (4) Initialize routes
|
||||
---------------------------------------------------------*/
|
||||
/* (1) Init routes */
|
||||
$this->routes = [];
|
||||
|
||||
foreach($this->http_methods as $method)
|
||||
$this->routes[$method] = [];
|
||||
|
||||
/* (2) Default configuration if missing */
|
||||
if( !isset($this->cnf['routes']) || !is_array($this->cnf['routes']) )
|
||||
$this->cnf['routes'] = [];
|
||||
|
||||
|
||||
/* (5) Loads each route
|
||||
---------------------------------------------------------*/
|
||||
foreach($this->cnf['routes'] as $pattern=>$route){
|
||||
|
||||
/* (1) If missing (required) parameters */
|
||||
if( !isset($route['controller']) )
|
||||
continue;
|
||||
|
||||
/* (2) Default value for 'methods' */
|
||||
( !isset($route['methods']) || !is_array($route['methods']) ) && ($route['methods'] = $this->http_methods);
|
||||
|
||||
/* (3) Default value for 'arguments' */
|
||||
( !isset($route['arguments']) || !is_array($route['arguments']) ) && ($route['arguments'] = []);
|
||||
|
||||
/* (4) Add route */
|
||||
$added = $this->add($pattern, $route['controller'], $route['arguments']);
|
||||
|
||||
// if error -> next
|
||||
if( $added === false )
|
||||
continue;
|
||||
|
||||
|
||||
/* (5) Add route for each method */
|
||||
foreach($route['methods'] as $method)
|
||||
if( in_array($method, $this->http_methods) )
|
||||
$this->routes[$method][] = $added;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* [4] Adds a route
|
||||
*
|
||||
* @pattern<String> URL pattern with {somevar} variables within
|
||||
* @controller<String> Controller name + method "controllername:methodname"
|
||||
* @arguments<Array> List of pattern's arguments and their RegExp composition (default is alphanumeric)
|
||||
*
|
||||
* @return route<Route> New instance of Route || false on error
|
||||
*
|
||||
=========================================================*/
|
||||
public function add($pattern=null, $controller=null, $arguments=[]){
|
||||
|
||||
/* (1) Format and check pattern
|
||||
---------------------------------------------------------*/
|
||||
/* (1) If not a string */
|
||||
if( !is_string($pattern) )
|
||||
return false;
|
||||
|
||||
/* (2) Format pattern and check result */
|
||||
$pattern = self::formatPattern($pattern, $arguments);
|
||||
|
||||
if( $pattern === false )
|
||||
return false;
|
||||
|
||||
|
||||
/* (2) Check controller
|
||||
---------------------------------------------------------*/
|
||||
/* (1) Check default type */
|
||||
if( !is_string($controller) || !preg_match('/^([A-Za-z_]\w+):([A-Za-z_]\w+)$/', $controller, $c_matches) )
|
||||
return false;
|
||||
|
||||
/* (2) Check existence */
|
||||
if( !ControllerFactory::checkController($c_matches[1]) )
|
||||
return false;
|
||||
|
||||
|
||||
/* (3) Check method
|
||||
---------------------------------------------------------*/
|
||||
if( !method_exists('\\router\\controller\\'.$c_matches[1], $c_matches[2]) )
|
||||
return false;
|
||||
|
||||
|
||||
/* (4) Return new route
|
||||
---------------------------------------------------------*/
|
||||
return new Route($pattern, $c_matches[1], $c_matches[2]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* [5] Router launch
|
||||
*
|
||||
=========================================================*/
|
||||
public function run(){
|
||||
/* (1) Manage HTTP method
|
||||
---------------------------------------------------------*/
|
||||
/* (1) Fetch HTTP method */
|
||||
$httpMethod = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
/* (2) If no route for this -> exit */
|
||||
if( !isset($this->routes[$httpMethod]) || count($this->routes[$httpMethod]) <= 0 )
|
||||
return false;
|
||||
|
||||
|
||||
/* (2) Manage routes (matching)
|
||||
---------------------------------------------------------*/
|
||||
/* (1) Check for each HTTP method's route */
|
||||
foreach($this->routes[$httpMethod] as $route)
|
||||
|
||||
/* (2) First route that matches -> call & return response */
|
||||
if( $route->match($this->url) )
|
||||
return $route->call();
|
||||
|
||||
|
||||
/* (3) If no route found -> return false
|
||||
---------------------------------------------------------*/
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/* FORMATS A PATTERN
|
||||
*
|
||||
* @pattern<String> Pattern to process on
|
||||
* @arguments<Array> List of used arguments, with regex if given
|
||||
* @vars<Boolean> [OPT] If variable replacement have to be done
|
||||
*
|
||||
* @return formatted<String> Formatted pattern || false on error
|
||||
*
|
||||
*/
|
||||
public static function formatPattern($pattern, $arguments=[]){
|
||||
|
||||
/* (1) Check arguments
|
||||
---------------------------------------------------------*/
|
||||
/* (1) Check minimal length */
|
||||
if( strlen($pattern) < 1 )
|
||||
return false;
|
||||
|
||||
/* (2) Arguments formatting */
|
||||
$arguments = !is_array($arguments) ? [] : $arguments;
|
||||
|
||||
|
||||
/* (2) Replace special characters + replace vars
|
||||
---------------------------------------------------------*/
|
||||
/* (1) Check default URL format */
|
||||
if( !preg_match('/^(\/[\w\{\}-]*)*\/?$/', $pattern) )
|
||||
return false;
|
||||
|
||||
/* (2) Escape special characters */
|
||||
$pattern = str_replace('/', '\\/', $pattern);
|
||||
|
||||
/* (3) Add optional ending '/' */
|
||||
if( !preg_match('/\/$/', $pattern) )
|
||||
$pattern .= '\\/?';
|
||||
|
||||
/* (4) Replace variable by tagged capturing groups */
|
||||
$boundary = self::randBoundary();
|
||||
$pattern = preg_replace('/\{([a-z_][a-z0-9_]*)\}/i', '(?P<$1>'.$boundary.'-$1-'.$boundary.')', $pattern);
|
||||
|
||||
|
||||
/* (3) Variable replacement
|
||||
---------------------------------------------------------*/
|
||||
/* (1) List variables */
|
||||
$vars = [];
|
||||
$var_pattern = '/'.$boundary.'\-([A-Za-z_][A-Za-z0-9_]*)\-'.$boundary.'/';
|
||||
preg_match_all($var_pattern, $pattern, $matches);
|
||||
|
||||
/* (2) For each matching variable -> replace with associated regex */
|
||||
if( is_array($matches) && isset($matches[1]) ){
|
||||
|
||||
foreach($matches[1] as $m=>$varname){
|
||||
|
||||
// {3.1.1} Not in @arguments -> default regex //
|
||||
if( !isset($arguments[$varname]) || !is_string($arguments[$varname]) ){
|
||||
$pattern = str_replace($matches[0][$m], '[A-Za-z0-9_]+', $pattern);
|
||||
continue;
|
||||
}
|
||||
|
||||
// {3.1.2} If variable in @arguments -> set regex without capturing-groups //
|
||||
// $without_capg = str_replace('(', '(?:', $arguments[$varname]);
|
||||
$pattern = str_replace($matches[0][$m], $arguments[$varname], $pattern);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* (4) Return formatted pattern
|
||||
---------------------------------------------------------*/
|
||||
return $pattern;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* LOADS CONFIGURATION
|
||||
*
|
||||
* @return cnf<Array> Configuration content || NULL if error
|
||||
*
|
||||
*/
|
||||
private static function loadConfig(){
|
||||
/* (1) Set configuration file's path */
|
||||
$cnfpath = self::config_path();
|
||||
|
||||
/* (2) Checks file */
|
||||
if( !file_exists($cnfpath) )
|
||||
return null; // throw new \Exception("[Router] Configuration file not found");
|
||||
|
||||
/* (3) Checks format -> null if error */
|
||||
return json_decode( file_get_contents($cnfpath), true );
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* CHECKS METHODS AND CLEAN THE LIST IF CORRECT
|
||||
*
|
||||
* @wanted<Array> List of wanted methods
|
||||
*
|
||||
* @return cleaned<Array> Cleaned methods || null if error
|
||||
*
|
||||
*/
|
||||
private static function cleanMethods($wanted=[]){
|
||||
/* (1) Checks @wanted */
|
||||
if( !is_array($wanted) || count($wanted) < 1 )
|
||||
return null; // throw new \Exception('[Router] Configuration file error, \'methods\' must be an array containing managed HTTP methods');
|
||||
|
||||
/* (2) Set methods (uppercase and unique) */
|
||||
$cleaned = [];
|
||||
|
||||
foreach($wanted as $method)
|
||||
if( !in_array(strtoupper($method), $cleaned) )
|
||||
$cleaned[] = strtoupper($method);
|
||||
|
||||
/* (3) Return cleaned method list */
|
||||
return $cleaned;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue