Rest

The REST module implements a basic CRUD,
with an authentication system, directly testable in the administration part.

REST and routing

The router is essential to the REST module, since REST (Respresentation State Transfer) is based on URLs and HTTP methods.

Note

For performance reasons, REST routes are cached independently of other routes.
It is therefore necessary to start the router in a particular way to activate the REST routes and not to obtain a recurring 404 error.

The router is started in services.php.

Without activation of REST routes:

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

To enable REST routes in an application that also has a non-REST part:

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

To activate only Rest routes:

Router::startRest();

It is possible to start routing conditionally (this method will only be more efficient if the number of routes is large in either part):

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

Resource REST

A REST controller can be directly associated with a model.

Note

If you do not have a mysql database on hand, you can download this one: messagerie.sql

Creation

With devtools:

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

Or with webtools:

Go to the REST section and choose Add a new resource:

../_images/addNewResource.png

The created controller :

app/controllers/RestUsersController.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
     namespace controllers;

     /**
      * Rest Controller RestUsersController
      * @route("/rest/users","inherited"=>true,"automated"=>true)
      * @rest("resource"=>"models\\User")
      */
     class RestUsersController extends \Ubiquity\controllers\rest\RestController {

     }

Since the attributes automated and inherited of the route are set to true, the controller has the default routes of the parent class.

Test interface

Webtools provide an interface for querying datas:

../_images/createdResource.png

Getting an instance

A user instance can be accessed by its primary key (id):

../_images/getOneResource.png

Inclusion of associated members: the organization of the user

../_images/getOneResourceInclude.png

Inclusion of associated members: organization, connections and groups of the user

../_images/getOneResourceIncludeAll.png

Getting multiple instances

Getting all instances:

../_images/getAllOrgas.png

Setting a condition:

../_images/condition-orgas.png

Including associated members:

../_images/include-orgas.png

Adding an instance

The datas are sent by the POST method, with a content type defined at application/x-www-form-urlencoded:

Add name and domain parameters by clicking on the parameters button:

../_images/post-parameters.png

The addition requires an authentication, so an error is generated, with the status 401:

../_images/unauthorized-post.png

The administration interface allows you to simulate the default authentication and obtain a token, by requesting the connect method:

../_images/connect.png

The token is then automatically sent in the following requests.
The record can then be inserted.

../_images/added.png

Updating an instance

The update follows the same scheme as the insertion.

Deleting an instance

../_images/delete-instance.png

Authentification

Ubiquity REST implements an Oauth2 authentication with Bearer tokens.
Only methods with @authorization annotation require the authentication, these are the modification methods (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 );
             }

The connect method of a REST controller establishes the connection and returns a new token.
It is up to the developer to override this method to manage a possible authentication with login and password.

../_images/token.png

Simulation of a connection with login

In this example, the connection consists simply in sending a user variable by the post method.
If the user is provided, the connect method of $server instance returns a valid token that is stored in session (the session acts as a database here).

app/controllers/RestOrgas.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
     namespace controllers;

     use Ubiquity\utils\http\URequest;
     use Ubiquity\utils\http\USession;

     /**
      * Rest Controller RestOrgas
      * @route("/rest/orgas","inherited"=>true,"automated"=>true)
      * @rest("resource"=>"models\\Organization")
      */
     class RestOrgas extends \Ubiquity\controllers\rest\RestController {

             /**
              * This method simulate a connection.
              * Send a <b>user</b> variable with <b>POST</b> method to retreive a valid access token
              * @route("methods"=>["post"])
              */
             public function connect(){
                     if(!URequest::isCrossSite()){
                             if(URequest::isPost()){
                                     $user=URequest::post("user");
                                     if(isset($user)){
                                             $tokenInfos=$this->server->connect ();
                                             USession::set($tokenInfos['access_token'], $user);
                                             $tokenInfos['user']=$user;
                                             echo $this->_format($tokenInfos);
                                             return;
                                     }
                             }
                     }
                     throw new \Exception('Unauthorized',401);
             }
     }

For each request with authentication, it is possible to retrieve the connected user (it is added here in the response headers) :

app/controllers/RestOrgas.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
     namespace controllers;

     use Ubiquity\utils\http\URequest;
     use Ubiquity\utils\http\USession;

     /**
      * Rest Controller RestOrgas
      * @route("/rest/orgas","inherited"=>true,"automated"=>true)
      * @rest("resource"=>"models\\Organization")
      */
     class RestOrgas extends \Ubiquity\controllers\rest\RestController {

             ...

             public function isValid($action){
                     $result=parent::isValid($action);
                     if($this->requireAuth($action)){
                             $key=$this->server->_getHeaderToken();
                             $user=USession::get($key);
                             $this->server->_header('active-user',$user,true);
                     }
                     return $result;
             }
     }

Use the webtools interface to test the connection:

../_images/connected-user.png

Customizing

Api tokens

It is possible to customize the token generation, by overriding the getRestServer method:

app/controllers/RestOrgas.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
     namespace controllers;

     use Ubiquity\controllers\rest\RestServer;
     class RestOrgas extends \Ubiquity\controllers\rest\RestController {

             ...

             protected function getRestServer(): RestServer {
                     $srv= new RestServer($this->config);
                     $srv->setTokenLength(32);
                     $srv->setTokenDuration(4800);
                     return $srv;
             }
     }

Allowed origins and CORS

Cross-Origin Resource Sharing (CORS)

If you access your api from another site, it is necessary to set up CORS.

In this case, for requests of type PATCH, PUT, DELETE, your api must define a route allowing CORS to carry out its control pre-request using the OPTIONS method.

app/controllers/RestOrgas.php
1
2
3
4
5
6
7
8
9
     class RestOrgas extends \Ubiquity\controllers\rest\RestController {

             ...

             /**
              * @options('{url}')
              */
             public function options($url='') {}
     }

Allowed origins

Allowed origins allow to define the clients that can access the resource in case of a cross domain request by defining The Access-Control-Allow-Origin response header.
This header field is returned by the OPTIONS method.

app/controllers/RestOrgas.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
     class RestOrgas extends \Ubiquity\controllers\rest\RestController {

             ...

             protected function getRestServer(): RestServer {
                     $srv= new RestServer($this->config);
                     $srv->setAllowOrigin('http://mydomain/');
                     return $srv;
             }
     }

It is possible to authorize several origins:

app/controllers/RestOrgas.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
     class RestOrgas extends \Ubiquity\controllers\rest\RestController {

             ...

             protected function getRestServer(): RestServer {
                     $srv= new RestServer($this->config);
                     $srv->setAllowOrigins(['http://mydomain1/','http://mydomain2/']);
                     return $srv;
             }
     }

Response

To change the response format, it is necessary to create a class inheriting from ResponseFormatter.
We will take inspiration from HAL, and change the format of the responses by:

  • adding a link to self for each resource
  • adding an _embedded attribute for collections
  • removing the data attribute for unique resources
app/controllers/RestOrgas.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
     namespace controllers\rest;

     use Ubiquity\controllers\rest\ResponseFormatter;
     use Ubiquity\orm\OrmUtils;

     class MyResponseFormatter extends ResponseFormatter {

             public function cleanRestObject($o, &$classname = null) {
                     $pk = OrmUtils::getFirstKeyValue ( $o );
                     $r=parent::cleanRestObject($o);
                     $r["links"]=["self"=>"/rest/orgas/get/".$pk];
                     return $r;
             }

             public function getOne($datas) {
                     return $this->format ( $this->cleanRestObject ( $datas ) );
             }

             public function get($datas, $pages = null) {
                     $datas = $this->getDatas ( $datas );
                     return $this->format ( [ "_embedded" => $datas,"count" => \sizeof ( $datas ) ] );
             }
     }

Then assign MyResponseFormatter to the REST controller by overriding the getResponseFormatter method:

app/controllers/RestOrgas.php
1
2
3
4
5
6
7
8
     class RestOrgas extends \Ubiquity\controllers\rest\RestController {

             ...

             protected function getResponseFormatter(): ResponseFormatter {
                     return new MyResponseFormatter();
             }
     }

Test the results with the getOne and get methods:

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

APIs

Unlike REST resources, APIs controllers are multi-resources.

SimpleRestAPI

JsonApi

Ubiquity implements the jsonApi specification with the class JsonApiRestController.
JsonApi is used by EmberJS and others.
see https://jsonapi.org/ for more.

Creation

With devtools:

Ubiquity restapi JsonApiTest -p=/jsonapi

Or with webtools:

Go to the REST section and choose Add a new resource:

../_images/jsonapi-creation.png

Test the api in webtools:

../_images/jsonapi-admin.png

Getting an array of objects

By default, all associated members are included:

../_images/getAll.png
Including associated members

you need to use the include parameter of the request:

URL Description
/jsonapi/user?include=false No associated members are included
/jsonapi/user?include=organization Include the organization
/jsonapi/user?include=organization,connections Include the organization and the connections
/jsonapi/user?include=groupes.organization Include the groups and their organization
Filtering instances

you need to use the filter parameter of the request,
filter parameter corresponds to the where part of an SQL statement:

URL Description
/jsonapi/user?1=1 No filtering
/jsonapi/user?firstname='Benjamin' Returns all users named Benjamin
/jsonapi/user?filter=firstname like 'B*' Returns all users whose first name begins with a B
/jsonapi/user?filter=suspended=0 and lastname like 'ca*' Returns all suspended users whose lastname begins with ca

Adding an instance

The datas, contained in data[attributes], are sent by the POST method, with a content type defined at application/json; charset=utf-8.

Add your parameters by clicking on the parameters button:

../_images/add-parameters.png

The addition requires an authentication, so an error is generated, with the status 401 if the token is absent or expired.

../_images/add-response.png

Deleting an instance

Deletion requires the DELETE method, and the use of the id of the object to be deleted:

../_images/delete-response.png