Rest

El módulo REST implementa un CRUD básico,
con un sistema de autenticación, directamente comprobable en la parte de administración.

REST y enrutamiento

El enrutador es esencial para el módulo REST, ya que REST (Respresentation State Transfer) se basa en URL y métodos HTTP.

Nota

Por razones de rendimiento, las rutas REST se almacenan en caché independientemente de otras rutas.
Por lo tanto, es necesario iniciar el enrutador de una manera particular para activar las rutas REST y no obtener un error 404 recurrente.

El router se inicia en services.php.

Sin activación de rutas REST:

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

Para habilitar rutas REST en una aplicación que también tiene una parte no REST:

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

Para activar sólo las rutas Rest:

Router::startRest();

Es posible iniciar el enrutamiento condicionalmente (este método sólo será más eficiente si el número de rutas es grande en cualquiera de las partes):

app/config/services.php
...
     if($config['isRest']()){
             Router::startRest();
     }else{
             Router::start();
     }

Recursos REST

Un controlador REST puede asociarse directamente a un modelo.

Nota

Si no tiene a mano una base de datos mysql, puede descargar ésta: messagerie.sql

Creación

Con devtools:

Ubiquity rest RestUsersController -r=User -p=/rest/users

O con webtools:

Vaya a la sección REST y elija Añadir un nuevo recurso:

../_images/addNewResource.png

El controlador creado :

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     }

Dado que los atributos automated y inherited de la ruta están en true, el controlador tiene las rutas por defecto de la clase padre.

The base controller RestController is not standardized, it should be considered as an example for data interrogation.

Interfaz de prueba

Webtools ofrecen una interfaz para consultar datos:

../_images/createdResource.png

Obtener una instancia

Se puede acceder a una instancia de usuario por su clave principal (id):

../_images/getOneResource.png

Inclusión de miembros asociados: la organización del usuario

../_images/getOneResourceInclude.png

Inclusión de miembros asociados: organización, conexiones y grupos del usuario

../_images/getOneResourceIncludeAll.png

Obtener varias instancias

Obtener todas las instancias:

../_images/getAllOrgas.png

Establecer una condición:

../_images/condition-orgas.png

Incluidos los miembros asociados:

../_images/include-orgas.png

Añadir una instancia

Los datos se envían mediante el método POST, con un tipo de contenido definido en application/x-www-form-urlencoded:

Añada los parámetros de nombre y dominio pulsando el botón parámetros:

../_images/post-parameters.png

La adición requiere una autenticación, por lo que se genera un error, con el estado 401:

../_images/unauthorized-post.png

La interfaz de administración permite simular la autenticación por defecto y obtener un token, solicitando el método connect:

../_images/connect.png

El token se envía automáticamente en las siguientes solicitudes.
A continuación, se puede insertar el registro.

../_images/added.png

Actualización de una instancia

La actualización sigue el mismo esquema que la inserción.

Borrar una instancia

../_images/delete-instance.png

Personalización

Rutas

Por supuesto, es posible personalizar y simplificar las rutas.
En este caso, es preferible utilizar la herencia de la clase RestBaseController, y no habilitar las rutas automáticas.

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}

Tras reinicializar la caché, la interfaz de prueba muestra las rutas accesibles:

../_images/custom-orgas.png

Modificación de los datos enviados

By overriding

Es posible modificar los datos enviados a los métodos update y add, para añadir, modificar o borrar el valor de los campos antes de enviarlos.
Ya sea sobredefiniendo el método getDatas:

app/controllers/RestOrgas.php
...

     protected function getDatas() {
             $datas = parent::getDatas();
             unset($datas['aliases']);// Remove aliases field
             return $datas;
     }

Con eventos

O bien de forma más global actuando sobre los eventos de descanso:

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
});

Autentificación

Ubiquity REST implementa una autenticación Oauth2 con tokens Bearer. |br|Sólo los métodos con la anotación ``@authorization`` requieren la autenticación, estos son los métodos de modificación (añadir, actualizar y eliminar). |br|

             /**
              * 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 );
             }

El método connect de un controlador REST establece la conexión y devuelve un nuevo token.
Corresponde al desarrollador anular este método para gestionar una posible autenticación con nombre de usuario y contraseña.

../_images/token.png

Simulación de una conexión con inicio de sesión

En este ejemplo, la conexión consiste simplemente en enviar una variable de usuario por el método post.
Si se proporciona el usuario, el método connect de la instancia $server devuelve un token válido que se almacena en sesión (la sesión actúa aquí como base de datos).

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     }

Para cada solicitud con autenticación, es posible recuperar el usuario conectado (se añade aquí en las cabeceras de respuesta) :

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     }

Utilice la interfaz webtools para probar la conexión:

../_images/connected-user.png

Personalización

Api tokens

Es posible personalizar la generación de tokens anulando el método 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     }

Orígenes y CORS permitidos

Compartición de recursos entre orígenes (CORS)

Si accede a su api desde otro sitio, es necesario configurar CORS.

En este caso, para peticiones de tipo PATCH, PUT, DELETE, tu api debe definir una ruta que permita a CORS realizar su control pre-petición mediante el método 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     }

Orígenes permitidos

Los orígenes permitidos permiten definir los clientes que pueden acceder al recurso en caso de una petición entre dominios definiendo la cabecera de respuesta Access-Control-Allow-Origin.
Este campo de cabecera es devuelto por el método 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     }

Es posible autorizar varios orígenes:

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     }

Respuesta

Para cambiar el formato de las respuestas, es necesario crear una clase que herede de ResponseFormatter.
Nos inspiraremos en HAL, y cambiaremos el formato de las respuestas por:

  • añadir un enlace a sí mismo para cada recurso

  • añadir un atributo _embedded para las colecciones

  • eliminación del atributo data para los recursos únicos

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     }

A continuación, asigna MyResponseFormatter al controlador REST anulando el método 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     }

Comprueba los resultados con los métodos getOne y get:

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

APIs

A diferencia de los recursos REST, los controladores API son multirrecursos.

SimpleRestAPI

JsonApi

Ubiquity implementa la especificación jsonApi con la clase JsonApiRestController.
JsonApi es utilizado por ``EmberJS <https://api.emberjs.com/ember-data/release/classes/DS.JSONAPIAdapter>`_and otros.
ver https://jsonapi.org/ para más.

Creación

Con devtools:

Ubiquity restapi JsonApiTest -p=/jsonapi

O con webtools:

Vaya a la sección REST y elija Añadir un nuevo recurso:

../_images/jsonapi-creation.png

Prueba la api en webtools:

../_images/jsonapi-admin.png

Obtener una matriz de objetos

Por defecto, se incluyen todos los miembros asociados:

../_images/getAll.png
Incluidos los miembros asociados

debe utilizar el parámetro include de la solicitud:

URL

Descripción

/jsonapi/user?include=false

No se incluyen miembros asociados

/jsonapi/user?include=organization

Incluir la organización

/jsonapi/user?include=organization,connections

Incluir la organización y las conexiones

/jsonapi/user?include=groupes.organization

Incluir los grupos y su organización

Filtrado de instancias

debe utilizar el parámetro filter de la solicitud,
el parámetro filter corresponde a la parte where de una sentencia SQL:

URL

Descripción

/jsonapi/user?1=1

Sin filtro

/jsonapi/user?firstname='Benjamin'

Devuelve todos los usuarios llamados Benjamin

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

Devuelve todos los usuarios cuyo nombre empieza por B

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

Devuelve todos los usuarios suspendidos cuyo apellido empiece por ca

Paginación

debe utilizar los parámetros page[number] y page[size] de la solicitud:

URL

Descripción

/jsonapi/user

Sin paginación

/jsonapi/user?page[number]=1

Mostrar la primera página (el tamaño de página es 1)

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

Mostrar la primera página (el tamaño de página es 10)

Añadir una instancia

Los datos, contenidos en data[attributes], se envían mediante el método POST, con un tipo de contenido definido en application/json; charset=utf-8.

Añada sus parámetros haciendo clic en el botón parameters:

../_images/add-parameters.png

La adición requiere una autenticación, por lo que se genera un error, con el estado 401 si el token está ausente o caducado.

../_images/add-response.png

Borrar una instancia

El borrado requiere el método DELETE, y el uso del id del objeto a borrar:

../_images/delete-response.png