<?php
namespace WeChat\Contracts;
use WeChat\Exceptions\InvalidArgumentException;
use WeChat\Exceptions\InvalidResponseException;
* 微信基础类
* @package WeChat\Contracts
*/
class BasicWeChat
{
* 静态缓存
* @var static
*/
protected static $cache;
* 当前微信配置
* @var DataArray
*/
public $config;
* 访问AccessToken
* @var string
*/
public $access_token = '';
* 当前请求方法参数
* @var array
*/
protected $currentMethod = [];
* 当前模式
* @var bool
*/
protected $isTry = false;
* 注册代替函数
* @var string
*/
protected $GetAccessTokenCallback;
* 构造函数
* @param array $options 必填:appid、appsecret,可选:GetAccessTokenCallback、cache_path
*/
public function __construct(array $options)
{
if (empty($options['appid'])) {
throw new InvalidArgumentException("Missing Config -- [appid]");
}
if (empty($options['appsecret'])) {
throw new InvalidArgumentException("Missing Config -- [appsecret]");
}
if (isset($options['GetAccessTokenCallback']) && is_callable($options['GetAccessTokenCallback'])) {
$this->GetAccessTokenCallback = $options['GetAccessTokenCallback'];
}
if (!empty($options['cache_path'])) {
Tools::$cache_path = $options['cache_path'];
}
$this->config = new DataArray($options);
}
* 静态创建对象
* @param array $config 配置(appid、appsecret 等)
* @return static
*/
public static function instance(array $config)
{
$key = md5(get_called_class() . serialize($config));
if (isset(self::$cache[$key])) return self::$cache[$key];
return self::$cache[$key] = new static($config);
}
* 接口通用POST请求方法
* @param string $url 接口URL
* @param array $data POST提交接口参数
* @param bool $toJson 是否转换为JSON参数
* @return array
* @throws \WeChat\Exceptions\InvalidResponseException
* @throws \WeChat\Exceptions\LocalCacheException
*/
public function callPostApi($url, array $data, $toJson = true, array $options = [])
{
$this->registerApi($url, __FUNCTION__, func_get_args());
return $this->httpPostForJson($url, $data, $toJson, $options);
}
* 注册当前请求接口(自动处理 ACCESS_TOKEN)
* @param string $url 接口地址(引用传递,会被修改)
* @param string $method 当前接口方法名
* @param array $arguments 请求参数
* @return string 替换 ACCESS_TOKEN 后的 URL
* @throws \WeChat\Exceptions\InvalidResponseException
* @throws \WeChat\Exceptions\LocalCacheException
*/
protected function registerApi(&$url, $method, $arguments = [])
{
$this->currentMethod = ['method' => $method, 'arguments' => $arguments];
if (empty($this->access_token)) $this->access_token = $this->getAccessToken();
return $url = str_replace('ACCESS_TOKEN', urlencode($this->access_token), $url);
}
* 获取访问 AccessToken
* @return string
* @throws \WeChat\Exceptions\InvalidResponseException
* @throws \WeChat\Exceptions\LocalCacheException
*/
public function getAccessToken()
{
if (!empty($this->access_token)) {
return $this->access_token;
}
$cache = $this->config->get('appid') . '_access_token';
$this->access_token = Tools::getCache($cache);
if (!empty($this->access_token)) {
return $this->access_token;
}
if (!empty($this->GetAccessTokenCallback) && is_callable($this->GetAccessTokenCallback)) {
$this->access_token = call_user_func_array($this->GetAccessTokenCallback, [$this->config->get('appid'), $this]);
if (!empty($this->access_token)) {
Tools::setCache($cache, $this->access_token, 7000);
}
return $this->access_token;
}
list($appid, $secret) = [$this->config->get('appid'), $this->config->get('appsecret')];
$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appid}&secret={$secret}";
$result = Tools::json2arr(Tools::get($url));
if (!empty($result['access_token'])) {
Tools::setCache($cache, $result['access_token'], 7000);
}
return $this->access_token = $result['access_token'];
}
* 设置外部接口 AccessToken
* @param string $accessToken
* @throws \WeChat\Exceptions\LocalCacheException
* @author 高一平 <iam@gaoyiping.com>
*
* 当用户使用自己的缓存驱动时,直接实例化对象后可直接设置 AccessToken
* - 多用于分布式项目时保持 AccessToken 统一
* - 使用此方法后就由用户来保证传入的 AccessToken 为有效 AccessToken
*/
public function setAccessToken($accessToken)
{
if (!is_string($accessToken)) {
throw new InvalidArgumentException("Invalid AccessToken type, need string.");
}
$cache = $this->config->get('appid') . '_access_token';
Tools::setCache($cache, $this->access_token = $accessToken);
}
* 以POST获取接口数据并转为数组
* @param string $url 接口地址
* @param array $data 请求数据
* @param bool $toJson 转换JSON
* @param array $options 请求扩展数据
* @return array
* @throws \WeChat\Exceptions\InvalidResponseException
* @throws \WeChat\Exceptions\LocalCacheException
*/
protected function httpPostForJson($url, array $data, $toJson = true, array $options = [])
{
try {
$options['headers'] = isset($options['headers']) ? $options['headers'] : [];
if ($toJson) $options['headers'][] = 'Content-Type: application/json';
return Tools::json2arr(Tools::post($url, $toJson ? Tools::arr2json($data) : $data, $options));
} catch (InvalidResponseException $exception) {
if (!$this->isTry && in_array($exception->getCode(), ['40014', '40001', '41001', '42001'])) {
$this->delAccessToken();
$this->isTry = true;
return call_user_func_array([$this, $this->currentMethod['method']], $this->currentMethod['arguments']);
}
throw new InvalidResponseException($exception->getMessage(), $exception->getCode());
}
}
* 清理删除 AccessToken
* @return bool
*/
public function delAccessToken()
{
$this->access_token = '';
return Tools::delCache($this->config->get('appid') . '_access_token');
}
* 通用 GET 请求(自动处理 ACCESS_TOKEN)
* @param string $url 接口 URL
* @return array
* @throws \WeChat\Exceptions\InvalidResponseException
* @throws \WeChat\Exceptions\LocalCacheException
*/
public function callGetApi($url)
{
$this->registerApi($url, __FUNCTION__, func_get_args());
return $this->httpGetForJson($url);
}
* 以GET获取接口数据并转为数组
* @param string $url 接口地址
* @return array
* @throws \WeChat\Exceptions\InvalidResponseException
* @throws \WeChat\Exceptions\LocalCacheException
*/
protected function httpGetForJson($url)
{
try {
return Tools::json2arr(Tools::get($url));
} catch (InvalidResponseException $exception) {
if (isset($this->currentMethod['method']) && empty($this->isTry)) {
if (in_array($exception->getCode(), ['40014', '40001', '41001', '42001'])) {
$this->delAccessToken();
$this->isTry = true;
return call_user_func_array([$this, $this->currentMethod['method']], $this->currentMethod['arguments']);
}
}
throw new InvalidResponseException($exception->getMessage(), $exception->getCode());
}
}
* 通用接口调用
* @param string $url 完整 URL 或相对路径,支持 ACCESS_TOKEN 占位符自动替换
* @param array|string $data GET 参数或请求体,数组自动 JSON;字符串原样发送
* @param string $method GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS,默认 GET
* @return array 解析后的数组
* @throws \WeChat\Exceptions\InvalidResponseException
* @throws \WeChat\Exceptions\LocalCacheException
*/
public function callApi($url, $data = [], $method = 'GET')
{
$method = strtoupper($method);
if (strpos($url, 'ACCESS_TOKEN') !== false) {
if (empty($this->access_token)) {
$this->access_token = $this->getAccessToken();
}
$url = str_replace('ACCESS_TOKEN', urlencode($this->access_token), $url);
}
if (in_array($method, ['GET', 'HEAD', 'OPTIONS'])) {
if (!empty($data) && is_array($data)) {
$url .= (strpos($url, '?') !== false ? '&' : '?') . http_build_query($data);
}
return $this->httpGetForJson($url);
}
$postData = is_array($data) ? $data : [];
$toJson = is_array($data);
if ($method === 'POST') {
return $this->httpPostForJson($url, $postData, $toJson, []);
}
$requestData = is_string($data) ? $data : Tools::arr2json($postData);
$response = Tools::doRequest($method, $url, ['data' => $requestData]);
return Tools::json2arr($response);
}
}