This part will be a little long, but it will be very helpful.
Parameter is important for backend APIs but also for client request.
That's why we hope to simplify backend APIs development in getting client params, checking params, verifying params and writting API docs. On the other hand, client can post params with more freedom.
So in PhalApi, we introuce the concept of parsing client params, i.e. auto getting and verifying client params by rule configs.
What developers need to do is just writting rule config, and PhalApi will do the rest automatically. Let's explore more detail.
Developers who are from Yii should by familiar with rule configs. Those who have not used Yii before can handle thess configs in a short time. With time moving on, you can find these configs follow the format as below.
array(
'backend param name' => array('name' => 'API param name', 'type' => 'param type', 'default' => 'param default value', ...),
... ...
)
Let's take login API as example. Assume that we need to provide a login API service, which will allow user to login by providing his/her username and password.
<?php
class Api_User extends PhalApi_Api
{
public function getRules()
{
return array(
'login' => array(
'username' => array('name' => 'username'),
'password' => array('name' => 'password'),
),
);
}
public function login()
{
return array('username' => $this->username, 'password' => $this->password);
}
}
Then we can request it like this:
/?service=User.Login&username=test&password=123456
and it will reponse:
{"ret":0,"data":{"username":"test","password":"123456"},"msg":""}
All rule configs should located at getRules()
method. In this case, there are tow params for login
service. Then develop can get the client param by class member property, such as $this->username
, $this->password
.
We just only define tow simple params. However, we need to do more other things before we get the param from client, such as verifying whether required or not, max length, min/max value limitiation etc.
Let's continue to complete the rule configs above. The params username
and password
should be required, and the min length of password
should be greater than 6.
'login' => array(
'username' => array('name' => 'username', 'require' => true),
'password' => array('name' => 'password', 'require' => true, 'min' => 6),
),
Try to request again without any params. When we request /?service=User.Login
, we will get:
{"ret":400,"data":[],"msg":"Illegal Param: wrong param: username"}
When we request with wrong length of password like /?service=User.Login&username=test&password=123
, we will get:
{"ret":400,"data":[],"msg":"Illegal Param: password.len should >= 6, but now password.len = 3"}
Look! PhalApi do many things automatically, what we need to do is just a little config and then enjoy it!
Currently, there is only one system param, i.e. service
. The param service
is a string, which looks like XXX.XXX
, determining which API service client will call.
Here are some examples.
# Recommend example
/?service=User.GetBaseInfo
# Right example(start with lower case letter)
/?service=user.getBaseInfo(method name is all lower case letter. NOTE: class name just can start with lower case letter, the rest should keep the same with class file name, or lead to a NOT FOUND exception)
/?service=user.getbaseinfo
# Illegal service(missing method name)
/?service=User
# Illegal service(missing dot between class name and method name)
/?service=UserGetBaseInfo
# Illegal service(only support split with dot now)
/?service=User|GetBaseInfo
Application params are common client params in one API project. All thess application params should be located at apiCommonRules
in the config file ./Config/app.php
. For example, if we need a required sign
param, and a NOT required version
param for all APIS, we can edit the apiCommonRules
and add the code as below.
//$vim ./Config/app.php
<?php
return array(
/**
* API common param rules
*/
'apiCommonRules' => array(
// a required ```sign``` param
'sign' => array(
'name' => 'sign', 'require' => true,
),
// a NOT required ```version``` param
'version' => array(
'name' => 'version', 'default' => '',
),
),
... ...
At last, we can define sepecified client params for each API service. In addition, in order to reuse rule configs in the same API class, we can use *
rules, which are common rules only in one API class. For example, we add a reqiured code
param, which's length is 4, for all User APIs.
public function getRules()
{
return array(
'*' => array(
'code' => array('name' => 'code', 'require' => true, 'min' => 4, 'max' => 4),
),
'login' => array(
'username' => array('name' => 'username', 'require' => true),
'password' => array('name' => 'password', 'require' => true, 'min' => 6),
),
);
}
After we configure application common params, API common params and sepcecified API params, client can request the login
API service as below.
/?service=User.login&sign=77f81c17d512302383e5f26b99dae4d9&username=test&password=123456&version=1.3.6&code=abcd
NOTE: The indexes of rule configs in API class are case-insensitive.
In summary, there are tow kinds of params in API class, i.e. API common params and sepcecified API params. The former is represent with *
, while the latter is represent with the name of method.
When one rule config for the same param repeat in application params, API common params and sepcecified API params, the latter rule will cover the former one. That's to say, sepcecified API params rule will take replace of common params rule to ensure developers can custom their rules by need.
In short, the priority of parameter rule is as below(as you expect):
service
) For the sake of convenience to check all the client params fast, PhalApi provides online API detail documents. They are auto generated by the parameter rules above. Vist the follow URI in the bowser:
/demo/checkApiParams.php?service=Default.Index
And you can see:
Thess API docs can be also provided to client developers during project development.
It is worth to mention that the parameter rules we define actually are self-description data. It means the code of rules reflect related properties of params.
Param Type(type) | IS Required(require) | Defaul Value(default) | Min&Max Value(min&max) | MORE |
---|---|---|---|---|
string | true/false, default is false | should be a string | Optional | regex index defines the regex need to be matched, and format index defines charaset, such as utf8, gbk, gb2312 etc. |
int | true/false, default is false | should be a integer | Optional | --- |
float | true/false, default is false | should be a float number | Optional | --- |
boolean | true/false, default is false | true/false | --- | It will treat ok, true, success, on, yes, 1 as TRUE |
date | true/false, default is false | translate with the format | Optional, only need to check while the format is timestamp |It will turns into timestamp while the format is timestamp |
|
array | true/false, default is false | should be an array, or try to trun into an array if NOT | Optional, check array count | It will call explode() with format separator if format is explode , and will call json_decode() if format is json |
enum | true/false, default is false | should inside the range |---|Required,use range range to sepecify enum range |
||
file | true/false, default is false | should be an array | min nad max are represent the size of upload file|range index is represent for the types of upload file allowing to post, while ext will filter the external name |
|
callable | true/false, default is false | --- | --- | callback can set callback function with params as the third param, the first is param value, the second is current rule config |
NOTE: You can add
desc
index for all of the rule types, which will be displayed in the sectionDesc
in online API detail docs.
Let's study each rule type with examples one by one.
When a rule config is defined without type
, it will be string
in default. A full config for string type is:
array('name' => 'username', 'type' => 'string', 'require' => true, 'default' => 'nobody', 'min' => 1, 'max' => 10)
We use 'type' => 'string'
to sepecify username is a string.
Then if anyone post a too much long username, such as &username=alonglonglonglongname
, he will get an exception as returned.
{"ret":400,"data":[],"msg":"Illegal Param: username.len should <= 10, but now username.len = 21"}
It there will be some special(other charset) in username, we can add format
index to sepecify which charset we use.
array('name' => 'username', 'type' => 'string','format' => 'utf8', 'min' => 1, 'max' => 10)
We can add more powerful verification with regex
index. Take email for example.
'email' => array(
'name' => 'email',
'require' => true,
'min' => '1',
'regex' => "/^([0-9A-Za-z\\-_\\.]+)@([0-9a-z]+\\.[a-z]{2,3}(\\.[a-z]{2})?)$/i",
'desc' => 'You should post a correct email address',
),
Usually, we can configure id
param for database tables as below.
array('name' => 'id', 'type' => 'int', 'require' => true, 'min' => 1 )
NOTE:
id
should be in [min
,max
).
If client post an id
beyond the range, such as &id=0
, it will fail.
{"ret":400,"data":[],"msg":"Illegal Param: id should >= 1, but now id = 0"}
See int
in above.
PhalApi will treat strings like ok, true, success, on, yes
as boolean TRUE, any other strings PHP will parse as TRUE will also turn into TRUE. For example, we can add one more param for login
service.
array('name' => 'isRememberMe', 'type' => 'boolean', 'default' => true)
We can post a date as we want.
array('name' => 'registerData', 'type' => 'date')
When client post &risterData=2015-01-31 10:00:00
, we will get 2015-01-31 10:00:00
in API.
If we want a timestamp instead of a string, we can configure as below:
array('name' => 'registerData', 'type' => 'date', 'format' => 'timestamp')
At last, we will get 1422669600, which is equals to strtotime('2015-01-31 10:00:00')
.
Usually, client need to post serveral items in one request. That's why we need array
type. For example:
array('name' => 'uids', 'type' => 'array', 'format' => 'explode', 'separator' => ',')
Then params &uids=1,2,3
will be turned into:
array ( 0 => '1', 1 => '2', 2 => '3', )
If client want to post params in JSON, we need to set format
index with json
.
array('name' => 'params', 'type' => 'array', 'format' => 'json')
Then params ¶ms={"username":"test","password":"123456"}
will be turned into:
array ( 'username' => 'test', 'password' => '123456', )
Please note that, if we use array type but without format, it will be turned into an array, which contains one element. For example, &name=test
will become array(0 => 'test')
.
You can use this enum type when you need to limit the range of parameters. Take sex for example.
array('name' => 'sex', 'type' => 'enum', 'range' => array('female', 'male'))
We only allow female/male for sex
param. Therefore, any other strings will be treated as illegal param. Let's try to post &sex=unknow
. No doubt that we will get something like this.
{"ret":400,"data":[],"msg":"Illegal Param: sex should be in female\/male, but now sex = unknow"}
Please keep in mind, it's better to use string, NOT int/float, for range
.
Because in generally, what we get from GET/POST method are strings, not int/float, otherwise will lead to unexpected result. Here is an example show how wrong range occur a bug.
// Assume client post &type=N
// rule config as below
array('name' => 'type', 'type' => 'enum', 'range' => array(0, 1, 2))
// wrong result
var_dump(in_array('N', array(0, 1, 2))); // it prints true, since 'N' == 0 in PHP
To avoid this kind bug, we should configure as below.
// rule config (use STRING, not integer)
array('name' => '&type', 'type' => 'enum', 'range' => array(`0`, `1`, `2`))
When we want to receive files from client, we can use type file
. Let's take an exmaple.
array(
'name' => 'upfile',
'type' => 'file',
'min' => 0,
'max' => 1024 * 1024,
'range' => array('image/jpeg', 'image/png') ,
'ext' => array('jpeg', 'png')
)
These config means that, the size of file should be greater than 0B, and less than 1MB, client only can post jpeg/png files in form filed upfile
, and the external file name should be jpeg/png. If client upload successfully, we will get $_FILES["upfile"]
.
array(
'name' => '',
'type' => '',
'size' => '',
'tmp_name' => '',
)
More information about $_FILE
, please visit $_FILE.
There are some more examples about ext
.
// single external name in array
'ext' => array('jpg')
// single external name in string
'ext' => 'jpg'
// multi external names in array
'ext' => array('jpg', 'jpeg', 'png', 'bmp')
// multi external names in string, split with comma
'ext' => 'jpg,jpeg,png,bmp'
If we want to verify and parse the client params by ourself, we can use type callable
. Let's try to check the version param with Common_MyVersion::formatVersion()
.
array('name' => 'version', 'type' => 'callable', 'callback' => array('Common_MyVersion', 'formatVersion'))
Then PhalApi will call Common_MyVersion::formatVersion()
client post a version. And we can do anything in Common_MyVersion::formatVersion()
as we want.
// create an new class
class Common_MyVersion {
public static function formatVersion($value, $rule) {
if (count(explode('.', $value)) < 3) {
throw new PhalApi_Exception_BadRequest('Illegal version');
}
}
}
NOTE: the first param
$value
is the origin value from client param, the second param$rule
is our rule config as above, the third param(OPTIONAL) equals to theparams
index in$rule
.
In some case, backend would like to name variable by Camel-Case, while client would prefer name parameters with underline. That's why we need tow name in rule config, one is for backend, anther one is for client.
array(
'isRememberMe' => array('name' => 'is_remember_me', 'type' => 'boolean', 'default' => true),
)
Here, client post with &is_remember_me=1
, and backend get with $this->isRememberMe
. Perfect cooporation between backend and frontend? AhA~ I think so.
We can also make a shorter name for client to save network traffic.
array(
'isRememberMe' => array('name' => 're', 'type' => 'boolean', 'default' => true),
)
Then client can post with &re=1
instead.
PhalApi will return an exception, which's code is usually 4xx
, while client post illegal params. This can remind client they post wrong params.
When PhalApi provides parameter rules that do not meet your need, you can extend the PhalApi_Request_Formatter
interface to customize your own type, in addition to using the callable
type.
There are tow more step before you use your own type.
PhalApi_Request_Formatter
interface.Let's take email
type for example.
Firstly, we need to implement interface PhalApi_Request_Formatter
.
<?php
class Common_MyFormatter_Email implements PhalApi_Request_Formatter {
public function parse($value, $rule) {
if (!preg_match('/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/', $value)) {
throw new PhalApi_Exception_BadRequest('Illegal email');
}
return $value;
}
}
Secondly, register it into DI.
DI()->_formatterEmail = 'Common_MyFormatter_Email';
NOTE: The name of service should follow
DI()->_formatter + type name(first letter is upper case, the rest is lower case)
.
At last, we can use our new type email
now as below.
array('name' => 'user_email', 'type' => 'email')
There are all the formatting service by now.