Rest
Le module REST implémente un CRUD de base,
avec un système d’authentification, directement testable dans la partie administration.
REST et routage
Le routeur est essentiel au module REST, puisque REST (Representation State Transfer) est basé sur les URL et les méthodes HTTP.
Note
Pour des raisons de performance, les routes REST sont mises en cache indépendamment des autres routes.
Il est donc nécessaire de démarrer le routeur d’une manière particulière pour activer les routes REST et ne pas obtenir une erreur 404 récurrente.
Le routeur est démarré dans le fichier services.php
.
Sans l’activation des routes REST :
...
Router::start();
Pour activer les routes REST dans une application qui a également une partie non-REST :
...
Router::startAll();
Pour activer uniquement les routes REST :
Router::startRest();
Il est possible de lancer le routage de manière conditionnelle (cette méthode ne sera efficace que si le nombre de routes est important dans l’une ou l’autre partie) :
...
if($config['isRest']()){
Router::startRest();
}else{
Router::start();
}
Ressource REST
Un contrôleur REST peut être directement associé à un modèle.
Note
Si vous n’avez pas de base de données Mysql sous la main, vous pouvez télécharger celle-ci : messagerie.sql
Création
Avec les devtools :
Ubiquity rest RestUsersController -r=User -p=/rest/users
Ou avec les webtools :
Allez dans la partie REST puis choisir Add a new resource :
Le contrôleur créé :
1 namespace controllers;
2
3 /**
4 * Rest Controller RestUsersController
5 * @route("/rest/users","inherited"=>true,"automated"=>true)
6 * @rest("resource"=>"models\\User")
7 */
8 class RestUsersController extends \Ubiquity\controllers\rest\RestController {
9
10 }
Comme les attributs automated et inherited de la route sont définis sur true, le contrôleur a les routes par défaut de la classe parente.
The base controller RestController is not standardized, it should be considered as an example for data interrogation.
Interface de test
Les webtools fournissent une interface pour l’interrogation des données :
Obtenir une instance
Une instance d’utilisateur est accessible par sa clé primaire (id) :
Inclusion des membres associés : l’organisation de l’utilisateur
Inclusion des membres associés : organisation, connexions et groupes de l’utilisateur.
Obtenir plusieurs instances
Récupérer toutes les instances :
Définir une condition :
Inclure les membres associés :
Ajout d’une instance
Les données sont envoyées par la méthode POST, avec un content-type défini à application/x-www-form-urlencoded
:
Ajoutez les paramètres du nom et du domaine en cliquant sur le bouton parameters :
L’ajout nécessite une authentification, donc une erreur est générée, avec le statut 401 :
L’interface d’administration vous permet de simuler l’authentification par défaut et d’obtenir un jeton, en sollicitant la méthode connect :
Le jeton est alors automatiquement envoyé dans les requêtes suivantes.
L’enregistrement peut alors être inséré.
Mise à jour d’une instance
La mise à jour suit le même schéma que l’insertion.
Suppression d’une instance
Personnalisation
Routes
Il est bien sûr possible de personnaliser et de simplifier les routes.
Dans ce cas, il est préférable d’utiliser l’héritage depuis la classe RestBaseController, et de ne pas activer les routes automatiques.
1namespace controllers;
2
3use models\Organization;
4
5/**
6 * Rest Controller for organizations
7 *
8 * @route("/orgas")
9 * @rest
10 */
11class RestOrgas extends \Ubiquity\controllers\rest\RestBaseController {
12
13 public function initialize() {
14 $this->model = Organization::class;
15 parent::initialize();
16 }
17
18 /**
19 *
20 * @get
21 */
22 public function index() {
23 $this->_get();
24 }
25
26 /**
27 *
28 * @get("{keyValues}")
29 */
30 public function get($keyValues) {
31 $this->_getOne($keyValues);
32 }
33
34 /**
35 *
36 * @post("/")
37 */
38 public function add() {
39 $this->_add();
40 }
41
42 /**
43 *
44 * @patch("{keyValues}")
45 */
46 public function update(...$keyValues) {
47 $this->_update(...$keyValues);
48 }
49
50 /**
51 *
52 * @delete("{keyValues}")
53 */
54 public function delete(...$keyValues) {
55 $this->_delete(...$keyValues);
56 }
57}
Après avoir réinitialisé le cache, l’interface de test montre les routes accessibles :
Modification des données envoyées
Par sur-définition
Il est possible de modifier les données envoyées aux méthodes update et add, afin d’ajouter, modifier ou supprimer la valeur des champs avant l’envoi.
Soit en surdéfinissant la méthode getDatas :
...
protected function getDatas() {
$datas = parent::getDatas();
unset($datas['aliases']);// Remove aliases field
return $datas;
}
Avec les events
Soit de manière plus globale en agissant sur les événements de repos :
use Ubiquity\events\EventsManager;
use Ubiquity\events\RestEvents;
use Ubiquity\controllers\rest\RestBaseController;
...
EventsManager::addListener(RestEvents::BEFORE_INSERT, function ($o, array &$datas, RestBaseController $resource) {
unset($datas['aliases']);// Remove aliases field
});
Authentification
Ubiquity REST implémente une authentification Oauth2 avec des jetons Bearer.
Seules les méthodes avec l’annotation @authorization
nécessitent l’authentification, ce sont les méthodes de modification (add, update & delete).
/**
* Update an instance of $model selected by the primary key $keyValues
* Require members values in $_POST array
* Requires an authorization with access token
*
* @param array $keyValues
* @authorization
* @route("methods"=>["patch"])
*/
public function update(...$keyValues) {
$this->_update ( ...$keyValues );
}
La méthode connect d’un contrôleur REST établit la connexion et renvoie un nouveau jeton.
Il appartient au développeur de surcharger cette méthode pour gérer une éventuelle authentification avec login et mot de passe.
Simulation d’une connexion avec login
Dans cet exemple, la connexion consiste simplement à envoyer une variable utilisateur par la méthode post.
Si l’utilisateur est fourni, la méthode connect
de l’instance $server
retourne un jeton valide qui est stocké dans la session (la session agit ici comme une base de données).
1 namespace controllers;
2
3 use Ubiquity\utils\http\URequest;
4 use Ubiquity\utils\http\USession;
5
6 /**
7 * Rest Controller RestOrgas
8 * @route("/rest/orgas","inherited"=>true,"automated"=>true)
9 * @rest("resource"=>"models\\Organization")
10 */
11 class RestOrgas extends \Ubiquity\controllers\rest\RestController {
12
13 /**
14 * This method simulate a connection.
15 * Send a <b>user</b> variable with <b>POST</b> method to retreive a valid access token
16 * @route("methods"=>["post"])
17 */
18 public function connect(){
19 if(!URequest::isCrossSite()){
20 if(URequest::isPost()){
21 $user=URequest::post("user");
22 if(isset($user)){
23 $tokenInfos=$this->server->connect ();
24 USession::set($tokenInfos['access_token'], $user);
25 $tokenInfos['user']=$user;
26 echo $this->_format($tokenInfos);
27 return;
28 }
29 }
30 }
31 throw new \Exception('Unauthorized',401);
32 }
33 }
Pour chaque requête avec authentification, il est possible de récupérer l’utilisateur connecté (il est ajouté ici dans les en-têtes de réponse) :
1 namespace controllers;
2
3 use Ubiquity\utils\http\URequest;
4 use Ubiquity\utils\http\USession;
5
6 /**
7 * Rest Controller RestOrgas
8 * @route("/rest/orgas","inherited"=>true,"automated"=>true)
9 * @rest("resource"=>"models\\Organization")
10 */
11 class RestOrgas extends \Ubiquity\controllers\rest\RestController {
12
13 ...
14
15 public function isValid($action){
16 $result=parent::isValid($action);
17 if($this->requireAuth($action)){
18 $key=$this->server->_getHeaderToken();
19 $user=USession::get($key);
20 $this->server->_header('active-user',$user,true);
21 }
22 return $result;
23 }
24 }
Utilisez l’interface webtools pour tester la connexion :
Personnalisation
Jetons d’api
Il est possible de personnaliser la génération de jetons, en surdéfinissant la méthode getRestServer
:
1 namespace controllers;
2
3 use Ubiquity\controllers\rest\RestServer;
4 class RestOrgas extends \Ubiquity\controllers\rest\RestController {
5
6 ...
7
8 protected function getRestServer(): RestServer {
9 $srv= new RestServer($this->config);
10 $srv->setTokenLength(32);
11 $srv->setTokenDuration(4800);
12 return $srv;
13 }
14 }
Origines autorisées et CORS
Cross-Origin Resource Sharing (CORS)
Si vous accédez à votre api depuis un autre site, il est nécessaire de configurer CORS.
Dans ce cas, pour les requêtes de type PATCH
, PUT
, DELETE
, votre api doit définir une route permettant à CORS d’effectuer son contrôle pré-requête en utilisant la méthode OPTIONS
.
1 class RestOrgas extends \Ubiquity\controllers\rest\RestController {
2
3 ...
4
5 /**
6 * @options('{url}')
7 */
8 public function options($url='') {}
9 }
Origines autorisées
Les origines autorisées permettent de définir les clients qui peuvent accéder à la ressource dans le cas d’une requête inter-domaine en définissant l’en-tête de réponse Access-Control-Allow-Origin.
Ce champ d’en-tête est renvoyé par la méthode OPTIONS
.
1 class RestOrgas extends \Ubiquity\controllers\rest\RestController {
2
3 ...
4
5 protected function getRestServer(): RestServer {
6 $srv= new RestServer($this->config);
7 $srv->setAllowedOrigin('http://mydomain/');
8 return $srv;
9 }
10 }
Il est possible d’autoriser plusieurs origines :
1 class RestOrgas extends \Ubiquity\controllers\rest\RestController {
2
3 ...
4
5 protected function getRestServer(): RestServer {
6 $srv= new RestServer($this->config);
7 $srv->setAllowedOrigins(['http://mydomain1/','http://mydomain2/']);
8 return $srv;
9 }
10 }
Réponse
Pour changer le format des réponses, il est nécessaire de créer une classe héritant de ResponseFormatter
.
Nous allons nous inspirer de HAL, et changer le format des réponses par :
ajout d’un lien vers self pour chaque ressource
ajout d’un attribut « _embedded » pour les collections
suppression de l’attribut « data » pour les ressources uniques
1 namespace controllers\rest;
2
3 use Ubiquity\controllers\rest\ResponseFormatter;
4 use Ubiquity\orm\OrmUtils;
5
6 class MyResponseFormatter extends ResponseFormatter {
7
8 public function cleanRestObject($o, &$classname = null) {
9 $pk = OrmUtils::getFirstKeyValue ( $o );
10 $r=parent::cleanRestObject($o);
11 $r["links"]=["self"=>"/rest/orgas/get/".$pk];
12 return $r;
13 }
14
15 public function getOne($datas) {
16 return $this->format ( $this->cleanRestObject ( $datas ) );
17 }
18
19 public function get($datas, $pages = null) {
20 $datas = $this->getDatas ( $datas );
21 return $this->format ( [ "_embedded" => $datas,"count" => \sizeof ( $datas ) ] );
22 }
23 }
Assignation ensuite de « MyResponseFormatter » au contrôleur REST en surchargeant la méthode « getResponseFormatter » :
1 class RestOrgas extends \Ubiquity\controllers\rest\RestController {
2
3 ...
4
5 protected function getResponseFormatter(): ResponseFormatter {
6 return new MyResponseFormatter();
7 }
8 }
Testez les résultats avec les méthodes getOne et get :
APIs
Contrairement aux ressources REST, les contrôleurs de type APIs sont multi-ressources.
SimpleRestAPI
JsonApi
Ubiquity implémente la spécification jsonApi avec la classe JsonApiRestController
.
JsonApi est utilisé par EmberJS et d’autres.
voir https://jsonapi.org/ pour en savoir plus.
Création
Avec les devtools :
Ubiquity restapi JsonApiTest -p=/jsonapi
Ou avec les webtools :
Allez dans la partie REST puis choisir Add a new resource :
Testez l’api avec les webtools :
Liens
La route links (méthode index) renvoie la liste des urls accessibles :
Récupérer un tableau d’objets
Par défaut, tous les membres associés sont inclus :
Inclusion des membres associés
vous devez utiliser le paramètre include de la requête :
URL |
Description |
---|---|
|
Aucun membre associé n’est inclus |
|
Inclusion de l’organisation |
|
Inclusion de l’organisation et des connexions |
|
Inclusion des groupes, et de leur organisation |
Filtrage d’instances
il est nécessaire d’utiliser le paramètre filter de la requête,
Le paramètre filter correspond à la partie where d’une instruction SQL :
URL |
Description |
---|---|
|
Sans filtrage |
|
Retourne tous les utilisateurs prénommés Benjamin |
|
Retourne tous les utilisateurs dont le nom commence par un B |
|
Retourne tous les utilisateurs suspendus dont le nom commence par « ca » |
Pagination
vous devez utiliser les paramètres page[number] et page[size] de la requête :
URL |
Description |
---|---|
|
Sans pagination |
|
Affiche la première page (page size vaut 1) |
|
Affiche la première page (page size vaut 10) |
Ajout d’une instance
Les données, contenues dans data[attributes]
, sont envoyées par la méthode POST, avec un type de contenu défini à application/json ; charset=utf-8
.
Ajoutez vos paramètres en cliquant sur le bouton paramètres :
L’ajout nécessite une authentification, une erreur est donc générée, avec le statut 401 si le jeton est absent ou expiré.
Suppression d’une instance
La suppression requiert la méthode DELETE, et l’utilisation de l”id de l’objet à supprimer :