如何更好的封裝PHP SDK

什麼是sdk

軟件開發工具包Software Development Kit, SDK)通常是一些被軟件工程師用於爲特定的軟件包、軟件框架、硬件平臺、操做系統等建立應用軟件的開發工具的集合。php

一般意義的sdk

在web開發領域,咱們接觸的sdk一般是爲了調用開放接口而封裝好的公司外的第三方庫。好比,各大互聯網公司在幾年前都作了開發者平臺html

Github: GitHub API v3git

Weibo: API - 微博APIgithub

Twitter: dev.twitter.com/web

Instagram: www.instagram.com/developer/算法

開放接口特色

能夠理解爲,爲了調用開放接口,sdk就是爲了方便調用才封裝好了一些方法和工具。咱們能夠嘗試總結下咱們接觸的開放接口的特色:編程

  1. 基於 http, 這種方式是最簡單的,跨平臺和編程語言的數據交換方式;
  2. 使用 GET / POST 請求方式,傳遞數據,而且通常都有接入方的憑證,如 TOKEN SIGN 等;
  3. 傳遞參數不定,請求前都須要進行整理格式化,好比,加入請求憑證 或 json encode 亦或 加密;
  4. 返回數據須要從新整理才能供內部使用,好比是否成功判斷,字段名轉換;

場景特色傳達的幾個信息

  1. 咱們做爲調用方,此時至關於客戶端的角色,要請求接口;
  2. 對於調用方的憑證或隱私信息保存要靈活配置,由於可能接口有沙箱環境,那麼相關配置如調用方id, key 或私鑰文件等都不一樣;
  3. PHP大法好?!,強大的 array 類型,一會兒打包就 post 過去,雖然這樣最簡單省事,可是這樣可讀性並很差;提倡把接口數據抽象成一個對象,一個接口對應一個類,利用 傳輸對象模式
  4. 對軟件架構中的防腐層設計有一個概念,系統內部和外部交互進行字段轉換,起碼字段轉換相關邏輯必定單獨作;

編碼的原則

  • 可讀性
  • 可擴展性
  • 避免魔法值,就是寫死的數字、字符等,該作常量就作常量,該作成配置就單獨抽出來作成配置;

實現

建立接口請求對象的抽象類

<?php

/** * Class AbstractRequestBody */
abstract class AbstractRequestBody {
    /** * @var string $path * 由於每一個接口的路徑不一樣,因此須要每一個接口都顯示的指定請求路徑 */
    protected $path;

    /** * @var array $data * 咱們把每一個接口的業務參數最後統一收納在data數組中 */
    protected $data = [];

    /** * AbstractRequestBody constructor. */
    public function __construct() {
        $this->setPath();
    }

    /** * @return mixed * 假若有必要,每一個接口均可以進行一些自定義的參數校驗 */
    abstract public function validate();

    /** * @param $response * * @return mixed */
    abstract public function transfer(&$response);

    /** * @return string * 最後發起請求的時候獲取接口路徑 */
    public function getPath() {
        return $this->path;
    }

    /** * @return mixed * 抽象化對象,強制要求每一個接口類必須設定接口路徑 */
    abstract protected function setPath();

    /** * @return array * 須要打包的一些公共參數 */
    public function package(): array {
        $data = $this->getData();
        $params['data'] = json_encode($data, 320);
        $params['version'] = Constants::VERSION;
        $params['nonce_str'] = Utils::getNonceStr();
        $params['sign'] = Utils::sign($params);

        return $params;
    }

    protected function getData() {
        return $this->data;
    }
}

複製代碼

OneApi 接口請求類

<?php

/** * Class OneApi */
class OneApi extends AbstractRequestBody {
    public function setOneParam($value) {
        $this->data['one_param'] = $value;
        
        // 爲實現可鏈式調用
        return $this;
    }

    public function setTwoParam($value) {
         $this->data['two_param'] = $value;
        
          // 爲實現可鏈式調用
        return $this;
    }

    /** * ...更多參數 */

     /** * 自定義的參數驗證 */
    public function validate() {
        // TODO: Implement validate() method.
    }

     /** * 字段轉換 */
    public function transfer(&$response) {
        // TODO: Implement transfer() method.
    }

    protected function setPath() {
        $this->path = '/path/api';
    }
}

複製代碼

工具類

<?php

/** * Class Utils */
class Utils {
    /** * @param int $length * @return string */
    public static function getNonceStr() {
    }

    /** * 簽名算法 * 可能要須要用到key 或 私鑰文件 * @param array $params * @return string */
    public static function sign(array $params): string {
    }

    /** * 驗證接口返回數據 * @param array $params * @return bool */
    public static function verifySign(array $params): bool {
    }
}

複製代碼

常量類

若是常量比較多,建議根據功能分類作成多個文件。json

<?php

/** * Class Constants */
class Constants {
    const DOMAIN_PROD = 'https://open.api.com';
    const VERSION = '1.0.0';
    const SIGN_TYPE = 'MD5';

    /** * 接口其餘相關常量 */

}

複製代碼

配置

這裏只作簡單示例,配置也能夠作成 return array() 的形式,而後加載,或者相似 Laravel 利用 env 函數從環境變量取;api

有一個原則是:代碼配置安全的檢測標準之一是代碼庫是否可當即開源。數組

<?php

/** * Class Config */
class Config {
    const SIGN_MD5_KEY = '1111111111111';
    const SERVICE_ID = '1213444543545';
}

複製代碼

SdkClient類

<?php

/** * Class SdkClient */
class SdkClient {
    /** * @param AbstractRequestBody $requestBody * @param int $timeout * @return string */
    public function send($requestBody, int $timeout = 15) {
        try {
            // 校驗參數
            $requestBody->validate();

            // 拼湊url
            $url = Constants::DOMAIN_PROD . $requestBody->getPath();

            // 獲取請求參數
            $data = $requestBody->package();

            // 發送請求並通過一層統一轉換
            $response = $this->response(
                $this->post(
                    $url,
                    $data,
                    $timeout
                )
            );
            // 若比較特殊的格式轉換,能夠在各自請求類中進行響應過濾,響應數據是傳引用的,直接處理便可
            $requestBody->transfer($response);
            
        } catch (\Exception $exception) {
            // TODO: 異常處理 $response = ...
        }
        return $response;
    }

    /** * 對返回數據統一處理,數據過濾 * 好比轉換成數組,仍是對象,若是是對象能夠new Response,在構造函數處理 * @param string $content * @return string */
    private function response(string $content) {
        return $content;
    }

    /** * TODO: 這裏亦可根據需求,替換成項目可用的http包 * @param string $url * @param array $fields * @param int $timeout * @return bool|string */
    private function post(string $url, array $fields, int $timeout = 10) {
        $headers = ['Content-Type: application/json;charset=UTF-8',];
        $ch = curl_init();
        array_push($headers, "Expect:");
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
           // ...
        
        return curl_exec($ch);
    }
}

複製代碼

調用示例

<?php

class Demo {
    private $sdk_client;

    public function __construct() {
        $this->sdk_client = new SdkClient();
    }

    public function testOneApi() {
   
        $request = new OneApi();
        $request->setOneParam('3333')
            ->setTwoParam('1111');
        
        print_r($this->sdk_client->send($request));
    }
}

複製代碼

工程目錄參考

以前封裝了調用新支付開放平臺的sdk,工程目錄做爲參考

image.png

反思

這種封裝方式適用在那些場景?

總結這個封裝套路是在接了幾個支付渠道後,不斷嘗試總結出來的;而且有借鑑Java中面向對象的設計思路;
特別適合業務參數比較多、接口比較多、邏輯複雜、簽名嚴格的場景下封裝抽象出這幾個類。

假如參數比較少請求比較簡單,好比 GET 請求一兩個參數,尚未特別複雜的簽名算法的狀況下,這種顯然是不適合的,這隻會徒增工做量。

以上代碼只是做爲一個參考,嘗試總結封裝的套路,還有細節和不完善的地方。

擴展

前段時間按照這些思路封裝了一個釘釘聊天機器人sdk,能夠做爲參考,歡迎 star 或 下載 composer require baiyutang/dingtalk-chatbot

相關文章
相關標籤/搜索