api:2.2 notice in progress 2

This commit is contained in:
xdrm-brackets 2016-12-11 18:41:37 +01:00
parent deb6484f6c
commit 5d75e23091
11 changed files with 1387 additions and 24 deletions

View File

@ -1,4 +1,7 @@
### xdrm-framework # xdrm-framework
#### Description #### Description
xdrm-framework is a tool that wraps my framework and all it's component's versions. It allows to build a web framework's base with chosen modules and their versions. xdrm-framework is a tool that wraps my framework and all it's component's versions. It allows to build a web framework's base with chosen modules and their versions.

View File

@ -28,6 +28,14 @@
"http": [ "http": [
"1.0" "1.0"
] ]
},
"2.3": {
"error": [
"2.0"
],
"http": [
"1.0"
]
} }
}, },
"orm": { "orm": {
@ -63,7 +71,7 @@
} }
}, },
"installed": { "installed": {
"api": "2.2", "api": "2.3",
"error": "2.0", "error": "2.0",
"http": "1.0", "http": "1.0",
"orm": "0.8.2", "orm": "0.8.2",

300
notice/api/2.0.md Normal file
View File

@ -0,0 +1,300 @@
```yaml
module: api
version: 2.2
requires:
- http: 1.0
- error: 2.0
```
Links
====
[[1] User guide]()
- [1 - Overview]()
- [(1) introduction & features]()
- [(2) basic knowledge]()
- [2 - Usage]()
- [(1) setup]()
- [(2) from php internally]()
- [(3) from HTTP requests]()
- [3 - Configuration]()
- [(1) Basic usage]()
- [(2) Advanced usage]()
- [4 - Implementation]()
- [(1) Permissions / AuthSystem]()
- [(2) Modules & methods]()
- [(3) Automatic type check]()
- [5 - Class documentation]()
- [(1) Request]()
- [(2) Response]()
- [(3) AuthSystem]()
- [(4) Checker]()
- [(4) ModuleFactory]()
[[2]. Advanced guide]()
User guide
====
1 - Overview
----
#### (1) Introduction & features
The `api` package (v2.2) allows you to easily create and manage an API. It could be an HTTP API (REST, or other), or you can use it as an internal core for your system.
The aim of this package is to make your life easier working with API. The only things you have to do is to implement your processes and edit the configuration, the package will do the rest.
Things you have to do :
- implement your processes (obviously)
- implement your authentication system (cf. [AuthSystem]())
- edit the configuration file (cf. [configuration]())
Things you **don't have** to do :
- input type check (cf. [Checker]())
- API multiple permission management
- optional or required inputs
- before and after scripts
#### (2) Basic knowledge
The API is based over a 2-level delegation structure :
1. `module` which is a set of methods
2. `method` which have input, output, permissions, and is bound to a function
2 - Usage
----
#### (1) Setup
In order to make the API work, you have to :
1. Edit the configuration file according to your needs (cf. [configuration]())
2. Implement the Authentication System to manage permissions (cf. [AuthSystem]())
#### (2) From php internally
> ##### 1) include the `autoloader` file
> ```php
> <?php
> require_once '../autoloader.php';
> ```
> ##### 2) load useful classes
> ```php
> <?php
>
> ...
>
> // for API use
> use \api\core\Request;
> use \api\core\Response;
>
> // for error handling
> use \error\core\Err;
>```
> ##### 3) create a request
> ```php
> <?php
>
> ...
>
> // creates a request for the module {module} and its method {method} with params
> $request = new Request('{module}/{method}', [
> 'param1' => 10,
> 'param2' => 'somevalue'
> ]);
>
>```
> ##### 4) catch possible errors (optional)
> ```php
> <?php
>
> ...
>
> // if error is not Err::Success
> if( $request->error->get() !== Err::Success )
> 'do something';
> ```
> ##### 5) execute the request and catch response
> ```php
> <?php
>
> ...
>
> $response = $request->dispatch();
> ```
> ##### 6) catch response errors (optional)
> ```php
> <?php
>
> ...
>
> // if error is not Err::Success
> if( $response->error->get() !== Err::Success )
> 'do something';
> ```
> ##### 7) catch response output
> ```php
> <?php
>
> ...
>
> // fetch all outputs
> $output = $response->getAll();
>
> // fetch specific output
> $specific = $response->get('someOutputName');
> ```
#### (3) From HTTP requests
5 - class documentation
----
#### (4) Checker
`Checker` checks the input values according to the type given in the configuration.
The default types below are available in the default package.
To add a new type, just open the file `/build/api/Checker.php` and add an entry in the `switch` statement.
##### Default types
|Type|Example|Description|
|---|---|---|
|`mixed`|`[9,"a"]`, `"a"`|Any content (can be simple or complex)|
|`id`|`10`, `"23"`|Positive integer number between `0` and `2147483647`|
|`numeric`|`-10.2`, `"23"`|Any number, `null` and the string `"null"`|
|`text`|`"Hello!"`|String that can be of any length (even empty)|
|`hash`|`"4612473aa81f93a878674f9ebffa8d63a1b51ea28dcdcdb1e89eb512aae9b77e"`|String with a length of 40 or 64, containing only hexadecimal characters|
|`alphanumeric`|`"abc029.-sd9"`|String containing only alphanumeric, ___, _-_, and _._ characters|
|`letters`|`"abc -sd"`|String containing only letters, _-_, and space characters|
|`mail`|`"a.b@c.def"`|Valid email address|
|`number`|`0102030405`|Phone number, following formats allowed : `06`, `+336`, `+33 6`|
|`array`|`[1, 3]`|Non-empty array|
|`object`|_works only within php_|Non-empty object|
|`boolean`|`true`, `false`|Boolean|
|`varchar(a,b)`|`"Hello!"`|String with a length between `a` and `b` (included)|
|`varchar(a,b,c)`|`"abc"`|String with a length between `a` and `b` (included) and matching the `c` type|
##### Complex type : chainable array
|Type|Sub-Type|Description|
|---|---|---|
|`array<a>`|`a`|Array containing only entries matching the type `a`|
> **Note:** It is possible to chain `array` type as many as needed.
**Ex.:** `array<array<id>>` - Will match array only containing arrays that only contains `id` entries.
## IV. How to use ?
#### 1. Set up
To make your api work, you have to:
1. edit the configuration (mode details [here](#IV. configuration))
2. create the modules classes and implement their methods according to your configuration (mode details [here](#V. implementation))
#### 2. Internal use in php
You can use your api from php internally without using HTTP request.
First, you must require the `autoloader` file and load the API.
```php
<?php require_once '../autoloader.php'
use \api\core\Request;
use \api\core\Request;
```
Then, you must pass the module, the method and the parameters.
```php
<?php require_once '../autoloader.php'
use \api\core\Request;
use \api\core\Request;
$module = 'user'; // the module 'user'
$method = 'getUsername'; // and its method 'getUsername'
$params = ['id_user' => 'someusername']; // and the parameters
// 1. Create the request
$request = new Request("$module/$method", $params);
// 2. Execute request and catch response
$response = $request->dispatch();
// 3. Get response error code
$error_code = $response->error->get();
// 4. Get response output
$output = $response->getAll();
```
#### 3. HTTP Request use in php
In order to setup an automatic bound from HTTP requests to API directly, you must use a router. Then you have some possibilities :
**Case 1**: You want an URL like `http://www.host.com/{module}/{method}/` and pass parameters through POST or form-data. In order to set it up, you must catch the url starting at `/{module}/{method}` so you have to truncate the beginning (for instance if you have /api/{module}/..)
**Case 2**: You want an URL like `http://www.host.com/api/` and pass all data through POST or form-data.
## V. configuration
```json
{
"{module_name}": {
"{http_method}::{method_name}": {
"description": "{method_description}",
"permissions": ["{method_perm}"],
"options": { "download": "{is_downloadable}" },
"parameters": {
"{name_param}": { "description": "{desc_param}", "type": "{type_param}", "optional": "{is_optional}" }
},
"output": {
"{name_output}": { "description": "{desc_output}", "type": "{type_output}" }
}
}
}
}
```
|variable|description|exemple|
|-------|-------|------|
|`{module_name}`|alphanumeric module name|"publications"|
|`{http_method}`|uppercase HTTP method|"POST"|
|`{method_name}`|alphanumeric method name|"article"|
|`{method_description}`|textual description|"Returns a specific article"|
|`{method_perm}`|permission array|`["poster", "admin", "user"]`|
|`{is_downloadable}`|If you want this method to return a file|`true`, `false`|
|`{name_param}`|Your param's name|"id_article"|
|`{desc_param}`|Your param's description|"Wanted article's id"|
|`{type_param}`|Your param's type (cf. Checker)|"Wanted article's type"|
|`{is_optional}`|Whether to make your param _required_|`true`, `false`|
|`{name_output}`|Your output's name|"article"|
|`{desc_output}`|Your output's description|"Article content"|
## VI. implementation
For the implementation let's assume that `/config/modules.json` looks like this
```json
{
"user": {
"POST::sign_in": {
""
}
}
}
```

View File

@ -30,6 +30,18 @@ The api works on a 2-level delegation structure :
## III. components and files ## III. components and files
The API core is build over 5 classes:
> 1. **Request** - is the core of the package
2. **Response** - renders and manages the responses
3. **Authentification** - manages the authentification part (it has to be over written for each project)
4. **Checker** - manages auto checked types for input parameters
5. **ModuleFactory** - used to instanciate each module, transparent to the user
The API file structure is composed with :
> 1. the configuration file is located at `/config/modules.php`.
2. the core folder located at `/build/api/core/`
3. the module's implementation folder located at `/build/api/module/`
## IV. How to use ? ## IV. How to use ?
#### 1. Set up #### 1. Set up
@ -86,12 +98,6 @@ In order to setup an automatic bound from HTTP requests to API directly, you mus
## V. configuration ## V. configuration
###### How to use
In order to use the API, you must begin by writting your _"methods"_ in the configuration file located at `/config/modules.json`.
In order to be understood, lets call `module` a set of methods, and `method` a function which outputs data from input parameters.
**Configuration format**
```json ```json
{ {
@ -111,7 +117,6 @@ In order to be understood, lets call `module` a set of methods, and `method` a f
} }
} }
``` ```
|variable|description|exemple| |variable|description|exemple|
@ -129,15 +134,16 @@ In order to be understood, lets call `module` a set of methods, and `method` a f
|`{name_output}`|Your output's name|"article"| |`{name_output}`|Your output's name|"article"|
|`{desc_output}`|Your output's description|"Article content"| |`{desc_output}`|Your output's description|"Article content"|
###### Classes (advanced)
The API is managed through 5 classes :
1. Request - is the core of the module
2. Response - renders and manages the responses
3. Authentification - manages the authentification part (it has to be over written for each case)
4. Checker - manages auto checked parameters' types
5. ModuleFactory - to instanciate each module, transparent to the user
## VI. implementation ## VI. implementation
For the implementation let's assume that your module is `xmodulex` and your method For the implementation let's assume that `/config/modules.json` looks like this
```json
{
"user": {
"POST::sign_in": {
""
}
}
}
```

View File

@ -81,11 +81,6 @@
return $checker && is_numeric($value) && $value <= 2147483647 && $value >= 0; return $checker && is_numeric($value) && $value <= 2147483647 && $value >= 0;
break; 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) // String quelconque (peut etre vide)
case 'text': case 'text':
return $checker && is_string($value); return $checker && is_string($value);

View File

@ -0,0 +1,20 @@
<?php
namespace api\core;
use \error\core\Err;
use \error\core\Error;
interface AuthSystem{
/* VERIFICATION DES ACCES EN FONCTION DE PERMISSIONS ATTENDUES
*
* @expected<array> Liste des permissions attendues
*
* @return error<Error> Erreur associée à la permission (Success/PermissionError/TokenError/etc)
*
*/
public static function permission($expected);
}
?>

145
src/packages/api/2.3/core/Checker.php vendored Normal file
View File

@ -0,0 +1,145 @@
<?php
namespace api\core;
class Checker{
/* VERIFICATIONS DES TYPES UTILES GENERIQUES
*
* @type<String> Type que l'on veut verifier
* @value<mixed*> Valeur a verifier
*
* @return match<Boolean> 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;
// 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;
}
}
?>

View File

@ -0,0 +1,31 @@
<?php
namespace api\core;
class ModuleFactory{
/* INSTANCIE UN MODULE
*
* @module<String> Nom du module
* @arguments<Array> [OPTIONNEL] Arguments à passer au constructeur
*
* @return instance<Module> 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);
}
}

608
src/packages/api/2.3/core/Request.php vendored Normal file
View File

@ -0,0 +1,608 @@
<?php
namespace api\core;
use \database\core\DatabaseDriver;
use \api\core\AuthSystem;
use \api\core\ModuleFactory;
use \error\core\Error;
use \error\core\Err;
use \http\core\HttpRequest;
class Request{
// Constantes
private static function config_path(){ return __ROOT__.'/config/modules.json'; }
private static $default_options = [
'download' => false
];
private static $authsystem = null;
// 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<String> Chemin de delegation ("module/methode")
* @param<Array> Tableau associatif contenant les parametres utiles au traitement
*
* @return status<Boolean> Retourne si oui ou non tout s'est bien passe
*
*/
public function __construct($path=null, $params=null){
/* [1] Fetch HttpRequest correct data
=========================================================*/
/* (1) Parse HttpRequest data because php doesn't parse it for non-POST HTTP method */
$httprequest = new HttpRequest();
$_POST = $httprequest->POST();
/* [2] 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;
}
/* [3] 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;
}
/* [4] 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']));
/* [5] Verification du chemin (existence module+methode)
=========================================================*/
if( !$this->checkPath($path) ) // Verification de la coherence du chemin + attribution
return false;
/* [6] Verification des droits
=========================================================*/
if( !$this->checkPermission() ) // Si on a pas les droits
return false;
/* [7] Verification des parametres (si @type est defini)
=========================================================*/
if( !$this->checkParams($params) ) // Verification de tous les types
return false;
/* [8] Récupèration des options
=========================================================*/
$this->buildOptions();
/* [9] Construction de l'objet
=========================================================*/
$this->params = $params;
$this->error->set(Err::Success);
return true; // On retourne que tout s'est bien passe
}
/* DEFINIT LE SYSTEME D'AUTHENTIFICATION
*
* @instance<AuthSystem> Instance de type AuthSystem
*
* @return success<Boolean> Whether the AuthSystem is valid or not
*
*/
public static function setAuthSystem($instance=null){
/* (1) Check instance type */
if( !($instance instanceof AuthSystem) )
return false;
/* (2) Store instance */
self::$authsystem = $instance;
}
/* EXECUTE LE TRAITEMENT ASSOCIE ET REMPLIE LA REPONSE
*
* @return answer<Response> Retourne une reponse de type <Response> 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, '<?php'.PHP_EOL);
/* (2) Script qui écrira les headers */
foreach($returned['headers'] as $header=>$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<String> Contenu de l'url formatté (commence à "/module/methode")
* @post<Array> [opt] Tableau des donnes
*
* @return instance<Request> Retourne un objet de type <Request>
*
* @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 = 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 ){
// {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 <Request>
=========================================================*/
return new Request($data['path'], $params);
}
/* VERIFICATION DU FORMAT ET DE LA COHERENCE DU CHEMIN SPECIFIE
*
* @path<String> String correspondant au chemin de delegation ("module/methode")
*
* @return validity<Boolean> 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<bool> 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']) || !is_array($method['permissions']) || count($method['permissions']) < 1 )
return true;
/* [2] Vérification des permissions et de l'authentification
=========================================================*/
$granted = self::$authsystem::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<Array> 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<bool> 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<String> 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']);
}
}
?>

122
src/packages/api/2.3/core/Response.php vendored Normal file
View File

@ -0,0 +1,122 @@
<?php
namespace api\core;
use \error\core\Error;
use \error\core\Err;
class Response{
// Attributs prives utiles (initialisation)
private $data;
public $error;
/* CONSTRUCTEUR D'UNE REPONSE DE MODULE
*
* @error<ModuleError> 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<String> Le nom de la valeur a ajouter
* @value<mixed*> 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<Array> 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<String> Le nom de la valeur a recuperer
*
* @return value<mixed*> La valeur a cette cle
* @return error<null> 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<Array> Les donnees de la reponse
*
*/
public function getAll(){
// Sinon, on retourne la valeur associee
return $this->data;
}
/* SERIALISATION A PARTIR DES DONNEES
*
* @return json<String> 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);
}
}
?>

View File

@ -0,0 +1,125 @@
<?php
namespace api\module;
use error\core\Error;
use error\core\Err;
class RESTexample{
public function __construct(){
// Routine to execute before each call to RESTexample's method
}
public function __destruct(){
// Routine to execute after each call to RESTexample's method
}
public function article($argv){
extract($argv);
switch(__HTTP_METHOD__){
case 'POST':
// POST a new article with variables:
$title; // new article's title
$content; // new article's content
// ...
// 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' => "Article with title = `$title` and content=`$content`"];
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
$success = true;
// ...
if( !$success )
return ['error' => new Error(Err::ModuleError)]; // or other `Err` constant
// optional param not given is set to null
if( is_null($URL_0) )
return ['articles' => [ "Article number `1`: sometitle / somecontent", "Article number `2`: sometitle / somecontent"] ];
else
return ['articles' => [ "Article number `$URL_0`: sometitle / somecontent"] ];
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
$success = true;
// ...
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' => "Article number `$URL_0`: sometitle / somecontent"
];
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
$success = true;
// ...
if( !$success )
return ['error' => new Error(Err::ModuleError)]; // or other `Err` constant
return ['article' => "Article number `$URL_0`: $title / $content"];
break;
case 'DELETE':
// DELETEs an article with the variable:
$URL_0; // id of the article to remove
// ...
// process to delete article
$success = true;
// ...
if( !$success )
return ['error' => new Error(Err::ModuleError)]; // or other `Err` constant
return ['log' => "Article `$URL_0` successfully deleted"]; // returns success
break;
// if no match -> error
default:
return ['error' => new Error(Err::UnknownHttpMethod)];
break;
}
}
}