PHP 如何更优雅地调用 API 接口

2021-01-20 16:53:37 +08:00
 topthink

API 接口在各种场景中已经非常普遍使用,通常在 PHP 后台调用 API 接口,需要通过 Curl 库来自己封装,且不说各种充值门槛,还要被各种 api 接口平台的 appKey 、appSecret 之类的参数困惑,没法实现统一调用,很多小白更是被 Curl 各种配置参数弄得头大。ThinkPHP官方出品的ThinkAPI服务正是为了解决 PHP 接口调用的各种麻烦问题。

ThinkAPI统一API接口服务是由官方联合合作伙伴封装的一套接口调用服务及 SDK,旨在帮助ThinkPHP开发者更方便和更低成本调用官方及第三方的提供的各类API接口及服务,从而更好的构建开发者生态。

通过ThinkAPI提供的 SDK 功能可以以更优雅的方式来调用 API 接口,首先需要在你的项目里面安装 think-api 库(适用于任何 PHP5.6+项目,没有任何框架要求)。

composer require topthink/think-api

然后就可以调用你需要的接口进行查询和返回数据,支持ThinkAPI所有的 API 接口,以查询身份证所属地区接口为例:

use think\api\Client;

$client = new Client("appCode");

$result = $client->idcardIndex()
    ->withCardno('身份证号码')
    ->request();

idcardIndex方法就是调用了身份证归属地查询接口 withCardno方法则表示传入了cardno参数,如果还需要传入更多的参数则链式调用更多的方法即可,最后通过request方法进行实际调用并返回数据。通过 IDE 配合的话,你不需要自己记住任何接口方法名和参数方法名,都会有自动提示。

ThinkAPI所有的 API 调用服务必须设置appCode值(只需要注册一个账号即可获取),用于接口调用的身份认证。如需多次调用的话,建议自己在项目里面封装一个助手函数,例如:

use think\api\Client;

/**
 * API 接口调用助手函数
 * @return Client
 */
function api(): Client
{
    return new Client('yourAppCode');
}

// 调用示例
$result = api()->idcardIndex()
    ->withCardno('身份证号码')
    ->request();

所有的接口服务和方法都支持 IDE 自动提示和完成(请务必注意方法大小写必须保持一致),所有的返回数据都是 JSON 格式,因此基本上不需要文档即可完成接口开发工作。API 接口调用中的一些常见问题通过系统的方法封装都可以规避掉,你甚至不需要关心接口是要用 GET 还是 POST,都是系统自动处理的。

SDK 把所有接口和参数都封装为一个个独立的方法,你可以像调用一个类的方法一样简单的调用官方支持的任何 API 接口,也无需再去记住每个接口的参数有哪些。

如果你的环境不支持 Composer 或者 PHP 版本过低,可能需要你自己封装 Curl 库来调用接口。ThinkAPI 接口文档都提供了两种方式调用:直接调用接口地址和使用 SDK 调用。

目前 ThinkAPI 已经接入包括实名认证、人工智能、电子商务、新闻资讯和生活服务等类目在内的常用 API 接口共 269 个,包含大量免费接口,付费接口的价格比较实在、门槛也低,并且还在陆续扩充中。更详细的用法可以参考: https://docs.topthink.com/think-api

3571 次点击
所在节点    PHP
25 条回复
dvaknheo
2021-01-22 16:36:07 +08:00
那我贴出来吧。为什么要有 facade 等东西,目的就是为了实现 “调用方式不变,实现方式可变”啊。

```
<?php
require_once(__DIR__.'/vendor/autoload.php'); //@DUCKPHP_HEADFILE
use think\api\Client;
use GuzzleHttp\Client as GuzzleHttp_Client;
use GuzzleHttp\HandlerStack ;
use think\helper\Str;

trait SingletonExTrait
{
protected static $_instances = [];
public static function G($object = null)
{
if (defined('__SINGLETONEX_REPALACER')) {
$callback = __SINGLETONEX_REPALACER;
return ($callback)(static::class, $object);
}
//fwrite(STDOUT,"SINGLETON ". static::class ."\n");
if ($object) {
self::$_instances[static::class] = $object;
return $object;
}
$me = self::$_instances[static::class] ?? null;
if (null === $me) {
$me = new static();
self::$_instances[static::class] = $me;
}

return $me;
}
}
class MyService
{
use SingletonExTrait;
public $options =[
'endpoint' => 'https://api.topthink.com/',
'app_code' => '???',
'default_http_method'=>'GET',
];
public function init(array $options, object $context = null)
{
$this->options = array_intersect_key(array_replace_recursive($this->options, $options) ?? [], $this->options);
}
protected function do_call($method, $args)
{
$http_method = $this->get_http_method($method);
$uri = $this->get_uri($method);
$parameters = $this->get_parameters($method, $args);

try {
return $this->do_request($this->options['endpoint'], $this->options['app_code'], $http_method, $uri, $parameters);
} catch (RequestException $e) {
if ($e->hasResponse()) {
$response = $e->getResponse();
throw new Exception($response->getStatusCode(), $response->getBody()->getContents());
}
throw $e;
}
}
protected function get_http_method($method)
{
return $this->options['default_http_method'];
}
protected function get_uri($method)
{
//TODO 移除 Str 的引用
return $this->uri_map[$method] ?? Str::snake(class_basename($method), "/");
}
protected function get_parameters($method, $args)
{
$reflect = new \ReflectionMethod($this, $method);
$ret=[];
$params = $reflect->getParameters();
foreach ($args as $i => $v) {
if($v === null){
continue;
}
$name = $params[$i]->getName();
$ret[$name] = $v;
}
return $ret;
}

protected function do_request($endpoint, $app_code, $method, $uri, $parameters)
{
$body =[];
if ($method == 'GET') {
$options['query'] = $data;
} else {
$options['body'] = $data;
}

$handleStack = HandlerStack::create(null);
$client = new GuzzleHttp_Client([
'base_uri' => $endpoint,
'handler' => $handleStack,
'headers' => [
'Authorization' => "AppCode ".$app_code,
'User-Agent' => "ThinkApi/1.0",
],
'verify' => false,
]);
$response = $client->request($method, $uri, $options);
$result = $response->getBody()->getContents();
if (false !== strpos($response->getHeaderLine('Content-Type'), 'application/json')) {
$result = json_decode($result, true);
}

return $result;
}
}

// 这个类用脚本生成,省略更多
class CalendarService extends MyService
{
/**
* 查日历
*
* @access public
* @param mixed $year_month
*/
public function calendarMonth($yearMonth = null){
return $this->do_call(__FUNCTION__, func_get_args());
}
}
class LocalCalendarService extends CalendarService
{
public function calendarMonth($yearMonth = null){
return ['这是模拟数据'];
}
}

//*
// 这里是核心工程师老大的干活
//CalendarService::G(LocalCalendarService::G()); // 线上出问题的时候切这句
$options=['app_code'=>'???'];
CalendarService::G()->init($options);

//// 下面是小弟写
$result = CalendarService::G()->calendarMonth('2019-1');
var_dump($result);
return;
//*/
$client = new Client('???');
$result = $client->calendarMonth()->withYearMonth('2019-1')->request();
var_dump($result);
```
topthink
2021-01-22 17:29:38 +08:00
@dvaknheo 你这还是基于 think-api 的库 有什么优势 我还以为你要用 100 行代码写一个 think-api 出来呢,其实 think-api 库就是一个 IDE 提示工具而已 真正的调用又不是在这个库 你把问题想的太简单了吧
dvaknheo
2021-01-22 18:54:21 +08:00
@topthink 没基于 think-api 啊, 除了用字符串处理部分还用到 think\helper\Str 而已。 最前面的 use think\api\Client 是后面代码切换演示方便而已。
topthink
2021-01-23 16:40:06 +08:00
@dvaknheo 问题是我看不明白你如何离开了 think-api 或者切换到其它 sdk 的支持 怎么做到同样的优雅调用? think-api 的优雅的前提是统一封装了 api 接口的 这个才是最大的问题和工作量
dvaknheo
2021-01-23 18:45:18 +08:00
@topthink
这个客户端,优雅在这里: 小弟不用改业务代码,只要老大在核心代码那里修改到子类就可以切换实现

CalendarService 是根据 think-api 的库生成的。CalendarService 的所有方法都会在 IDE 里实现。没必要方法和文档分离。 所有的 API 函数方法 内容都一句 return $this->do_call(__FUNCTION__, func_get_args());


CalendarService::G(LocalCalendarService::G());

演示的是切到第三方的实现 。 这里只是简单的输出 ['这是模拟数据'] 。 实际应用的时候,可以针对性的各种修改,比如 api 结果如果有缓存则用缓存。 比如当对 think-api 的返回的结果异常 如接口上线, 可以添加其他 服务端的支持。

这一切,不需要改动业务代码。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/746743

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX