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 :

app/config/services.php
...
Router::start();

Pour activer les routes REST dans une application qui a également une partie non-REST :

app/config/services.php
...
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) :

app/config/services.php
...
     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 :

../_images/addNewResource.png

Le contrôleur créé :

app/controllers/RestUsersController.php
 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 :

../_images/createdResource.png

Obtenir une instance

Une instance d’utilisateur est accessible par sa clé primaire (id) :

../_images/getOneResource.png

Inclusion des membres associés : l’organisation de l’utilisateur

../_images/getOneResourceInclude.png

Inclusion des membres associés : organisation, connexions et groupes de l’utilisateur.

../_images/getOneResourceIncludeAll.png

Obtenir plusieurs instances

Récupérer toutes les instances :

../_images/getAllOrgas.png

Définir une condition :

../_images/condition-orgas.png

Inclure les membres associés :

../_images/include-orgas.png

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 :

../_images/post-parameters.png

L’ajout nécessite une authentification, donc une erreur est générée, avec le statut 401 :

../_images/unauthorized-post.png

L’interface d’administration vous permet de simuler l’authentification par défaut et d’obtenir un jeton, en sollicitant la méthode connect :

../_images/connect.png

Le jeton est alors automatiquement envoyé dans les requêtes suivantes.
L’enregistrement peut alors être inséré.

../_images/added.png

Mise à jour d’une instance

La mise à jour suit le même schéma que l’insertion.

Suppression d’une instance

../_images/delete-instance.png

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.

app/controllers/RestOrgas.php
 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 :

../_images/custom-orgas.png

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 :

app/controllers/RestOrgas.php
...

     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 :

app/config/services.php
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.

../_images/token.png

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).

app/controllers/RestOrgas.php
 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) :

app/controllers/RestOrgas.php
 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 :

../_images/connected-user.png

Personnalisation

Jetons d’api

Il est possible de personnaliser la génération de jetons, en surdéfinissant la méthode getRestServer :

app/controllers/RestOrgas.php
 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.

app/controllers/RestOrgas.php
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.

app/controllers/RestOrgas.php
 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 :

app/controllers/RestOrgas.php
 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

app/controllers/RestOrgas.php
 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 » :

app/controllers/RestOrgas.php
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 :

../_images/getOneFormatted.png ../_images/getFormatted.png

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 :

../_images/jsonapi-creation.png

Testez l’api avec les webtools :

../_images/jsonapi-admin.png

Récupérer un tableau d’objets

Par défaut, tous les membres associés sont inclus :

../_images/getAll.png
Inclusion des membres associés

vous devez utiliser le paramètre include de la requête :

URL

Description

/jsonapi/user?include=false

Aucun membre associé n’est inclus

/jsonapi/user?include=organization

Inclusion de l’organisation

/jsonapi/user?include=organization,connections

Inclusion de l’organisation et des connexions

/jsonapi/user?include=groupes.organization

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

/jsonapi/user?1=1

Sans filtrage

/jsonapi/user?firstname='Benjamin'

Retourne tous les utilisateurs prénommés Benjamin

/jsonapi/user?filter=firstname like 'B*'

Retourne tous les utilisateurs dont le nom commence par un B

/jsonapi/user?filter=suspended=0 and lastname like 'ca*'

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

/jsonapi/user

Sans pagination

/jsonapi/user?page[number]=1

Affiche la première page (page size vaut 1)

/jsonapi/user?page[number]=1&&page[size]=10

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 :

../_images/add-parameters.png

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é.

../_images/add-response.png

Suppression d’une instance

La suppression requiert la méthode DELETE, et l’utilisation de l”id de l’objet à supprimer :

../_images/delete-response.png