TooBasic: Services
Service?
Think about a controller, but smaller and simpler, some king of API. A service provides a way to request information or trigger a task through a controller, but avoiding complex rendering stuff and adding a few things that my be of help.
In TooBasic, a service is a small tool that always gives an answer in JSON format with a standard structure, something like this:
{
"status": true,
"transaction": false,
"data": {
"service": "login",
"name": "login"
},
"error": false,
"errors": []
}
Or this when there's an error:
{
"status": false,
"transaction": false,
"data": [],
"error": {
"code": "400",
"message": "Parameter 'id' is not set (GET)",
"location": {
"method": "TooBasic\\Exporter::checkParams()",
"file": "/var/www/mysite/includes/Exporter.php",
"line": 186
}
},
"errors": [
{
"code": "400",
"message": "Parameter 'id' is not set (GET)",
"location": {
"method": "TooBasic\\Exporter::checkParams()",
"file": "/var/www/mysite/includes/Exporter.php",
"line": 186
}
}
]
}
Let's use an example
Let's think you have a site that handles users and it must provide a way to log-in from anywhere without using a web page, say a cell phone application or another site with REST access. When you provide the right username and password, you obtain a token you can use later on every request.
Creating a service
Let's create a service to attend this matter by writing the next code and saving it at ROOTDIR/site/services/login.php:
<?php
class LoginService extends \TooBasic\Service {
protected function basicRun() {
$out = true;
if($this->params->server->REQUEST_METHOD != "POST") {
$this->setError(HTTPERROR_BAD_REQUEST, "Method '{$this->params->server->REQUEST_METHOD}' not supported");
$out = false;
} else {
$username = $this->params->post->username;
$password = $this->params->post->password;
if($this->model->users->auth($username, $password)) {
$this->assign("token", $this->model->users->genToken($username));
}
$this->_headers["Access-Control-Allow-Origin"] = "*";
}
return $out;
}
protected function init() {
parent::init();
$this->_requiredParams["POST"][] = "username";
$this->_requiredParams["POST"][] = "password";
}
}
Possible answer:
{
"status": true,
"transaction": false,
"data": {
"service": "login",
"name": "login",
"token": "bb7aa3e54918aa5f5aec3f7898bd23c9"
},
"error": false,
"errors": []
}
Let's explain how this works:
- As you've probably guessed already, we are indicating that post parameters
usernameandpasswordare required. - We're making sure this service is been called as a
POSTrequest.- Otherwise, we trigger an error.
- If it's
POSTwe validate the user and generate a token for it. - We also set a header called
Access-Control-Allow-Originto avoid CORS. This may not be polite, but it's ok for our examples. Nonetheless, if you want the polite what, you can read more about it in the section CORS.
Simpler
There is a way to make this more simpler and avoid one of our controls automatically:
<?php
class LoginService extends \TooBasic\Service {
protected function runPOST() {
$out = true;
$username = $this->params->post->username;
$password = $this->params->post->password;
if($this->model->users->auth($username, $password)) {
$this->assign("token", $this->model->users->genToken($username));
} else {
$this->setError(HTTPERROR_BAD_REQUEST, "Invalid username or password");
}
$this->_headers["Access-Control-Allow-Origin"] = "*";
return $out;
}
protected function init() {
parent::init();
$this->_requiredParams["POST"][] = "username";
$this->_requiredParams["POST"][] = "password";
}
}
In this way you're forcing your service to work only for POST requests.
May I?
Yes, you may replace the common method basicRun() for runGET() and in most
cases it'll be ok.
Interfaces
Services in TooBasic provide a way to know what is required to call certain service in the right manner. If you call your service in this way:
You may obtain something like this:
{
"status": true,
"interface": {
"name": "login",
"cached": false,
"methods": [
"POST"
],
"required_params": {
"POST": [
"username",
"password"
]
},
"cache_params": {
"GET": [
"mode"
],
"POST": []
},
"CORS": {
"headers": [
"Accept",
"Content-Type"
],
"methods": [
"POST",
"OPTIONS"
],
"origins": []
}
},
"error": false,
"errors": []
}
Here you may find the right request method and all the require parameters.
Also you may call this URL to obtain a full list of services and their interfaces.
CORS
When you are developing services and trying to provide them as API, one of the first problems you'll find is CORS. If you want to really understand it you may follow this link, but to make it simple, let's make an example.
Let's say you created a service at http://example.com and then a page at http://otherexample.com. In this last one you have a JavaScript that wants to use the service from the first page. Everything seems nice, but your browser will fail and won't let you access it due to CORS issues. The reason is that you can only use JavaScript to access remote servers when the page your are visiting has the same server name than the remote one (and schema and port) or when the remote server allows you to do so.
This seems somehow problematic but it's a security policy enforced by browsers and there's not much to do except working in compliance with such policy. Is there a work around? well yes, you can always create a proxy and access trough it, but TooBasic provides a more polite way to do this.
Allowing sites
The first thing you'll want to configure is which sites are allowed to access your services and there are three way to achieve it.
The first one is to allow sites inside each service writing something like this:
<?php class LoginService extends \TooBasic\Service { . . . protected function init() { parent::init(); . . . $this->_corsAllowOrigin[] = 'http://otherexample.com'; } }This setting will allow any page in http://otherexample.com to access your login service using JavaScript.
The second way and the most generic is to add a configuration like this one:
$Defaults[GC_DEFAULTS_SERVICE_ALLOWEDSITES][] = 'http://otherexample.com';This mechanism allows any page in http://otherexample.com to access any service in your site.
If you take a closer look, the first way implies code modification every time you need to add/remove/update a site, while the second one affects all services at once. For this reason there's a third way that is in the middle of the other two. If you want to make only your login service available at any page in http://otherexample.com without changing the code, you may add this configuration:
$Defaults[GC_DEFAULTS_SERVICE_ALLOWEDBYSRV]['login'] = [ 'http://otherexample.com' ];
Methods
By default, TooBasic tries to guess what methods your are allowing, but if you what to provide some in particular, you can add this to your service:
<?php
class LoginService extends \TooBasic\Service {
. . .
protected function init() {
parent::init();
. . .
$this->_corsAllowMethods[] = 'PUT';
$this->_corsAllowMethods[] = 'POST';
}
}
Note: Method OPTIONS is always present.
Headers
If you need to make use of some specific headers you may specify them as allowed headers writing something like this:
<?php
class LoginService extends \TooBasic\Service {
. . .
protected function init() {
parent::init();
. . .
$this->_corsAllowHeaders[] = 'Authorization';
}
}
Transaction Tracking
In any service call you can specify a parameter called transaction with whatever
value you prefer and it will be returned as a first level parameter with the same
value.
This may seem pointless but it could be useful to track your current transaction
when you are running multiple calls to the same service.