天天看點

5.接口參數過濾(phalapi架構總結)

在接口類檔案裡,可以設定某個接口方法的請求參數相關過濾條件,然後直接用$this調用請求參數,如下:

<?php
namespace App\Api\Home;

use PhalApi\Api;

/**
 * 雜項
 */
class SiteInfo extends Common {
    public function getRules() {
        return array(
            'advice' => array(
                'content' => array('name' => 'content', 'require' => true, 'type' => 'string','des'=>'回報内容'),
                'contact' => array('name' => 'contact', 'require' => true, 'type' => 'string','des'=>'聯系電話'),
                'imageList' => array('name' => 'imageList', 'require' => false, 'type' => 'string','des'=>'圖檔'),
            )
        );
    }
           

SiteInfo的advice接口有三個請求參數,其中content和contact都是必傳的.由于所有的接口類都繼承Api是以,我們去看一個請求來時,是怎麼在運作請求方法時,先過濾相關參數是否合法.

PhalApi\Api類的初始化方法如下:

/**
     * 初始化
     *
     * 主要完成的初始化工作有:
     * - 1、[必須]按參數規則解析生成接口參數
     * - 2、[可選]過濾器調用,如:簽名驗證
     * - 3、[可選]使用者身份驗證
     * 
     * @uses Api::createMemberValue()
     * @uses Api::filterCheck()
     * @uses Api::userCheck()
     * @return null
     */
    public function init() {
        $this->createMemberValue();

        $this->filterCheck();

        $this->userCheck();
    }
    第一步$this->createMemberValue();就是對請求參數進行檢查,我們看函數内容:
    /**
     * 按參數規則解析生成接口參數
     *
     * 根據配置的參數規則,解析過濾,并将接口參數存放于類成員變量
     * 
     * @uses Api::getApiRules()
     */
    protected function createMemberValue() {
        foreach ($this->getApiRules() as $key => $rule) {
            $this->$key = DI()->request->getByRule($rule);
        }
    }
    /**
     * 取接口參數規則
     *
     * 主要包括有:
     * - 1、[固定]系統級的service參數
     * - 2、應用級統一接口參數規則,在app.apiCommonRules中配置
     * - 3、接口級通常參數規則,在子類的*中配置
     * - 4、接口級目前操作參數規則
     *
     * <b>當規則有沖突時,以後面為準。另外,被請求的函數名和配置的下标都轉成小寫再進行比對。</b>
     *
     * @uses Api::getRules()
     * @return array
     */
    public function getApiRules() {
        $rules = array();

        $allRules = $this->getRules();
        if (!is_array($allRules)) {
            $allRules = array();
        }
        $allRules = array_change_key_case($allRules, CASE_LOWER);

        $action = strtolower(DI()->request->getServiceAction()); 
        if (isset($allRules[$action]) && is_array($allRules[$action])) {
            $rules = $allRules[$action];
        }

        if (isset($allRules['*'])) {
            $rules = array_merge($allRules['*'], $rules);
        }

        $apiCommonRules = DI()->config->get('app.apiCommonRules', array());
        if (!empty($apiCommonRules) && is_array($apiCommonRules)) {
            // fixed issue #22
            if ($this->isServiceWhitelist()) {
                foreach ($apiCommonRules as &$ruleRef) {
                    $ruleRef['require'] = false;
                }
            }

            $rules = array_merge($apiCommonRules, $rules);
        }

        return $rules;
    }
getApiRules方法主要是做了下面幾件事:
1.先調用請求類檔案裡的getRules方法擷取具體類檔案的使用者參數規則配置.然後檢索出目前請求的具體方法的參數規則。
2.phalapi的參數配置分全局和具體,全局可以在配置檔案裡通過 apiCommonRules配置,類全局是通過在本類的getRules裡通過*配置:
    /**
     * 應用接口層的統一參數
     */
    'apiCommonRules' => array(
        //簽名
        'sign' => array(
            'name' => 'sign', 'require' => true,
        ),
        //用戶端App版本号,預設為:1.4.0
        'version' => array(
            'name' => 'version', 'default' => '1.4.0', 
        ),
    ),
   類全局如下:
            '*' => array(
                'code' => array('name' => 'code', 'require' => true, 'min' => 4, 'max' => 4),
            ),
是以,我們這裡取到具體配置後,還要分别取全局參數配置以及類全局配置,然後通過array_merge組裝成一個數組。

擷取所有配置後,我們再來看它是怎麼處理的:
DI()->request->getByRule($rule)方法如下:

    /**
     * 根據規則擷取參數
     * 根據提供的參數規則,進行參數建立工作,并傳回錯誤資訊
     *
     * @param $rule array('name' => '', 'type' => '', 'defalt' => ...) 參數規則
     *
     * @return mixed
     * @throws BadRequestException
     * @throws InternalServerErrorException
     */
    public function getByRule($rule) {
        $rs = NULL;

        if (!isset($rule['name'])) {
            throw new InternalServerErrorException(T('miss name for rule'));
        }

        // 擷取接口參數級别的資料集
        $data = !empty($rule['source']) && substr(php_sapi_name(), 0, 3) != 'cli' 
            ? $this->getDataBySource($rule['source']) 
            : $this->data;

        $rs = Parser::format($rule['name'], $rule, $data);

        if ($rs === NULL && (isset($rule['require']) && $rule['require'])) {
            // 支援自定義友好的錯誤提示資訊,并支援i18n國際翻譯
            $message = isset($rule['message'])
                ? T($rule['message'])
                : T('{name} require, but miss', array('name' => $rule['name']));
            throw new BadRequestException($message);
        }

        return $rs;
    }

看來具體的驗證在Parser::format這個方法裡了.
主要實作是下面方法:

    /**
     * 根據範圍進行控制
     */
    protected function filterByRange($value, $rule) {
        $this->filterRangeMinLessThanOrEqualsMax($rule);

        $this->filterRangeCheckMin($value, $rule);

        $this->filterRangeCheckMax($value, $rule);

        return $value;
    }

    protected function filterRangeMinLessThanOrEqualsMax($rule) {
        if (isset($rule['min']) && isset($rule['max']) && $rule['min'] > $rule['max']) {
            throw new InternalServerErrorException(
                \PhalApi\T('min should <= max, but now {name} min = {min} and max = {max}',
                    array('name' => $rule['name'], 'min' => $rule['min'], 'max' => $rule['max']))
            );
        }
    }

    protected function filterRangeCheckMin($value, $rule) {
        if (isset($rule['min']) && $value < $rule['min']) {
            $message = isset($rule['message'])
                ? \PhalApi\T($rule['message'])
                : \PhalApi\T('{name} should >= {min}, but now {name} = {value}', 
                    array('name' => $rule['name'], 'min' => $rule['min'], 'value' => $value));
            throw new BadRequestException($message);
        }
    }

    protected function filterRangeCheckMax($value, $rule) {
        if (isset($rule['max']) && $value > $rule['max']) {
            $message = isset($rule['message'])
                ? \PhalApi\T($rule['message'])
                : \PhalApi\T('{name} should <= {max}, but now {name} = {value}',
                    array('name' => $rule['name'], 'max' => $rule['max'], 'value' => $value));
            throw new BadRequestException($message);
        }
    }
比如目前參數是整形的話,規則裡設定了min,max,屬性就通過這裡來判斷.值得注意的是,所有參數規則解析
是統一在formatAllType裡處理

    /**
     * 統一分發處理
     * @param string $type 類型
     * @param string $value 值
     * @param array $rule 規則配置
     * @return mixed
     */
    protected static function formatAllType($type, $value, $rule) {
        $diKey = '_formatter' . ucfirst($type);
        $diDefautl = '\\PhalApi\\Request\\Formatter\\' . ucfirst($type) . 'Formatter';

        $formatter = \PhalApi\DI()->get($diKey, $diDefautl);

        if (!($formatter instanceof Formatter)) {
            throw new InternalServerErrorException(
                //\PhalApi\T('invalid type: {type} for rule: {name}', array('type' => $type, 'name' => $rule['name']))
                \app\getMsg('074',"401", array('type' => $type, 'name' => $rule['name']))
            );
        }

        return $formatter->parse($value, $rule);
    }
formatAllType裡我們可以看到,這個方法裡根據參數類型,調用不同參數類型的處理類來處理對應參數.
           
總結:
我通路add接口,基類Api在實際方法前先進行參數檢查。比如在getRules裡設定參數為整形,我就調用Parse目錄裡的整形檢查類來進行相關檢查.