Fork me on GitHub

This part will be a little long, but it will be very helpful.

5.1 Parse client params

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.

5.2 Rule configs

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', ...),
    ... ...
)

5.3 Example

(1) A simple example

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.

(2)A full example

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!

5.4 Three kinds of params

(1)System params

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

(2)Application params

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' => '', 
        ),
    ),

    ... ...

(3)API params

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.

(4) Params priority

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):

    1. HIGHEST, sepcecified API parameter rule
    1. common API parameter rule
    1. application parameter rule
    1. LOWEST, system parameter rule(only one param, i.e. service)

5.5 Online API detail documents(auto generated)

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.

Self-description data

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.

5.6 All kinds of rule types

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 section Desc in online API detail docs.

Let's study each rule type with examples one by one.

(1)Rule type: string

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',
),

(2)Rule type: int

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"}

(3)Rule type: float

See int in above.

(4)Rule type: boolean

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)

(4)Rule type: date

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').

(5)Rule type: array

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 &params={"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').

(7)Rule type: enum

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`))

(8)Rule type: file

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'

(9)Rule type: callable

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 the params index in $rule.

5.7 The principle to design parameter rule

(1) Free to map client param name with backend member name

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.

(2)Returning exception

PhalApi will return an exception, which's code is usually 4xx, while client post illegal params. This can remind client they post wrong params.

5.8 Extend your rule type

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.

    1. Extend and implement the PhalApi_Request_Formatter interface.
    1. Register the new type in DI.

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.

  • _formatterArray
  • _formatterBoolean
  • _formatterCallable
  • _formatterDate
  • _formatterEnum
  • _formatterFile
  • _formatterFloat
  • _formatterInt
  • _formatterString