API 调用
与以往版本不同的是,SDK 不再内置具体 API 的逻辑,所有的 API 均交由开发者自行调用,以更新用户备注为例:
$api = $app->getClient();
$response = $api->post('/cgi-bin/user/info/updateremark', [
    'json' => [
            "openid" => "oDF3iY9ffA-hqb2vVvbr7qxf6A0Q",
            "remark" => "pangzi"
        ]
    ]);
// or
$response = $api->postJson('/cgi-bin/user/info/updateremark', [
    "openid" => "oDF3iY9ffA-hqb2vVvbr7qxf6A0Q",
    "remark" => "pangzi"
]);
语法说明
get(string $uri, array $options = []): Symfony\Contracts\HttpClient\ResponseInterface
post(string $uri, array $options = []): Symfony\Contracts\HttpClient\ResponseInterface
postJson(string $url, array $options = []): Symfony\Contracts\HttpClient\ResponseInterface
patch(string $uri, array $options = []): Symfony\Contracts\HttpClient\ResponseInterface
put(string $uri, array $options = []): Symfony\Contracts\HttpClient\ResponseInterface
delete(string $uri, array $options = []): Symfony\Contracts\HttpClient\ResponseInterface
$options 为请求参数,可以指定 query/body/json/xml/headers 等等,具体请参考:HttpClientInterface::OPTIONS_DEFAULTS
请求参数
GET
$response = $api->get('/cgi-bin/user/list', [
    'next_openid' => 'OPENID1',
]);
POST
$response = $api->post('/cgi-bin/user/info/updateremark', [
    'body' => \json_encode([
            "openid" => "oDF3iY9ffA-hqb2vVvbr7qxf6A0Q",
            "remark" => "pangzi"
        ])
    ]);
或者可以简写为:
$response = $api->postJson('/cgi-bin/user/info/updateremark', [
    "openid" => "oDF3iY9ffA-hqb2vVvbr7qxf6A0Q",
    "remark" => "pangzi"
]);
或者指定 xml 格式:
$response = $api->postXml('/mmpaymkttransfers/promotion/transfers', [
    'mch_appid' => $app->getConfig()['app_id'],
    'mchid' => $app->getConfig()['mch_id'],
    'partner_trade_no' => '202203081646729819743',
    'openid' => 'ogn1H45HCRxVRiEMLbLLuABbxxxx',
    'check_name' => 'FORCE_CHECK',
    're_user_name'=> 'overtrue',
    'amount' => 100,
    'desc' => '理赔',
 ]);
请求证书
你可以在请求支付时指定证书,以微信支付 V2 为例:
$response = $api->post('/mmpaymkttransfers/promotion/transfers', [
    'xml' => [
        'mch_appid' => $app->getConfig()['app_id'],
        'mchid' => $app->getConfig()['mch_id'],
        'partner_trade_no' => '202203081646729819743',
        'openid' => 'ogn1H45HCRxVRiEMLbLLuABbxxxx',
        'check_name' => 'FORCE_CHECK',
        're_user_name'=> 'overtrue',
        'amount' => 100,
        'desc' => '理赔',
    ],
    'local_cert' => $app->getConfig()['cert_path'],
    'local_pk' => $app->getConfig()['key_path'],
    ]);
文件上传
你有两种上传文件的方式可以选择:
从指定路径上传
use EasyWeChat\Kernel\Form\File;
use EasyWeChat\Kernel\Form\Form;
$options = Form::create(
    [
        'media' => File::fromPath('/path/to/image.jpg'),
    ]
)->toArray();
$response = $api->post('cgi-bin/media/upload?type=image', $options);
从二进制内容上传
use EasyWeChat\Kernel\Form\File;
use EasyWeChat\Kernel\Form\Form;
$options = Form::create(
    [
        'media' => File::withContents($contents, 'image.jpg'), // 注意:请指定文件名
    ]
)->toArray();
$response = $api->post('cgi-bin/media/upload?type=image', $options);
简化写法 6.4.0+
上面的两种传法都可以简写为下面的方式:
// withFile(string $localPath, string $formName = 'file', string $filename = null)
$media = $client->withFile($path, 'media')->post('cgi-bin/media/upload?type=image');
// withFileContents(string $contents, string $formName = 'file', string $filename = null)
$media = $client->withFileContents($contents, 'media', 'filename.png')->post('cgi-bin/media/upload?type=image');
预置参数的传递 6.4.0+
在调用 API 的时候难免有的需要传递账号的一些信息,尤其是支付相关的 API,例如查询订单:
$client->get('v3/pay/transactions/id/1217752501201407033233368018', [
    'mchid' => $app->getAccount()->getMchid(),
]);
不得不把商户号这种基础信息再读取传递一遍,比较麻烦,设计了如下的简化方案:
$client->withMchid()->get('v3/pay/transactions/id/1217752501201407033233368018');
原理就是 with + 配置 key:
$client->withAppid()->post('/path/to/resources', [...]);
$client->withAppid()->withMchid()->post('/path/to/resources', [...]);
也可以自定义值:
$client->withAppid('12345678')->post('/path/to/resources', [...]);
// or 
$client->with('appid', '123456')->post('/path/to/resources', [...]);
还可以设置别名:把 appid 作为参数 mch_appid 值使用:
$client->withAppidAs('mch_appid')->post('/path/to/resources', [...]);
其它通用方法:
$client->with('appid')->post(...)
$client->with(['appid', 'mchid'])->post(...)
$client->with(['appid' => '1234565', 'mchid'])->post(...)
处理响应
API Client 基于 symfony/http-client 实现,你可以通过以下方式对响应值进行访问:
异常处理 6.3.0+
当请求失败,例如状态码不为 200 时,默认访问响应内容都会抛出异常:
$response->getContent(); // 这里会抛出异常
如果你不希望默认抛出异常,而希望自己处理,可以在配置文件指定 http.throw 参数为 false:
$config = [
  //...
  'http' => [
    'throw' => false,
    //...
  ],
];
这样,你就可以在调用 API 时,自己处理异常:
$options = [
    'query' => [
        'openid' => 'oDF3iY9ffA-hqb2vVvbr7qxf6A0Q',
    ]
];
$response = $api->get('/cgi-bin/user/get', $options);
if ($response->isFailed()) {
    // 出错了,处理异常
}
return $response;
或者不改变默认配置的情况下,在调用请求时单独设置throw(false),若该请求失败,也可以自己处理异常。
// $options 同上文,这里省略
$response = $api->get('/cgi-bin/user/get', $options)->throw(false);
if ($response->isFailed()) {
    // 出错了,处理异常
}
return $response;
数组式访问
EasyWeChat 增强了 API 响应对象,比如增加了数组式访问,你可以不用每次 toArray 后再取值,更加便捷美观:
$response = $api->get('/foo/bar');
$response['foo']; // "bar"
isset($response['foo']); // true
获取状态码
$response->getStatusCode();
// 200
判断业务是否成功/失败 6.3.0+
比如状态码是 200,但是公众号接口返回 40029 code 错误:
$response->isSuccessful();  // false
$response->isFailed();      // true
获取响应头
$response->getHeaders();
// ['content-type' => ['application/json;encoding=utf-8'], '...']
$response->getHeader('content-type');
// ['application/json;encoding=utf-8']
$response->getHeaderLine('content-type');
// 'application/json;encoding=utf-8'
获取响应内容
$response->getContent();
$response->getContent(false); // 失败不抛出异常
// {"foo":"bar"}
// 获取 json 转换后的数组格式
$response->toArray();
$response->toArray(false); // 失败不抛出异常
// ["foo" => "bar"]
// 获取 json
$response->toJson();
$response->toJson(false);
// {"foo":"bar"}
// 将内容转换成流返回
$response->toStream();
$response->toStream(false); // 失败不抛出异常
保存到文件 6.3.0+
你可以方便的将内容直接存储到文件:
$path = $response->saveAs('/path/to/file.jpg');
// /path/to/file.jpg
转换为 Data URLs 6.3.0+
你可以将内容转换为Data URLs
$dataUrl = $response->toDataUrl();
// data:image/png,%89PNG%0D%0A...
获取其他上下文信息
如:"response_headers", "redirect_count", "start_time", "redirect_url" 等:
$httpInfo = $response->getInfo();
// 获取指定信息
$startTime = $response->getInfo('start_time');
// 获取请求日志
$httpLogs = $response->getInfo('debug');
📖 更多使用请参考: HTTP client: Processing Responses
异步请求
所有的请求都是异步的,当你第一次访问 $response 时才会真正的请求,比如:
// 这段代码会立即执行,并不会发起网络请求
$response = $api->postJson('/cgi-bin/user/info/updateremark', [
    "openid" => "oDF3iY9ffA-hqb2vVvbr7qxf6A0Q",
    "remark" => "pangzi"
]);
// 当你尝试访问 $response 的信息时,才会发起请求并等待返回
$contentType = $response->getHeaders()['content-type'][0];
// 尝试获取响应内容将阻塞执行,直到接收到完整的响应内容
$content = $response->getContent();
并行请求
由于请求天然是异步的,那么你可以很简单实现并行请求:
$responses = [
    $api->get('/cgi-bin/user/get'),
    $api->post('/cgi-bin/user/info/updateremark', ['body' => ...]),
    $api->post('/cgi-bin/user/message/custom/send', ['body' => ...]),
];
// 访问任意一个 $response 时将执行并发请求:
foreach ($responses as $response) {
    $content = $response->getContent();
    // ...
}
当然你也可以给每个请求分配名字独立访问:
$responses = [
    'users' => $api->get('/cgi-bin/user/get'),
    'remark' => $api->post('/cgi-bin/user/info/updateremark', ['body' => ...]),
    'message' => $api->post('/cgi-bin/user/message/custom/send', ['body' => ...]),
];
// 访问任意一个 $response 时将执行并发请求:
$responses['users']->toArray();
失败重试 6.1.0+
默认在公众号、小程序开启了重试机制,你可以通过全局配置或者手动开启重试特性。
🚨 不建议在支付模块使用重试功能,因为一旦重试导致支付数据异常,可能造成无法挽回的损失。
方式一:全局配置
在支持重试的模块里增加如下配置可以完成重试机制的全局启用:
    'http' => [
        //...
        'retry' => true, // 使用默认配置
        // 'retry' => [
        //     // 仅以下状态码重试
        //     'http_codes' => [429, 500]
        //     'max_retries' => 3
        //     // 请求间隔 (毫秒)
        //     'delay' => 1000,
        //     // 如果设置,每次重试的等待时间都会增加这个系数
        //     // (例如. 首次:1000ms; 第二次: 3 * 1000ms; etc.)
        //     'multiplier' => 0.1
        // ],
    ],
方式二:手动开启
如果你不想使用基于配置的全局重试机制,你可以使用 HttpClient::retry() 方法来开启失败重试能力:
$app->getClient()->retry()->get('/foo/bar');
当然,你可以在 retry 配置中自定义重试的配置,如下所示:
$app->getClient()->retry([
    'max_retries' => 2,
    //...
])->get('/foo/bar');
自定义重试策略
如果觉得参数不能满足需求,你还可以自己实现 Symfony\Component\HttpClient\RetryStrategyInterface 接口来自定义重试策略,然后调用 retryUsing 方法来使用它。
💡 建议继承基类来拓展,以实现默认重试类的基础功能。
class MyRetryStrategy extends \Symfony\Component\HttpClient\Retry\GenericRetryStrategy
{
    public function shouldRetry(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): ?bool
    {
        // 你的自定义逻辑
        // if (...) {
        //     return false;
        // }
        return parent::shouldRetry($context, $responseContent, $exception);
    }
}
使用自定义重试策略:
$app->getClient()->retryUsing(new MyRetryStrategy())->get('/foo/bar');
更多使用方法
📖 更多使用请参考:symfony/http-client