Creating a ReST API¶
This section covers how to make a ReST API from scratch and consume it in any App (Web or Mobile).
If you're not familiar with ReST concepts, see this great explanation.
Prerequisites¶
First, you'll need a working setup of equal-framework on your localhost. See Getting started > Quick start (skip point 3. Defining classes)
If you look at the Directory structure, you'll get a good idea of what comes with every project. In this one, we'll use actions, classes, and data.
Each of those folders correspond to a specific role :
- /actions = method DO (post, put/patch, delete, ...)
- /data = method GET (fetching data from server)
- /classes = class definition & DB architecture
Go ahead and create them in your package directory (ex: /packages/todolist/)
Defining classes¶
Inside folder /classes
create a new PHP file for each class you want to declare.
To continue with our previous todolist-app example, we'll create two classes: Task and User.
/classes/Task.class.php¶
<?php
namespace todolist;
use equal\orm\Model;
class Task extends Model {
public static function getColumns() {
return [
'title' => [
'type' => 'string'
],
'content' => [
'type' => 'string'
],
'deadline' => [
'type' => 'datetime'
],
'user_id' => [
'type' => 'many2one',
'foreign_object' => 'todolist\User'
]
];
}
}
/classes/User.class.php¶
<?php
namespace todolist;
class User extends \core\User {
public static function getColumns() {
return [
'name' => [
'type' => 'string'
],
'tasks_ids' => [
'type' => 'one2many',
'foreign_object' => 'todolist\Task',
'foreign_field' => 'user_id'
]
];
}
}
We're done! A few things to consider :
-
In order to work, your parameters have to match with your front-end
-
Objects'IDs aren't needed here, they're automatically generated by eQual
-
As you can see we're using a DBMS relationship, linking user_id with tasks_ids. This is also handled by the framework, all you have to know is the definition syntax (see Basics > Objects parameters)
Defining GET¶
Open /data and create a new file for each GET method you want to use.
In the following example, we're asking our database to retrieve all tasks :
/data/tasks.php¶
<?php
use todolist\Task;
list($params, $providers) = announce([
'description' => 'Retrieve the list of existing tasks',
'params' => [],
'response' => [
'content-type' => 'application/json',
'charset' => 'utf-8',
'accept-origin' => ['*']
],
'providers' => ['context']
]);
list($context) = [ $providers['context'] ];
$list = Task::search([])
->read(['id', 'title', 'content', 'deadline', 'user_id' => ['id', 'name']])
->adapt('txt')
->get(true);
$context->httpResponse()
->body($list)
->send();
What it does :¶
announce() will handle the values of our query :
- description
- params gives additional requirements and conditions
- response defines the format of the server response
- providers helps us to access useful services such as context, orm, auth
We use list($context) = [$providers['context']]
to implement the services we want to use
Then $list is where we receive the data from our query :
- Task::search([]) searchs for data associated with Task
Function defined inside the Collection class
-
->read([]) tells which parameters we want to retrieve from Task
-
->adapt('txt') turns the data into strings
-
->get(true)
Finally, $context is used to accomplish REST's purpose, displaying the data on our browser as JSON
<?php
$context
->httpResponse()// get the HTTP response being built
->body($list) // populate the body with resulting list
->send(); // output the response (i.e. some plain
// text @see https://www.w3.org/Protocols/rfc2616)
Using GET in your browser¶
We'll assume we already have an existing database containing a data sample of tasks, and have already done the following :
$> php run.php --do=test_package-consistency --package=todolist
$> php run.php --do=init_package --package=todolist
Open your browser, and in the localhost page you defined for eQual
To the address bar, add this :
?get=todolist_tasks
tasks refers to our tasks.php located in /data
It will display a JSON formatted answer showing an array of tasks
As easy as that. You now have a REST response that you can use in any frontend project
Defining DO¶
in /actions folder, create a sub-folder for each previously defined class, as follow :
/public
/packages
/todolist
/actions
/task
/user
...
Now let's define the CREATE function of our API:
/actions/task/create.php¶
<?php
use todolist\Task;
use todolist\User;
list($params, $providers) = announce([
'params' => [
'title' => [
'type' => 'string',
'required' => true
],
'content' => [
'type' => 'string',
'required' => true
],
'deadline' => [
'type' => 'datetime',
'default' => date("Y-m-d H:i:s")
],
'user_id' => [
'type' => 'integer',
'required' => true
]
],
'response' => [
'content-type' => 'application/json',
'charset' => 'utf-8',
'accept-origin' => ['http://localhost:4200', '*']
],
'providers' => ['context']
]);
list($context) = [$providers['context']];
$user = User::id($params['user_id'])->read(['id'])->first();
if(is_null($user)) {
throw new Exception('user_not_found', EQ_ERROR_UNKNOWN_OBJECT);
}
$task = Task::create($params)
->read(['id', 'title', 'content', 'deadline', 'user_id' => ['id', 'name']])
->first(true);
$context->httpResponse()
->status(201)
->body($task)
->send();
What it does :¶
params defines the properties of our class Task (title, content, deadline, user_id). Some of them are declared with a required field which will make them mandatory (or not if "false", which is the default behavior)
$user is used to retrieve the ID of a single user, based on the field user_id
$task is then used with the ::create() method to initiate a POST request
Finally, we use $context to send it and get a REST response
Using DO in your browser¶
DO : CREATE
http://equal.local/index.php?do=todolist_task_create&title=my+task
&content=lorem+ipsum&user_id=1
eQual resolves the operation ?do=todolist_task_create
to the script /todolist/actions/task/create.php
After CREATE, let's implement PUT and DELETE.
We'll do that with no further explanation as you should now be familiar with how it works
/actions/task/update.php¶
<?php
use todolist\Task;
use todolist\User;
list($params, $providers) = announce([
'params' => [
'id' => [
'type' => 'integer',
'required' => true
],
'title' => [
'type' => 'string',
'required' => true
],
'content' => [
'type' => 'string',
'required' => true
],
'deadline' => [
'type' => 'datetime',
'default' => date("Y-m-d H:i:s")
],
'user_id' => [
'type' => 'integer',
'required' => true
]
],
'response' => [
'content-type' => 'application/json',
'charset' => 'utf-8',
'accept-origin' => ['http://localhost:4200', '*']
],
'providers' => ['context']
]);
list($context) = [$providers['context']];
$user = User::id($params['user_id'])->read(['id'])->first();
if(is_null($user)) {
throw new Exception('user_not_found', EQ_ERROR_UNKNOWN_OBJECT);
}
$task = Task::ids($params['id'])
->update($params)
->read(['id', 'title', 'content', 'deadline', 'user_id' => ['id', 'name']])
->first(true);
$context->httpResponse()
->status(201)
->body($task)
->send();
In practice :
DO : PUT/PATCH
http://equal.local/index.php?do=todolist_task_update&id=1
- id refers to the task we update
- Additional parameters can be used, like title, content, dealine and user_id
/actions/task/delete.php¶
<?php
use todolist\Task;
use todolist\User;
list($params, $providers) = announce([
'params' => [
'id' => [
'type' => 'integer',
'required' => true
]
],
'response' => [
'content-type' => 'application/json',
'charset' => 'utf-8',
'accept-origin' => ['http://localhost:4200', '*']
],
'providers' => ['context']
]);
list($context) = [$providers['context']];
Task::ids($params['id'])->delete(true);
$context->httpResponse()
->status(204)
->send();
In practice :
DO : DELETE
http://equal.local/index.php?do=todolist_task_delete&id=1
id= refers to the task we delete
Mapping the ReST API routes¶
Back to the root folder of equal installation, create a file: /config/routing/20-api_todolist.json
Replace everything with this :
{
"/tasks": {
"GET": {
"description": "get all tasks",
"operation": "?get=todolist_tasks"
}
},
"/task": {
"POST": {
"description": "get all tasks",
"operation": "?do=todolist_task_create"
}
},
"/task/:id" : {
"PUT": {
"description": "update a task",
"operation": "?do=todolist_task_update"
},
"DELETE": {
"description": "delete a task",
"operation": "?do=todolist_task_delete"
}
}
}
What it does is pretty self-explanatory. The /:id is a way for us to target and retrieve a single task when needed.
From now on, route http://equal.local/tasks
is equivalent to calling http://equal.local/?get=todolist_tasks
.