如何更好的封装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

相关文章
相关标签/搜索