From 20997f26666b96ec82223d6ae3f4f2521239f4bd Mon Sep 17 00:00:00 2001
From: wuliangbo
Date: Mon, 6 Jan 2020 18:11:35 +0800
Subject: [PATCH] =?UTF-8?q?=E5=8F=82=E8=80=83wasywechat?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
composer.json | 9 +-
src/Factory.php | 2 +-
src/Kernel/AopClient.php | 559 ++++------------
src/Kernel/BaseClient.php | 216 +++++++
src/Kernel/Config.php | 23 +-
src/Kernel/Contracts/Arrayable.php | 21 +
src/Kernel/Events/ApplicationInitialized.php | 35 +
src/Kernel/Events/EventHandlerInterface.php | 25 +
src/Kernel/Events/HttpResponseCreated.php | 35 +
.../Events/ServerGuardResponseCreated.php | 35 +
src/Kernel/Exceptions/BadRequestException.php | 12 +
src/Kernel/Exceptions/Exception.php | 15 +
.../Exceptions/InvalidArgumentException.php | 21 +
.../Exceptions/InvalidConfigException.php | 21 +
src/Kernel/Exceptions/RuntimeException.php | 13 +
src/Kernel/Extension.php | 134 ++++
src/Kernel/Helpers.php | 82 +++
src/Kernel/Http/Response.php | 121 ++++
src/Kernel/Http/StreamResponse.php | 86 +++
src/Kernel/Log/LogManager.php | 608 ++++++++++++++++++
.../Providers/ConfigServiceProvider.php | 2 +-
.../EventDispatcherServiceProvider.php | 47 ++
.../Providers/ExtensionServiceProvider.php | 39 ++
.../Providers/HttpClientServiceProvider.php | 39 ++
src/Kernel/Providers/LogServiceProvider.php | 79 +++
.../Providers/RequestServiceProvider.php | 39 ++
src/Kernel/ServiceContainer.php | 66 +-
src/Kernel/Support/Arr.php | 466 ++++++++++++++
src/Kernel/Support/Collection.php | 420 ++++++++++++
src/Kernel/Support/File.php | 135 ++++
src/Kernel/Support/XML.php | 167 +++++
src/Kernel/Traits/HasHttpRequests.php | 9 +-
src/Kernel/Traits/ResponseCastable.php | 85 +++
src/Marketing/Pass/ServiceProvider.php | 1 -
src/Mini/Risk/ServiceProvider.php | 1 -
35 files changed, 3187 insertions(+), 481 deletions(-)
create mode 100644 src/Kernel/BaseClient.php
create mode 100644 src/Kernel/Contracts/Arrayable.php
create mode 100644 src/Kernel/Events/ApplicationInitialized.php
create mode 100644 src/Kernel/Events/EventHandlerInterface.php
create mode 100644 src/Kernel/Events/HttpResponseCreated.php
create mode 100644 src/Kernel/Events/ServerGuardResponseCreated.php
create mode 100644 src/Kernel/Exceptions/BadRequestException.php
create mode 100644 src/Kernel/Exceptions/Exception.php
create mode 100644 src/Kernel/Exceptions/InvalidArgumentException.php
create mode 100644 src/Kernel/Exceptions/InvalidConfigException.php
create mode 100644 src/Kernel/Exceptions/RuntimeException.php
create mode 100644 src/Kernel/Extension.php
create mode 100644 src/Kernel/Helpers.php
create mode 100644 src/Kernel/Http/Response.php
create mode 100644 src/Kernel/Http/StreamResponse.php
create mode 100644 src/Kernel/Log/LogManager.php
create mode 100644 src/Kernel/Providers/EventDispatcherServiceProvider.php
create mode 100644 src/Kernel/Providers/ExtensionServiceProvider.php
create mode 100644 src/Kernel/Providers/HttpClientServiceProvider.php
create mode 100644 src/Kernel/Providers/LogServiceProvider.php
create mode 100644 src/Kernel/Providers/RequestServiceProvider.php
create mode 100644 src/Kernel/Support/Arr.php
create mode 100644 src/Kernel/Support/Collection.php
create mode 100644 src/Kernel/Support/File.php
create mode 100644 src/Kernel/Support/XML.php
create mode 100644 src/Kernel/Traits/ResponseCastable.php
diff --git a/composer.json b/composer.json
index 038f950..d4fc7b8 100644
--- a/composer.json
+++ b/composer.json
@@ -17,12 +17,17 @@
"pimple/pimple": "^3.0",
"symfony/cache": "^3.3 || ^4.0",
"symfony/http-foundation": "^2.7 || ^3.0 || ^4.0",
- "symfony/psr-http-message-bridge": "^0.3 || ^1.0"
+ "symfony/psr-http-message-bridge": "^0.3 || ^1.0",
+ "symfony/event-dispatcher": "^5.0",
+ "monolog/monolog": "^1.22 || ^2.0"
},
"autoload": {
"psr-4": {
"EasyAlipay\\": "src/"
- }
+ },
+ "files": [
+ "src/Kernel/Helpers.php"
+ ]
},
"autoload-dev": {
"psr-4": {
diff --git a/src/Factory.php b/src/Factory.php
index 946f4cd..3367b49 100755
--- a/src/Factory.php
+++ b/src/Factory.php
@@ -9,7 +9,7 @@ namespace EasyAlipay;
* @method static \EasyAlipay\Mini\Application mini(array $config)
* @method static \EasyAlipay\OpenPublic\Application openPublic(array $config)
* @method static \EasyAlipay\Marketing\Application marketing(array $config)
- * @method static \EasyAlipay\BasicService\Application basicService(array $config)
+ * @method static \EasyAlipay\Base\Application base(array $config)
*/
class Factory
{
diff --git a/src/Kernel/AopClient.php b/src/Kernel/AopClient.php
index 1644481..1faff67 100755
--- a/src/Kernel/AopClient.php
+++ b/src/Kernel/AopClient.php
@@ -2,10 +2,12 @@
namespace EasyAlipay\Kernel;
+use EasyAlipay\Kernel\Exceptions\InvalidConfigException;
+use function EasyAlipay\Kernel\encrypt;
use Exception;
use EasyAlipay\Kernel\SignData;
-class AopClient
+class AopClient extends BaseClient
{
//应用ID
private $appId;
@@ -48,35 +50,46 @@ class AopClient
protected $alipaySdkVersion = "alipay-sdk-php-easyalipay-20190820";
- public function __construct(ServiceContainer $app){
+ public function __construct(ServiceContainer $app)
+ {
+ parent::__construct($app);
+
$this->appId = $app['config']['app_id'];
$this->rsaPrivateKey = $app['config']['merchant_private_key'];
$this->alipayrsaPublicKey = $app['config']['alipay_public_key'];
$this->postCharset = $app['config']['charset'];
- $this->signType=$app['config']['sign_type'];
+ $this->signType = $app['config']['sign_type'];
$this->gatewayUrl = $app['config']['gateway_url'];
- if(empty($this->appId)||trim($this->appId)==""){
- throw new Exception("appId should not be NULL!");
+ if (empty($this->appId) || trim($this->appId) == "") {
+ throw new InvalidConfigException("appId should not be NULL!");
}
- if(empty($this->rsaPrivateKey)||trim($this->rsaPrivateKey)==""){
- throw new Exception("rsaPrivateKey should not be NULL!");
+ if (empty($this->rsaPrivateKey) || trim($this->rsaPrivateKey) == "") {
+ throw new InvalidConfigException("rsaPrivateKey should not be NULL!");
}
- if(empty($this->alipayrsaPublicKey)||trim($this->alipayrsaPublicKey)==""){
- throw new Exception("alipayPublicKey should not be NULL!");
+ if (empty($this->alipayrsaPublicKey) || trim($this->alipayrsaPublicKey) == "") {
+ throw new InvalidConfigException("alipayPublicKey should not be NULL!");
}
- if(empty($this->postCharset)||trim($this->postCharset)==""){
- throw new Exception("postCharset should not be NULL!");
+ if (empty($this->postCharset) || trim($this->postCharset) == "") {
+ throw new InvalidConfigException("postCharset should not be NULL!");
}
- if(empty($this->signType)||trim($this->signType)==""){
- throw new Exception("signType should not be NULL!");
+ if (empty($this->signType) || trim($this->signType) == "") {
+ throw new InvalidConfigException("signType should not be NULL!");
}
- if(empty($this->gatewayUrl)||trim($this->gatewayUrl)==""){
- throw new Exception("gatewayUrl should not be NULL!");
+ if (empty($this->gatewayUrl) || trim($this->gatewayUrl) == "") {
+ throw new InvalidConfigException("gatewayUrl should not be NULL!");
}
}
- public function execute($request, $authToken = null, $appInfoAuthtoken = null) {
+ /**
+ * @param AopRequest $request
+ * @param null $authToken
+ * @param null $appInfoAuthtoken
+ * @return bool|mixed|\SimpleXMLElement
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function execute(AopRequest $request, $authToken = null, $appInfoAuthtoken = null)
+ {
$this->setupCharsets($request);
//如果两者编码不一致,会出现签名验签或者乱码
if (strcasecmp($this->fileCharset, $this->postCharset)) {
@@ -106,7 +119,7 @@ class AopClient
$sysParams["app_auth_token"] = $appInfoAuthtoken;
//获取业务参数
$apiParams = $request->getApiParas();
- if (method_exists($request,"getNeedEncrypt") &&$request->getNeedEncrypt()){
+ if (method_exists($request, "getNeedEncrypt") && $request->getNeedEncrypt()) {
$sysParams["encrypt_type"] = $this->encryptType;
if ($this->checkEmpty($apiParams['biz_content'])) {
throw new Exception(" api request Fail! The reason : encrypt request is not supperted!");
@@ -131,47 +144,47 @@ class AopClient
$requestUrl = substr($requestUrl, 0, -1);
//发起HTTP请求
try {
- $resp = $this->curl($requestUrl, $apiParams);
+ $resp = $this->httpPost($requestUrl, $apiParams);
+ var_dump($resp);die;
} catch (Exception $e) {
- var_dump("HTTP_ERROR_" . $e->getCode(), $e->getMessage());
+ var_dump($e->getMessage());die;
return false;
}
//解析AOP返回结果
$respWellFormed = false;
// 将返回结果转换本地文件编码
- $r = iconv($this->postCharset, $this->fileCharset . "//IGNORE", $resp);
+// $r = iconv($this->postCharset, $this->fileCharset . "//IGNORE", $resp);
$signData = null;
if ("json" == $this->format) {
- $respObject = json_decode($r);
+ $respObject = $resp;
if (null !== $respObject) {
$respWellFormed = true;
$signData = $this->parserJSONSignData($request, $resp, $respObject);
+ var_dump($signData);die;
}
} else if ("xml" == $this->format) {
$disableLibxmlEntityLoader = libxml_disable_entity_loader(true);
$respObject = @ simplexml_load_string($resp);
if (false !== $respObject) {
$respWellFormed = true;
-
$signData = $this->parserXMLSignData($request, $resp);
}
libxml_disable_entity_loader($disableLibxmlEntityLoader);
}
//返回的HTTP文本不是标准JSON或者XML,记下错误日志
if (false === $respWellFormed) {
- var_dump("HTTP_RESPONSE_NOT_WELL_FORMED_".$resp);
return false;
}
// 验签
$this->checkResponseSign($request, $signData, $resp, $respObject);
// 解密
- if (method_exists($request,"getNeedEncrypt") &&$request->getNeedEncrypt()){
+ if (method_exists($request, "getNeedEncrypt") && $request->getNeedEncrypt()) {
if ("json" == $this->format) {
$resp = $this->encryptJSONSignSource($request, $resp);
// 将返回结果转换本地文件编码
$r = iconv($this->postCharset, $this->fileCharset . "//IGNORE", $resp);
$respObject = json_decode($r);
- }else{
+ } else {
$resp = $this->encryptXMLSignSource($request, $resp);
$r = iconv($this->postCharset, $this->fileCharset . "//IGNORE", $resp);
$disableLibxmlEntityLoader = libxml_disable_entity_loader(true);
@@ -183,116 +196,13 @@ class AopClient
}
- /**
- * 生成用于调用收银台SDK的字符串
- * @param $request SDK接口的请求参数对象
- * @param $appAuthToken 三方应用授权token
- * @return string
- * @author guofa.tgf
- */
- public function sdkExecute($request, $appAuthToken = null) {
- $this->setupCharsets($request);
- $params['app_id'] = $this->appId;
- $params['method'] = $request->getApiMethodName();
- $params['format'] = $this->format;
- $params['sign_type'] = $this->signType;
- $params['timestamp'] = date("Y-m-d H:i:s");
- $params['alipay_sdk'] = $this->alipaySdkVersion;
- $params['charset'] = $this->postCharset;
- $version = $request->getApiVersion();
- $params['version'] = $this->checkEmpty($version) ? $this->apiVersion : $version;
- if ($notify_url = $request->getNotifyUrl()) {
- $params['notify_url'] = $notify_url;
- }
- $params['app_auth_token'] = $appAuthToken;
- $dict = $request->getApiParas();
- $params['biz_content'] = $dict['biz_content'];
- ksort($params);
- $params['sign'] = $this->generateSign($params, $this->signType);
- foreach ($params as &$value) {
- $value = $this->characet($value, $params['charset']);
- }
- return http_build_query($params);
- }
-
- /**
- * 页面提交执行方法
- * @param $request 跳转类接口的request
- * @param string $httpmethod 提交方式,两个值可选:post、get;
- * @param null $appAuthToken 三方应用授权token
- * @return 构建好的、签名后的最终跳转URL(GET)或String形式的form(POST)
- * @throws Exception
- */
- public function pageExecute($request, $httpmethod = "POST", $appAuthToken = null) {
- $this->setupCharsets($request);
- if (strcasecmp($this->fileCharset, $this->postCharset)) {
- // writeLog("本地文件字符集编码与表单提交编码不一致,请务必设置成一样,属性名分别为postCharset!");
- throw new Exception("文件编码:[" . $this->fileCharset . "] 与表单提交编码:[" . $this->postCharset . "]两者不一致!");
- }
- $iv=null;
- if(!$this->checkEmpty($request->getApiVersion())){
- $iv=$request->getApiVersion();
- }else{
- $iv=$this->apiVersion;
- }
- //组装系统参数
- $sysParams["app_id"] = $this->appId;
- $sysParams["version"] = $iv;
- $sysParams["format"] = $this->format;
- $sysParams["sign_type"] = $this->signType;
- $sysParams["method"] = $request->getApiMethodName();
- $sysParams["timestamp"] = date("Y-m-d H:i:s");
- $sysParams["alipay_sdk"] = $this->alipaySdkVersion;
- $sysParams["terminal_type"] = $request->getTerminalType();
- $sysParams["terminal_info"] = $request->getTerminalInfo();
- $sysParams["prod_code"] = $request->getProdCode();
- $sysParams["notify_url"] = $request->getNotifyUrl();
- $sysParams["return_url"] = $request->getReturnUrl();
- $sysParams["charset"] = $this->postCharset;
- $sysParams["app_auth_token"] = $appAuthToken;
- //获取业务参数
- $apiParams = $request->getApiParas();
- if (method_exists($request,"getNeedEncrypt") &&$request->getNeedEncrypt()){
- $sysParams["encrypt_type"] = $this->encryptType;
- if ($this->checkEmpty($apiParams['biz_content'])) {
- throw new Exception(" api request Fail! The reason : encrypt request is not supperted!");
- }
- if ($this->checkEmpty($this->encryptKey) || $this->checkEmpty($this->encryptType)) {
- throw new Exception(" encryptType and encryptKey must not null! ");
- }
- if ("AES" != $this->encryptType) {
- throw new Exception("加密类型只支持AES");
- }
- // 执行加密
- $enCryptContent = encrypt($apiParams['biz_content'], $this->encryptKey);
- $apiParams['biz_content'] = $enCryptContent;
- }
- $totalParams = array_merge($apiParams, $sysParams);
- //待签名字符串
- $preSignStr = $this->getSignContent($totalParams);
- //签名
- $totalParams["sign"] = $this->generateSign($totalParams, $this->signType);
- if ("GET" == strtoupper($httpmethod)) {
- //value做urlencode
- $preString=$this->getSignContentUrlencode($totalParams);
- //拼接GET请求串
- $requestUrl = $this->gatewayUrl."?".$preString;
- return $requestUrl;
- } else {
- //拼接表单字符串
- return $this->buildRequestForm($totalParams);
- }
- }
-
- public function generateSign($params, $signType = "RSA") {
- return $this->sign($this->getSignContent($params), $signType);
- }
-
- public function rsaSign($params, $signType = "RSA") {
+ public function generateSign($params, $signType = "RSA")
+ {
return $this->sign($this->getSignContent($params), $signType);
}
- public function getSignContent($params) {
+ public function getSignContent($params)
+ {
ksort($params);
$stringToBeSigned = "";
$i = 0;
@@ -313,78 +223,24 @@ class AopClient
return $stringToBeSigned;
}
-
- //此方法对value做urlencode
- public function getSignContentUrlencode($params) {
- ksort($params);
- $stringToBeSigned = "";
- $i = 0;
- foreach ($params as $k => $v) {
- if (false === $this->checkEmpty($v) && "@" != substr($v, 0, 1)) {
- // 转换成目标字符集
- $v = $this->characet($v, $this->postCharset);
- if ($i == 0) {
- $stringToBeSigned .= "$k" . "=" . urlencode($v);
- } else {
- $stringToBeSigned .= "&" . "$k" . "=" . urlencode($v);
- }
- $i++;
- }
- }
- unset ($k, $v);
- return $stringToBeSigned;
- }
-
- protected function sign($data, $signType = "RSA") {
- if($this->checkEmpty($this->rsaPrivateKeyFilePath)){
- $priKey=$this->rsaPrivateKey;
+ protected function sign($data, $signType = "RSA")
+ {
+ if ($this->checkEmpty($this->rsaPrivateKeyFilePath)) {
+ $priKey = $this->rsaPrivateKey;
$res = "-----BEGIN RSA PRIVATE KEY-----\n" .
wordwrap($priKey, 64, "\n", true) .
"\n-----END RSA PRIVATE KEY-----";
- }else {
- $priKey = file_get_contents($this->rsaPrivateKeyFilePath);
- $res = openssl_get_privatekey($priKey);
- }
- ($res) or die('您使用的私钥格式错误,请检查RSA私钥配置');
- if ("RSA2" == $signType) {
- openssl_sign($data, $sign, $res, OPENSSL_ALGO_SHA256);
} else {
- openssl_sign($data, $sign, $res);
- }
- if(!$this->checkEmpty($this->rsaPrivateKeyFilePath)){
- openssl_free_key($res);
- }
- $sign = base64_encode($sign);
- return $sign;
- }
-
- /**
- * RSA单独签名方法,未做字符串处理,字符串处理见getSignContent()
- * @param $data 待签名字符串
- * @param $privatekey 商户私钥,根据keyfromfile来判断是读取字符串还是读取文件,false:填写私钥字符串去回车和空格 true:填写私钥文件路径
- * @param $signType 签名方式,RSA:SHA1 RSA2:SHA256
- * @param $keyfromfile 私钥获取方式,读取字符串还是读文件
- * @return string
- * @author mengyu.wh
- */
- public function alonersaSign($data,$privatekey,$signType = "RSA",$keyfromfile=false) {
- if(!$keyfromfile){
- $priKey=$privatekey;
- $res = "-----BEGIN RSA PRIVATE KEY-----\n" .
- wordwrap($priKey, 64, "\n", true) .
- "\n-----END RSA PRIVATE KEY-----";
- }
- else{
- $priKey = file_get_contents($privatekey);
+ $priKey = file_get_contents($this->rsaPrivateKeyFilePath);
$res = openssl_get_privatekey($priKey);
}
- ($res) or die('您使用的私钥格式错误,请检查RSA私钥配置');
+ ($res) or die('您使用的私钥格式错误,请检查RSA私钥配置');
if ("RSA2" == $signType) {
openssl_sign($data, $sign, $res, OPENSSL_ALGO_SHA256);
} else {
openssl_sign($data, $sign, $res);
}
- if($keyfromfile){
+ if (!$this->checkEmpty($this->rsaPrivateKeyFilePath)) {
openssl_free_key($res);
}
$sign = base64_encode($sign);
@@ -392,7 +248,8 @@ class AopClient
}
- protected function curl($url, $postFields = null) {
+ protected function curl($url, $postFields = null)
+ {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FAILONERROR, false);
@@ -440,61 +297,20 @@ class AopClient
return $reponse;
}
- protected function getMillisecond() {
+ protected function getMillisecond()
+ {
list($s1, $s2) = explode(' ', microtime());
return (float)sprintf('%.0f', (floatval($s1) + floatval($s2)) * 1000);
}
-
- protected function logCommunicationError($apiName, $requestUrl, $errorCode, $responseTxt) {
- $localIp = isset ($_SERVER["SERVER_ADDR"]) ? $_SERVER["SERVER_ADDR"] : "CLI";
- $logger = new LtLogger;
- $logger->conf["log_file"] = rtrim(AOP_SDK_WORK_DIR, '\\/') . '/' . "logs/aop_comm_err_" . $this->appId . "_" . date("Y-m-d") . ".log";
- $logger->conf["separator"] = "^_^";
- $logData = array(
- date("Y-m-d H:i:s"),
- $apiName,
- $this->appId,
- $localIp,
- PHP_OS,
- $this->alipaySdkVersion,
- $requestUrl,
- $errorCode,
- str_replace("\n", "", $responseTxt)
- );
- $logger->log($logData);
- }
-
-
- /**
- * 建立请求,以表单HTML形式构造(默认)
- * @param $para_temp 请求参数数组
- * @return 提交表单HTML文本
- */
- protected function buildRequestForm($para_temp) {
- $sHtml = "";
- $sHtml = $sHtml."";
- return $sHtml;
- }
-
-
/**
* 转换字符集编码
* @param $data
* @param $targetCharset
* @return string
*/
- function characet($data, $targetCharset) {
+ function characet($data, $targetCharset)
+ {
if (!empty($data)) {
$fileType = $this->fileCharset;
if (strcasecmp($fileType, $targetCharset) != 0) {
@@ -505,36 +321,13 @@ class AopClient
return $data;
}
- public function exec($paramsArray) {
- if (!isset ($paramsArray["method"])) {
- trigger_error("No api name passed");
- }
- $inflector = new LtInflector;
- $inflector->conf["separator"] = ".";
- $requestClassName = ucfirst($inflector->camelize(substr($paramsArray["method"], 7))) . "Request";
- if (!class_exists($requestClassName)) {
- trigger_error("No such api: " . $paramsArray["method"]);
- }
- $session = isset ($paramsArray["session"]) ? $paramsArray["session"] : null;
- $req = new $requestClassName;
- foreach ($paramsArray as $paraKey => $paraValue) {
- $inflector->conf["separator"] = "_";
- $setterMethodName = $inflector->camelize($paraKey);
- $inflector->conf["separator"] = ".";
- $setterMethodName = "set" . $inflector->camelize($setterMethodName);
- if (method_exists($req, $setterMethodName)) {
- $req->$setterMethodName ($paraValue);
- }
- }
- return $this->execute($req, $session);
- }
-
/**
* 校验$value是否非空
* if not set ,return true;
* if is null , return true;
**/
- protected function checkEmpty($value) {
+ protected function checkEmpty($value)
+ {
if (!isset($value))
return true;
if ($value === null)
@@ -544,184 +337,45 @@ class AopClient
return false;
}
- /** rsaCheckV1 & rsaCheckV2
- * 验证签名
- * 在使用本方法前,必须初始化AopClient且传入公钥参数。
- * 公钥是否是读取字符串还是读取文件,是根据初始化传入的值判断的。
- **/
- public function rsaCheckV1($params, $rsaPublicKeyFilePath,$signType='RSA') {
- $sign = $params['sign'];
- $params['sign_type'] = null;
- $params['sign'] = null;
- return $this->verify($this->getSignContent($params), $sign, $rsaPublicKeyFilePath,$signType);
- }
- public function rsaCheckV2($params, $rsaPublicKeyFilePath, $signType='RSA') {
+
+ public function rsaCheckV2($params, $rsaPublicKeyFilePath, $signType = 'RSA')
+ {
$sign = $params['sign'];
$params['sign'] = null;
return $this->verify($this->getSignContent($params), $sign, $rsaPublicKeyFilePath, $signType);
}
- function verify($data, $sign, $rsaPublicKeyFilePath, $signType = 'RSA') {
- if($this->checkEmpty($this->alipayPublicKey)){
- $pubKey= $this->alipayrsaPublicKey;
+ function verify($data, $sign, $rsaPublicKeyFilePath, $signType = 'RSA')
+ {
+ if ($this->checkEmpty($this->alipayPublicKey)) {
+ $pubKey = $this->alipayrsaPublicKey;
$res = "-----BEGIN PUBLIC KEY-----\n" .
wordwrap($pubKey, 64, "\n", true) .
"\n-----END PUBLIC KEY-----";
- }else {
+ } else {
//读取公钥文件
$pubKey = file_get_contents($rsaPublicKeyFilePath);
//转换为openssl格式密钥
$res = openssl_get_publickey($pubKey);
}
- ($res) or die('支付宝RSA公钥错误。请检查公钥文件格式是否正确');
+ ($res) or die('支付宝RSA公钥错误。请检查公钥文件格式是否正确');
//调用openssl内置方法验签,返回bool值
$result = FALSE;
if ("RSA2" == $signType) {
- $result = (openssl_verify($data, base64_decode($sign), $res, OPENSSL_ALGO_SHA256)===1);
+ $result = (openssl_verify($data, base64_decode($sign), $res, OPENSSL_ALGO_SHA256) === 1);
} else {
- $result = (openssl_verify($data, base64_decode($sign), $res)===1);
+ $result = (openssl_verify($data, base64_decode($sign), $res) === 1);
}
- if(!$this->checkEmpty($this->alipayPublicKey)) {
+ if (!$this->checkEmpty($this->alipayPublicKey)) {
//释放资源
openssl_free_key($res);
}
return $result;
}
-/**
- * 在使用本方法前,必须初始化AopClient且传入公私钥参数。
- * 公钥是否是读取字符串还是读取文件,是根据初始化传入的值判断的。
- **/
- public function checkSignAndDecrypt($params, $rsaPublicKeyPem, $rsaPrivateKeyPem, $isCheckSign, $isDecrypt, $signType='RSA') {
- $charset = $params['charset'];
- $bizContent = $params['biz_content'];
- if ($isCheckSign) {
- if (!$this->rsaCheckV2($params, $rsaPublicKeyPem, $signType)) {
- echo "
checkSign failure
";
- exit;
- }
- }
- if ($isDecrypt) {
- return $this->rsaDecrypt($bizContent, $rsaPrivateKeyPem, $charset);
- }
- return $bizContent;
- }
-
- /**
- * 在使用本方法前,必须初始化AopClient且传入公私钥参数。
- * 公钥是否是读取字符串还是读取文件,是根据初始化传入的值判断的。
- **/
- public function encryptAndSign($bizContent, $rsaPublicKeyPem, $rsaPrivateKeyPem, $charset, $isEncrypt, $isSign, $signType='RSA') {
- // 加密,并签名
- if ($isEncrypt && $isSign) {
- $encrypted = $this->rsaEncrypt($bizContent, $rsaPublicKeyPem, $charset);
- $sign = $this->sign($encrypted, $signType);
- $response = "$encryptedRSA$sign$signType";
- return $response;
- }
- // 加密,不签名
- if ($isEncrypt && (!$isSign)) {
- $encrypted = $this->rsaEncrypt($bizContent, $rsaPublicKeyPem, $charset);
- $response = "$encrypted$signType";
- return $response;
- }
- // 不加密,但签名
- if ((!$isEncrypt) && $isSign) {
- $sign = $this->sign($bizContent, $signType);
- $response = "$bizContent$sign$signType";
- return $response;
- }
- // 不加密,不签名
- $response = "$bizContent";
- return $response;
- }
-
- /**
- * 在使用本方法前,必须初始化AopClient且传入公私钥参数。
- * 公钥是否是读取字符串还是读取文件,是根据初始化传入的值判断的。
- **/
- public function rsaEncrypt($data, $rsaPublicKeyPem, $charset) {
- if($this->checkEmpty($this->alipayPublicKey)){
- //读取字符串
- $pubKey= $this->alipayrsaPublicKey;
- $res = "-----BEGIN PUBLIC KEY-----\n" .
- wordwrap($pubKey, 64, "\n", true) .
- "\n-----END PUBLIC KEY-----";
- }else {
- //读取公钥文件
- $pubKey = file_get_contents($rsaPublicKeyFilePath);
- //转换为openssl格式密钥
- $res = openssl_get_publickey($pubKey);
- }
- ($res) or die('支付宝RSA公钥错误。请检查公钥文件格式是否正确');
- $blocks = $this->splitCN($data, 0, 30, $charset);
- $chrtext = null;
- $encodes = array();
- foreach ($blocks as $n => $block) {
- if (!openssl_public_encrypt($block, $chrtext , $res)) {
- echo "
" . openssl_error_string() . "
";
- }
- $encodes[] = $chrtext ;
- }
- $chrtext = implode(",", $encodes);
- return base64_encode($chrtext);
- }
-
- /**
- * 在使用本方法前,必须初始化AopClient且传入公私钥参数。
- * 公钥是否是读取字符串还是读取文件,是根据初始化传入的值判断的。
- **/
- public function rsaDecrypt($data, $rsaPrivateKeyPem, $charset) {
- if($this->checkEmpty($this->rsaPrivateKeyFilePath)){
- //读字符串
- $priKey=$this->rsaPrivateKey;
- $res = "-----BEGIN RSA PRIVATE KEY-----\n" .
- wordwrap($priKey, 64, "\n", true) .
- "\n-----END RSA PRIVATE KEY-----";
- }else {
- $priKey = file_get_contents($this->rsaPrivateKeyFilePath);
- $res = openssl_get_privatekey($priKey);
- }
- ($res) or die('您使用的私钥格式错误,请检查RSA私钥配置');
- //转换为openssl格式密钥
- $decodes = explode(',', $data);
- $strnull = "";
- $dcyCont = "";
- foreach ($decodes as $n => $decode) {
- if (!openssl_private_decrypt($decode, $dcyCont, $res)) {
- echo "
" . openssl_error_string() . "
";
- }
- $strnull .= $dcyCont;
- }
- return $strnull;
- }
-
- function splitCN($cont, $n = 0, $subnum, $charset) {
- //$len = strlen($cont) / 3;
- $arrr = array();
- for ($i = $n; $i < strlen($cont); $i += $subnum) {
- $res = $this->subCNchar($cont, $i, $subnum, $charset);
- if (!empty ($res)) {
- $arrr[] = $res;
- }
- }
- return $arrr;
- }
-
- function subCNchar($str, $start = 0, $length, $charset = "gbk") {
- if (strlen($str) <= $length) {
- return $str;
- }
- $re['utf-8'] = "/[\x01-\x7f]|[\xc2-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xff][\x80-\xbf]{3}/";
- $re['gb2312'] = "/[\x01-\x7f]|[\xb0-\xf7][\xa0-\xfe]/";
- $re['gbk'] = "/[\x01-\x7f]|[\x81-\xfe][\x40-\xfe]/";
- $re['big5'] = "/[\x01-\x7f]|[\x81-\xfe]([\x40-\x7e]|\xa1-\xfe])/";
- preg_match_all($re[$charset], $str, $match);
- $slice = join("", array_slice($match[0], $start, $length));
- return $slice;
- }
- function parserResponseSubCode($request, $responseContent, $respObject, $format) {
+ function parserResponseSubCode($request, $responseContent, $respObject, $format)
+ {
if ("json" == $format) {
$apiName = $request->getApiMethodName();
$rootNodeName = str_replace(".", "_", $apiName) . $this->RESPONSE_SUFFIX;
@@ -748,14 +402,16 @@ class AopClient
}
}
- function parserJSONSignData($request, $responseContent, $responseJSON) {
+ function parserJSONSignData($request, $responseContent, $responseJSON)
+ {
$signData = new SignData();
$signData->sign = $this->parserJSONSign($responseJSON);
$signData->signSourceData = $this->parserJSONSignSource($request, $responseContent);
return $signData;
}
- function parserJSONSignSource($request, $responseContent) {
+ function parserJSONSignSource($request, $responseContent)
+ {
$apiName = $request->getApiMethodName();
$rootNodeName = str_replace(".", "_", $apiName) . $this->RESPONSE_SUFFIX;
$rootIndex = strpos($responseContent, $rootNodeName);
@@ -769,7 +425,8 @@ class AopClient
}
}
- function parserJSONSource($responseContent, $nodeName, $nodeIndex) {
+ function parserJSONSource($responseContent, $nodeName, $nodeIndex)
+ {
$signDataStartIndex = $nodeIndex + strlen($nodeName) + 2;
$signIndex = strrpos($responseContent, "\"" . $this->SIGN_NODE_NAME . "\"");
// 签名前-逗号
@@ -781,18 +438,21 @@ class AopClient
return substr($responseContent, $signDataStartIndex, $indexLen);
}
- function parserJSONSign($responseJSon) {
+ function parserJSONSign($responseJSon)
+ {
return $responseJSon->sign;
}
- function parserXMLSignData($request, $responseContent) {
+ function parserXMLSignData($request, $responseContent)
+ {
$signData = new SignData();
$signData->sign = $this->parserXMLSign($responseContent);
$signData->signSourceData = $this->parserXMLSignSource($request, $responseContent);
return $signData;
}
- function parserXMLSignSource($request, $responseContent) {
+ function parserXMLSignSource($request, $responseContent)
+ {
$apiName = $request->getApiMethodName();
$rootNodeName = str_replace(".", "_", $apiName) . $this->RESPONSE_SUFFIX;
$rootIndex = strpos($responseContent, $rootNodeName);
@@ -806,7 +466,8 @@ class AopClient
}
}
- function parserXMLSource($responseContent, $nodeName, $nodeIndex) {
+ function parserXMLSource($responseContent, $nodeName, $nodeIndex)
+ {
$signDataStartIndex = $nodeIndex + strlen($nodeName) + 1;
$signIndex = strrpos($responseContent, "<" . $this->SIGN_NODE_NAME . ">");
// 签名前-逗号
@@ -818,7 +479,8 @@ class AopClient
return substr($responseContent, $signDataStartIndex, $indexLen);
}
- function parserXMLSign($responseContent) {
+ function parserXMLSign($responseContent)
+ {
$signNodeName = "<" . $this->SIGN_NODE_NAME . ">";
$signEndNodeName = "" . $this->SIGN_NODE_NAME . ">";
$indexOfSignNode = strpos($responseContent, $signNodeName);
@@ -843,7 +505,8 @@ class AopClient
* @param $respObject
* @throws Exception
*/
- public function checkResponseSign($request, $signData, $resp, $respObject) {
+ public function checkResponseSign(AopRequest $request, $signData, $resp, $respObject)
+ {
if (!$this->checkEmpty($this->alipayPublicKey) || !$this->checkEmpty($this->alipayrsaPublicKey)) {
if ($signData == null || $this->checkEmpty($signData->sign) || $this->checkEmpty($signData->signSourceData)) {
throw new Exception(" check sign Fail! The reason : signData is Empty");
@@ -867,7 +530,8 @@ class AopClient
}
}
- private function setupCharsets($request) {
+ private function setupCharsets($request)
+ {
if ($this->checkEmpty($this->postCharset)) {
$this->postCharset = 'UTF-8';
}
@@ -877,7 +541,8 @@ class AopClient
// 获取加密内容
- private function encryptJSONSignSource($request, $responseContent) {
+ private function encryptJSONSignSource($request, $responseContent)
+ {
$parsetItem = $this->parserEncryptJSONSignSource($request, $responseContent);
$bodyIndexContent = substr($responseContent, 0, $parsetItem->startIndex);
$bodyEndContent = substr($responseContent, $parsetItem->endIndex, strlen($responseContent) + 1 - $parsetItem->endIndex);
@@ -886,7 +551,8 @@ class AopClient
}
- private function parserEncryptJSONSignSource($request, $responseContent) {
+ private function parserEncryptJSONSignSource($request, $responseContent)
+ {
$apiName = $request->getApiMethodName();
$rootNodeName = str_replace(".", "_", $apiName) . $this->RESPONSE_SUFFIX;
$rootIndex = strpos($responseContent, $rootNodeName);
@@ -900,16 +566,17 @@ class AopClient
}
}
- private function parserEncryptJSONItem($responseContent, $nodeName, $nodeIndex) {
+ private function parserEncryptJSONItem($responseContent, $nodeName, $nodeIndex)
+ {
$signDataStartIndex = $nodeIndex + strlen($nodeName) + 2;
$signIndex = strpos($responseContent, "\"" . $this->SIGN_NODE_NAME . "\"");
// 签名前-逗号
$signDataEndIndex = $signIndex - 1;
if ($signDataEndIndex < 0) {
- $signDataEndIndex = strlen($responseContent)-1 ;
+ $signDataEndIndex = strlen($responseContent) - 1;
}
$indexLen = $signDataEndIndex - $signDataStartIndex;
- $encContent = substr($responseContent, $signDataStartIndex+1, $indexLen-2);
+ $encContent = substr($responseContent, $signDataStartIndex + 1, $indexLen - 2);
$encryptParseItem = new EncryptParseItem();
$encryptParseItem->encryptContent = $encContent;
$encryptParseItem->startIndex = $signDataStartIndex;
@@ -919,7 +586,8 @@ class AopClient
// 获取加密内容
- private function encryptXMLSignSource($request, $responseContent) {
+ private function encryptXMLSignSource($request, $responseContent)
+ {
$parsetItem = $this->parserEncryptXMLSignSource($request, $responseContent);
$bodyIndexContent = substr($responseContent, 0, $parsetItem->startIndex);
$bodyEndContent = substr($responseContent, $parsetItem->endIndex, strlen($responseContent) + 1 - $parsetItem->endIndex);
@@ -927,7 +595,8 @@ class AopClient
return $bodyIndexContent . $bizContent . $bodyEndContent;
}
- private function parserEncryptXMLSignSource($request, $responseContent) {
+ private function parserEncryptXMLSignSource($request, $responseContent)
+ {
$apiName = $request->getApiMethodName();
$rootNodeName = str_replace(".", "_", $apiName) . $this->RESPONSE_SUFFIX;
$rootIndex = strpos($responseContent, $rootNodeName);
@@ -942,29 +611,31 @@ class AopClient
}
}
- private function parserEncryptXMLItem($responseContent, $nodeName, $nodeIndex) {
+ private function parserEncryptXMLItem($responseContent, $nodeName, $nodeIndex)
+ {
$signDataStartIndex = $nodeIndex + strlen($nodeName) + 1;
- $xmlStartNode="<".$this->ENCRYPT_XML_NODE_NAME.">";
- $xmlEndNode="".$this->ENCRYPT_XML_NODE_NAME.">";
- $indexOfXmlNode=strpos($responseContent,$xmlEndNode);
- if($indexOfXmlNode<0){
+ $xmlStartNode = "<" . $this->ENCRYPT_XML_NODE_NAME . ">";
+ $xmlEndNode = "" . $this->ENCRYPT_XML_NODE_NAME . ">";
+ $indexOfXmlNode = strpos($responseContent, $xmlEndNode);
+ if ($indexOfXmlNode < 0) {
$item = new EncryptParseItem();
$item->encryptContent = null;
$item->startIndex = 0;
$item->endIndex = 0;
return $item;
}
- $startIndex=$signDataStartIndex+strlen($xmlStartNode);
- $bizContentLen=$indexOfXmlNode-$startIndex;
- $bizContent=substr($responseContent,$startIndex,$bizContentLen);
+ $startIndex = $signDataStartIndex + strlen($xmlStartNode);
+ $bizContentLen = $indexOfXmlNode - $startIndex;
+ $bizContent = substr($responseContent, $startIndex, $bizContentLen);
$encryptParseItem = new EncryptParseItem();
$encryptParseItem->encryptContent = $bizContent;
$encryptParseItem->startIndex = $signDataStartIndex;
- $encryptParseItem->endIndex = $indexOfXmlNode+strlen($xmlEndNode);
+ $encryptParseItem->endIndex = $indexOfXmlNode + strlen($xmlEndNode);
return $encryptParseItem;
}
- function echoDebug($content) {
+ function echoDebug($content)
+ {
if ($this->debugInfo) {
echo "
" . $content;
}
diff --git a/src/Kernel/BaseClient.php b/src/Kernel/BaseClient.php
new file mode 100644
index 0000000..4da1c4d
--- /dev/null
+++ b/src/Kernel/BaseClient.php
@@ -0,0 +1,216 @@
+app = $app;
+ }
+
+ /**
+ * GET request.
+ *
+ * @param string $url
+ * @param array $query
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyAlipay\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyAlipay\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function httpGet(string $url, array $query = [])
+ {
+ return $this->request($url, 'GET', ['query' => $query]);
+ }
+
+ /**
+ * POST request.
+ *
+ * @param string $url
+ * @param array $data
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyAlipay\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyAlipay\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function httpPost(string $url, array $data = [])
+ {
+ return $this->request($url, 'POST', ['form_params' => $data]);
+ }
+
+ /**
+ * JSON request.
+ *
+ * @param string $url
+ * @param array $data
+ * @param array $query
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyAlipay\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyAlipay\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function httpPostJson(string $url, array $data = [], array $query = [])
+ {
+ return $this->request($url, 'POST', ['query' => $query, 'json' => $data]);
+ }
+
+ /**
+ * Upload file.
+ *
+ * @param string $url
+ * @param array $files
+ * @param array $form
+ * @param array $query
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyAlipay\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyAlipay\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function httpUpload(string $url, array $files = [], array $form = [], array $query = [])
+ {
+ $multipart = [];
+
+ foreach ($files as $name => $path) {
+ $multipart[] = [
+ 'name' => $name,
+ 'contents' => fopen($path, 'r'),
+ ];
+ }
+
+ foreach ($form as $name => $contents) {
+ $multipart[] = compact('name', 'contents');
+ }
+
+ return $this->request($url, 'POST', ['query' => $query, 'multipart' => $multipart, 'connect_timeout' => 30, 'timeout' => 30, 'read_timeout' => 30]);
+ }
+
+ /**
+ * @param string $url
+ * @param string $method
+ * @param array $options
+ * @param bool $returnRaw
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyAlipay\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyAlipay\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function request(string $url, string $method = 'GET', array $options = [], $returnRaw = false)
+ {
+ if (empty($this->middlewares)) {
+ $this->registerHttpMiddlewares();
+ }
+
+ $response = $this->performRequest($url, $method, $options);
+
+ $this->app->events->dispatch(new Events\HttpResponseCreated($response));
+
+ return $returnRaw ? $response : $this->castResponseToType($response, $this->app->config->get('response_type'));
+ }
+
+ /**
+ * @param string $url
+ * @param string $method
+ * @param array $options
+ *
+ * @return \EasyAlipay\Kernel\Http\Response
+ *
+ * @throws \EasyAlipay\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function requestRaw(string $url, string $method = 'GET', array $options = [])
+ {
+ return Response::buildFromPsrResponse($this->request($url, $method, $options, true));
+ }
+
+ /**
+ * Register Guzzle middlewares.
+ */
+ protected function registerHttpMiddlewares()
+ {
+ // retry
+ $this->pushMiddleware($this->retryMiddleware(), 'retry');
+ // log
+ $this->pushMiddleware($this->logMiddleware(), 'log');
+ }
+
+ /**
+ * Log the request.
+ *
+ * @return \Closure
+ */
+ protected function logMiddleware()
+ {
+ $formatter = new MessageFormatter($this->app['config']['http.log_template'] ?? MessageFormatter::DEBUG);
+
+ return Middleware::log($this->app['logger'], $formatter, LogLevel::DEBUG);
+ }
+
+ /**
+ * Return retry middleware.
+ *
+ * @return \Closure
+ */
+ protected function retryMiddleware()
+ {
+ return Middleware::retry(function (
+ $retries,
+ RequestInterface $request,
+ ResponseInterface $response = null
+ ) {
+ // Limit the number of retries to 2
+ if ($retries < $this->app->config->get('http.max_retries', 1) && $response && $body = $response->getBody()) {
+ // Retry on server errors
+ $response = json_decode($body, true);
+
+ if (!empty($response['errcode']) && in_array(abs($response['errcode']), [40001, 40014, 42001], true)) {
+ $this->accessToken->refresh();
+ $this->app['logger']->debug('Retrying with refreshed access token.');
+
+ return true;
+ }
+ }
+
+ return false;
+ }, function () {
+ return abs($this->app->config->get('http.retry_delay', 500));
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/Kernel/Config.php b/src/Kernel/Config.php
index 5c7fea7..1c30703 100755
--- a/src/Kernel/Config.php
+++ b/src/Kernel/Config.php
@@ -2,26 +2,9 @@
namespace EasyAlipay\Kernel;
-class Config
+use EasyAlipay\Kernel\Support\Collection;
+
+class Config extends Collection
{
- public $config = [
- //应用ID
- 'app_id' => '',
- //支付宝公钥
- 'alipay_public_key' => '',
- //商户私钥
- 'merchant_private_key' => '',
- //网管地址
- 'gateway_url' => "https://openapi.alipay.com/gateway.do",
- //异步通知地址
- 'notify_url' => "",
- //同步跳转
- 'return_url' => "",
- //编码格式
- 'charset' => "UTF-8",
- //签名方式,默认为RSA2(RSA2048)
- 'sign_type' =>"RSA2",
- // ...
- ];
}
diff --git a/src/Kernel/Contracts/Arrayable.php b/src/Kernel/Contracts/Arrayable.php
new file mode 100644
index 0000000..9818685
--- /dev/null
+++ b/src/Kernel/Contracts/Arrayable.php
@@ -0,0 +1,21 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyAlipay\Kernel\Events;
+
+use EasyAlipay\Kernel\ServiceContainer;
+
+/**
+ * Class ApplicationInitialized.
+ *
+ * @author mingyoung
+ */
+class ApplicationInitialized
+{
+ /**
+ * @var \EasyAlipay\Kernel\ServiceContainer
+ */
+ public $app;
+
+ /**
+ * @param \EasyAlipay\Kernel\ServiceContainer $app
+ */
+ public function __construct(ServiceContainer $app)
+ {
+ $this->app = $app;
+ }
+}
diff --git a/src/Kernel/Events/EventHandlerInterface.php b/src/Kernel/Events/EventHandlerInterface.php
new file mode 100644
index 0000000..3b063e2
--- /dev/null
+++ b/src/Kernel/Events/EventHandlerInterface.php
@@ -0,0 +1,25 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyAlipay\Kernel\Contracts;
+
+/**
+ * Interface EventHandlerInterface.
+ *
+ * @author mingyoung
+ */
+interface EventHandlerInterface
+{
+ /**
+ * @param mixed $payload
+ */
+ public function handle($payload = null);
+}
diff --git a/src/Kernel/Events/HttpResponseCreated.php b/src/Kernel/Events/HttpResponseCreated.php
new file mode 100644
index 0000000..af02ead
--- /dev/null
+++ b/src/Kernel/Events/HttpResponseCreated.php
@@ -0,0 +1,35 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyAlipay\Kernel\Events;
+
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Class HttpResponseCreated.
+ *
+ * @author mingyoung
+ */
+class HttpResponseCreated
+{
+ /**
+ * @var \Psr\Http\Message\ResponseInterface
+ */
+ public $response;
+
+ /**
+ * @param \Psr\Http\Message\ResponseInterface $response
+ */
+ public function __construct(ResponseInterface $response)
+ {
+ $this->response = $response;
+ }
+}
diff --git a/src/Kernel/Events/ServerGuardResponseCreated.php b/src/Kernel/Events/ServerGuardResponseCreated.php
new file mode 100644
index 0000000..2b3722e
--- /dev/null
+++ b/src/Kernel/Events/ServerGuardResponseCreated.php
@@ -0,0 +1,35 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyAlipay\Kernel\Events;
+
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Class ServerGuardResponseCreated.
+ *
+ * @author mingyoung
+ */
+class ServerGuardResponseCreated
+{
+ /**
+ * @var \Symfony\Component\HttpFoundation\Response
+ */
+ public $response;
+
+ /**
+ * @param \Symfony\Component\HttpFoundation\Response $response
+ */
+ public function __construct(Response $response)
+ {
+ $this->response = $response;
+ }
+}
diff --git a/src/Kernel/Exceptions/BadRequestException.php b/src/Kernel/Exceptions/BadRequestException.php
new file mode 100644
index 0000000..bc2320d
--- /dev/null
+++ b/src/Kernel/Exceptions/BadRequestException.php
@@ -0,0 +1,12 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyAlipay\Kernel\Exceptions;
+
+/**
+ * Class InvalidArgumentException.
+ *
+ * @author overtrue
+ */
+class InvalidArgumentException extends Exception
+{
+}
diff --git a/src/Kernel/Exceptions/InvalidConfigException.php b/src/Kernel/Exceptions/InvalidConfigException.php
new file mode 100644
index 0000000..95abaef
--- /dev/null
+++ b/src/Kernel/Exceptions/InvalidConfigException.php
@@ -0,0 +1,21 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyAlipay\Kernel\Exceptions;
+
+/**
+ * Class InvalidConfigException.
+ *
+ * @author overtrue
+ */
+class InvalidConfigException extends Exception
+{
+}
diff --git a/src/Kernel/Exceptions/RuntimeException.php b/src/Kernel/Exceptions/RuntimeException.php
new file mode 100644
index 0000000..d2a1dfd
--- /dev/null
+++ b/src/Kernel/Exceptions/RuntimeException.php
@@ -0,0 +1,13 @@
+app = $app;
+ $this->manifestPath = __DIR__.'/../extensions.php';
+ }
+
+ /**
+ * Get observers.
+ *
+ * @return array
+ */
+ public function observers(): array
+ {
+ if ($this->shouldIgnore()) {
+ return [];
+ }
+
+ $observers = [];
+
+ foreach ($this->getManifest() as $name => $extra) {
+ $observers = array_merge($observers, $extra['observers'] ?? []);
+ }
+
+ return array_map([$this, 'listObserver'], array_filter($observers, [$this, 'validateObserver']));
+ }
+
+ /**
+ * @param mixed $observer
+ *
+ * @return bool
+ */
+ protected function isDisable($observer): bool
+ {
+ return in_array($observer, $this->app->config->get('disable_observers', []));
+ }
+
+ /**
+ * Get the observers should be ignore.
+ *
+ * @return bool
+ */
+ protected function shouldIgnore(): bool
+ {
+ return !file_exists($this->manifestPath) || $this->isDisable('*');
+ }
+
+ /**
+ * Validate the given observer.
+ *
+ * @param mixed $observer
+ *
+ * @return bool
+ *
+ * @throws \ReflectionException
+ */
+ protected function validateObserver($observer): bool
+ {
+ return !$this->isDisable($observer)
+ && (new ReflectionClass($observer))->implementsInterface(EventHandlerInterface::class)
+ && $this->accessible($observer);
+ }
+
+ /**
+ * Determine whether the given observer is accessible.
+ *
+ * @param string $observer
+ *
+ * @return bool
+ */
+ protected function accessible($observer): bool
+ {
+ if (!method_exists($observer, 'getAccessor')) {
+ return true;
+ }
+
+ return in_array(get_class($this->app), (array) $observer::getAccessor());
+ }
+
+ /**
+ * @param mixed $observer
+ *
+ * @return array
+ */
+ protected function listObserver($observer): array
+ {
+ $condition = method_exists($observer, 'onCondition') ? $observer::onCondition() : '*';
+
+ return [$observer, $condition];
+ }
+
+ /**
+ * Get the easyalipay manifest.
+ *
+ * @return array
+ */
+ protected function getManifest(): array
+ {
+ if (!is_null($this->manifest)) {
+ return $this->manifest;
+ }
+
+ return $this->manifest = file_exists($this->manifestPath) ? require $this->manifestPath : [];
+ }
+}
diff --git a/src/Kernel/Helpers.php b/src/Kernel/Helpers.php
new file mode 100644
index 0000000..6a3bb38
--- /dev/null
+++ b/src/Kernel/Helpers.php
@@ -0,0 +1,82 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyAlipay\Kernel\Http;
+
+use EasyAlipay\Kernel\Support\Collection;
+use EasyAlipay\Kernel\Support\XML;
+use GuzzleHttp\Psr7\Response as GuzzleResponse;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Class Response.
+ *
+ * @author overtrue
+ */
+class Response extends GuzzleResponse
+{
+ /**
+ * @return string
+ */
+ public function getBodyContents()
+ {
+ $this->getBody()->rewind();
+ $contents = $this->getBody()->getContents();
+ $this->getBody()->rewind();
+
+ return $contents;
+ }
+
+ /**
+ * @param \Psr\Http\Message\ResponseInterface $response
+ *
+ * @return \EasyAlipay\Kernel\Http\Response
+ */
+ public static function buildFromPsrResponse(ResponseInterface $response)
+ {
+ return new static(
+ $response->getStatusCode(),
+ $response->getHeaders(),
+ $response->getBody(),
+ $response->getProtocolVersion(),
+ $response->getReasonPhrase()
+ );
+ }
+
+ /**
+ * Build to json.
+ *
+ * @return string
+ */
+ public function toJson()
+ {
+ return json_encode($this->toArray());
+ }
+
+ /**
+ * Build to array.
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ $content = $this->removeControlCharacters($this->getBodyContents());
+
+ if (false !== stripos($this->getHeaderLine('Content-Type'), 'xml') || 0 === stripos($content, 'toArray());
+ }
+
+ /**
+ * @return object
+ */
+ public function toObject()
+ {
+ return json_decode($this->toJson());
+ }
+
+ /**
+ * @return bool|string
+ */
+ public function __toString()
+ {
+ return $this->getBodyContents();
+ }
+
+ /**
+ * @param string $content
+ *
+ * @return string
+ */
+ protected function removeControlCharacters(string $content)
+ {
+ return \preg_replace('/[\x00-\x1F\x80-\x9F]/u', '', $content);
+ }
+}
diff --git a/src/Kernel/Http/StreamResponse.php b/src/Kernel/Http/StreamResponse.php
new file mode 100644
index 0000000..ff39048
--- /dev/null
+++ b/src/Kernel/Http/StreamResponse.php
@@ -0,0 +1,86 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyAlipay\Kernel\Http;
+
+use EasyAlipay\Kernel\Exceptions\InvalidArgumentException;
+use EasyAlipay\Kernel\Exceptions\RuntimeException;
+use EasyAlipay\Kernel\Support\File;
+
+/**
+ * Class StreamResponse.
+ *
+ * @author overtrue
+ */
+class StreamResponse extends Response
+{
+ /**
+ * @param string $directory
+ * @param string $filename
+ * @param bool $appendSuffix
+ *
+ * @return bool|int
+ *
+ * @throws \EasyAlipay\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyAlipay\Kernel\Exceptions\RuntimeException
+ */
+ public function save(string $directory, string $filename = '', bool $appendSuffix = true)
+ {
+ $this->getBody()->rewind();
+
+ $directory = rtrim($directory, '/');
+
+ if (!is_dir($directory)) {
+ mkdir($directory, 0755, true); // @codeCoverageIgnore
+ }
+
+ if (!is_writable($directory)) {
+ throw new InvalidArgumentException(sprintf("'%s' is not writable.", $directory));
+ }
+
+ $contents = $this->getBody()->getContents();
+
+ if (empty($contents) || '{' === $contents[0]) {
+ throw new RuntimeException('Invalid media response content.');
+ }
+
+ if (empty($filename)) {
+ if (preg_match('/filename="(?.*?)"/', $this->getHeaderLine('Content-Disposition'), $match)) {
+ $filename = $match['filename'];
+ } else {
+ $filename = md5($contents);
+ }
+ }
+
+ if ($appendSuffix && empty(pathinfo($filename, PATHINFO_EXTENSION))) {
+ $filename .= File::getStreamExt($contents);
+ }
+
+ file_put_contents($directory.'/'.$filename, $contents);
+
+ return $filename;
+ }
+
+ /**
+ * @param string $directory
+ * @param string $filename
+ * @param bool $appendSuffix
+ *
+ * @return bool|int
+ *
+ * @throws \EasyAlipay\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyAlipay\Kernel\Exceptions\RuntimeException
+ */
+ public function saveAs(string $directory, string $filename, bool $appendSuffix = true)
+ {
+ return $this->save($directory, $filename, $appendSuffix);
+ }
+}
diff --git a/src/Kernel/Log/LogManager.php b/src/Kernel/Log/LogManager.php
new file mode 100644
index 0000000..4948a04
--- /dev/null
+++ b/src/Kernel/Log/LogManager.php
@@ -0,0 +1,608 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyAlipay\Kernel\Log;
+
+use EasyAlipay\Kernel\ServiceContainer;
+use InvalidArgumentException;
+use Monolog\Formatter\LineFormatter;
+use Monolog\Handler\ErrorLogHandler;
+use Monolog\Handler\FormattableHandlerInterface;
+use Monolog\Handler\HandlerInterface;
+use Monolog\Handler\RotatingFileHandler;
+use Monolog\Handler\SlackWebhookHandler;
+use Monolog\Handler\StreamHandler;
+use Monolog\Handler\SyslogHandler;
+use Monolog\Handler\WhatFailureGroupHandler;
+use Monolog\Logger as Monolog;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Class LogManager.
+ *
+ * @author overtrue
+ */
+class LogManager implements LoggerInterface
+{
+ /**
+ * @var \EasyAlipay\Kernel\ServiceContainer
+ */
+ protected $app;
+
+ /**
+ * The array of resolved channels.
+ *
+ * @var array
+ */
+ protected $channels = [];
+
+ /**
+ * The registered custom driver creators.
+ *
+ * @var array
+ */
+ protected $customCreators = [];
+
+ /**
+ * The Log levels.
+ *
+ * @var array
+ */
+ protected $levels = [
+ 'debug' => Monolog::DEBUG,
+ 'info' => Monolog::INFO,
+ 'notice' => Monolog::NOTICE,
+ 'warning' => Monolog::WARNING,
+ 'error' => Monolog::ERROR,
+ 'critical' => Monolog::CRITICAL,
+ 'alert' => Monolog::ALERT,
+ 'emergency' => Monolog::EMERGENCY,
+ ];
+
+ /**
+ * LogManager constructor.
+ *
+ * @param \EasyAlipay\Kernel\ServiceContainer $app
+ */
+ public function __construct(ServiceContainer $app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * Create a new, on-demand aggregate logger instance.
+ *
+ * @param array $channels
+ * @param string|null $channel
+ *
+ * @return \Psr\Log\LoggerInterface
+ *
+ * @throws \Exception
+ */
+ public function stack(array $channels, $channel = null)
+ {
+ return $this->createStackDriver(compact('channels', 'channel'));
+ }
+
+ /**
+ * Get a log channel instance.
+ *
+ * @param string|null $channel
+ *
+ * @return mixed
+ *
+ * @throws \Exception
+ */
+ public function channel($channel = null)
+ {
+ return $this->driver($channel);
+ }
+
+ /**
+ * Get a log driver instance.
+ *
+ * @param string|null $driver
+ *
+ * @return mixed
+ *
+ * @throws \Exception
+ */
+ public function driver($driver = null)
+ {
+ return $this->get($driver ?? $this->getDefaultDriver());
+ }
+
+ /**
+ * Attempt to get the log from the local cache.
+ *
+ * @param string $name
+ *
+ * @return \Psr\Log\LoggerInterface
+ *
+ * @throws \Exception
+ */
+ protected function get($name)
+ {
+ try {
+ return $this->channels[$name] ?? ($this->channels[$name] = $this->resolve($name));
+ } catch (\Throwable $e) {
+ $logger = $this->createEmergencyLogger();
+
+ $logger->emergency('Unable to create configured logger. Using emergency logger.', [
+ 'exception' => $e,
+ ]);
+
+ return $logger;
+ }
+ }
+
+ /**
+ * Resolve the given log instance by name.
+ *
+ * @param string $name
+ *
+ * @return \Psr\Log\LoggerInterface
+ *
+ * @throws InvalidArgumentException
+ */
+ protected function resolve($name)
+ {
+ $config = $this->app['config']->get(\sprintf('log.channels.%s', $name));
+
+ if (is_null($config)) {
+ throw new InvalidArgumentException(\sprintf('Log [%s] is not defined.', $name));
+ }
+
+ if (isset($this->customCreators[$config['driver']])) {
+ return $this->callCustomCreator($config);
+ }
+
+ $driverMethod = 'create'.ucfirst($config['driver']).'Driver';
+
+ if (method_exists($this, $driverMethod)) {
+ return $this->{$driverMethod}($config);
+ }
+
+ throw new InvalidArgumentException(\sprintf('Driver [%s] is not supported.', $config['driver']));
+ }
+
+ /**
+ * Create an emergency log handler to avoid white screens of death.
+ *
+ * @return \Monolog\Logger
+ *
+ * @throws \Exception
+ */
+ protected function createEmergencyLogger()
+ {
+ return new Monolog('EasyAlipay', $this->prepareHandlers([new StreamHandler(
+ \sys_get_temp_dir().'/easyalipay/easyalipay.log',
+ $this->level(['level' => 'debug'])
+ )]));
+ }
+
+ /**
+ * Call a custom driver creator.
+ *
+ * @param array $config
+ *
+ * @return mixed
+ */
+ protected function callCustomCreator(array $config)
+ {
+ return $this->customCreators[$config['driver']]($this->app, $config);
+ }
+
+ /**
+ * Create an aggregate log driver instance.
+ *
+ * @param array $config
+ *
+ * @return \Monolog\Logger
+ *
+ * @throws \Exception
+ */
+ protected function createStackDriver(array $config)
+ {
+ $handlers = [];
+
+ foreach ($config['channels'] ?? [] as $channel) {
+ $handlers = \array_merge($handlers, $this->channel($channel)->getHandlers());
+ }
+
+ if ($config['ignore_exceptions'] ?? false) {
+ $handlers = [new WhatFailureGroupHandler($handlers)];
+ }
+
+ return new Monolog($this->parseChannel($config), $handlers);
+ }
+
+ /**
+ * Create an instance of the single file log driver.
+ *
+ * @param array $config
+ *
+ * @return \Psr\Log\LoggerInterface
+ *
+ * @throws \Exception
+ */
+ protected function createSingleDriver(array $config)
+ {
+ return new Monolog($this->parseChannel($config), [
+ $this->prepareHandler(new StreamHandler(
+ $config['path'],
+ $this->level($config),
+ $config['bubble'] ?? true,
+ $config['permission'] ?? null,
+ $config['locking'] ?? false
+ ), $config),
+ ]);
+ }
+
+ /**
+ * Create an instance of the daily file log driver.
+ *
+ * @param array $config
+ *
+ * @return \Psr\Log\LoggerInterface
+ */
+ protected function createDailyDriver(array $config)
+ {
+ return new Monolog($this->parseChannel($config), [
+ $this->prepareHandler(new RotatingFileHandler(
+ $config['path'],
+ $config['days'] ?? 7,
+ $this->level($config),
+ $config['bubble'] ?? true,
+ $config['permission'] ?? null,
+ $config['locking'] ?? false
+ ), $config),
+ ]);
+ }
+
+ /**
+ * Create an instance of the Slack log driver.
+ *
+ * @param array $config
+ *
+ * @return \Psr\Log\LoggerInterface
+ */
+ protected function createSlackDriver(array $config)
+ {
+ return new Monolog($this->parseChannel($config), [
+ $this->prepareHandler(new SlackWebhookHandler(
+ $config['url'],
+ $config['channel'] ?? null,
+ $config['username'] ?? 'EasyAliPay',
+ $config['attachment'] ?? true,
+ $config['emoji'] ?? ':boom:',
+ $config['short'] ?? false,
+ $config['context'] ?? true,
+ $this->level($config),
+ $config['bubble'] ?? true,
+ $config['exclude_fields'] ?? []
+ ), $config),
+ ]);
+ }
+
+ /**
+ * Create an instance of the syslog log driver.
+ *
+ * @param array $config
+ *
+ * @return \Psr\Log\LoggerInterface
+ */
+ protected function createSyslogDriver(array $config)
+ {
+ return new Monolog($this->parseChannel($config), [
+ $this->prepareHandler(new SyslogHandler(
+ 'EasyAlipay',
+ $config['facility'] ?? LOG_USER,
+ $this->level($config)
+ ), $config),
+ ]);
+ }
+
+ /**
+ * Create an instance of the "error log" log driver.
+ *
+ * @param array $config
+ *
+ * @return \Psr\Log\LoggerInterface
+ */
+ protected function createErrorlogDriver(array $config)
+ {
+ return new Monolog($this->parseChannel($config), [
+ $this->prepareHandler(
+ new ErrorLogHandler(
+ $config['type'] ?? ErrorLogHandler::OPERATING_SYSTEM,
+ $this->level($config)
+ )
+ ),
+ ]);
+ }
+
+ /**
+ * Prepare the handlers for usage by Monolog.
+ *
+ * @param array $handlers
+ *
+ * @return array
+ */
+ protected function prepareHandlers(array $handlers)
+ {
+ foreach ($handlers as $key => $handler) {
+ $handlers[$key] = $this->prepareHandler($handler);
+ }
+
+ return $handlers;
+ }
+
+ /**
+ * Prepare the handler for usage by Monolog.
+ *
+ * @param \Monolog\Handler\HandlerInterface $handler
+ *
+ * @return \Monolog\Handler\HandlerInterface
+ */
+ protected function prepareHandler(HandlerInterface $handler, array $config = [])
+ {
+ if (!isset($config['formatter'])) {
+ if ($handler instanceof FormattableHandlerInterface) {
+ $handler->setFormatter($this->formatter());
+ }
+ }
+
+ return $handler;
+ }
+
+ /**
+ * Get a Monolog formatter instance.
+ *
+ * @return \Monolog\Formatter\FormatterInterface
+ */
+ protected function formatter()
+ {
+ $formatter = new LineFormatter(null, null, true, true);
+ $formatter->includeStacktraces();
+
+ return $formatter;
+ }
+
+ /**
+ * Extract the log channel from the given configuration.
+ *
+ * @param array $config
+ *
+ * @return string
+ */
+ protected function parseChannel(array $config)
+ {
+ return $config['name'] ?? 'EasyAlipay';
+ }
+
+ /**
+ * Parse the string level into a Monolog constant.
+ *
+ * @param array $config
+ *
+ * @return int
+ *
+ * @throws InvalidArgumentException
+ */
+ protected function level(array $config)
+ {
+ $level = $config['level'] ?? 'debug';
+
+ if (isset($this->levels[$level])) {
+ return $this->levels[$level];
+ }
+
+ throw new InvalidArgumentException('Invalid log level.');
+ }
+
+ /**
+ * Get the default log driver name.
+ *
+ * @return string
+ */
+ public function getDefaultDriver()
+ {
+ return $this->app['config']['log.default'];
+ }
+
+ /**
+ * Set the default log driver name.
+ *
+ * @param string $name
+ */
+ public function setDefaultDriver($name)
+ {
+ $this->app['config']['log.default'] = $name;
+ }
+
+ /**
+ * Register a custom driver creator Closure.
+ *
+ * @param string $driver
+ * @param \Closure $callback
+ *
+ * @return $this
+ */
+ public function extend($driver, \Closure $callback)
+ {
+ $this->customCreators[$driver] = $callback->bindTo($this, $this);
+
+ return $this;
+ }
+
+ /**
+ * System is unusable.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return mixed
+ *
+ * @throws \Exception
+ */
+ public function emergency($message, array $context = [])
+ {
+ return $this->driver()->emergency($message, $context);
+ }
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return mixed
+ *
+ * @throws \Exception
+ */
+ public function alert($message, array $context = [])
+ {
+ return $this->driver()->alert($message, $context);
+ }
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return mixed
+ *
+ * @throws \Exception
+ */
+ public function critical($message, array $context = [])
+ {
+ return $this->driver()->critical($message, $context);
+ }
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return mixed
+ *
+ * @throws \Exception
+ */
+ public function error($message, array $context = [])
+ {
+ return $this->driver()->error($message, $context);
+ }
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return mixed
+ *
+ * @throws \Exception
+ */
+ public function warning($message, array $context = [])
+ {
+ return $this->driver()->warning($message, $context);
+ }
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return mixed
+ *
+ * @throws \Exception
+ */
+ public function notice($message, array $context = [])
+ {
+ return $this->driver()->notice($message, $context);
+ }
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return mixed
+ *
+ * @throws \Exception
+ */
+ public function info($message, array $context = [])
+ {
+ return $this->driver()->info($message, $context);
+ }
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return mixed
+ *
+ * @throws \Exception
+ */
+ public function debug($message, array $context = [])
+ {
+ return $this->driver()->debug($message, $context);
+ }
+
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @return mixed
+ *
+ * @throws \Exception
+ */
+ public function log($level, $message, array $context = [])
+ {
+ return $this->driver()->log($level, $message, $context);
+ }
+
+ /**
+ * Dynamically call the default driver instance.
+ *
+ * @param string $method
+ * @param array $parameters
+ *
+ * @return mixed
+ *
+ * @throws \Exception
+ */
+ public function __call($method, $parameters)
+ {
+ return $this->driver()->$method(...$parameters);
+ }
+}
diff --git a/src/Kernel/Providers/ConfigServiceProvider.php b/src/Kernel/Providers/ConfigServiceProvider.php
index 9299a18..f06098c 100755
--- a/src/Kernel/Providers/ConfigServiceProvider.php
+++ b/src/Kernel/Providers/ConfigServiceProvider.php
@@ -20,7 +20,7 @@ class ConfigServiceProvider implements ServiceProviderInterface
public function register(Container $pimple)
{
$pimple['config'] = function ($app) {
- return $app->getConfig();
+ return new Config($app->getConfig());
};
}
}
diff --git a/src/Kernel/Providers/EventDispatcherServiceProvider.php b/src/Kernel/Providers/EventDispatcherServiceProvider.php
new file mode 100644
index 0000000..21e0765
--- /dev/null
+++ b/src/Kernel/Providers/EventDispatcherServiceProvider.php
@@ -0,0 +1,47 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyAlipay\Kernel\Providers;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+
+/**
+ * Class EventDispatcherServiceProvider.
+ *
+ * @author mingyoung
+ */
+class EventDispatcherServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * Registers services on the given container.
+ *
+ * This method should only be used to configure services and parameters.
+ * It should not get services.
+ *
+ * @param Container $pimple A container instance
+ */
+ public function register(Container $pimple)
+ {
+ $pimple['events'] = function ($app) {
+ $dispatcher = new EventDispatcher();
+
+ foreach ($app->config->get('events.listen', []) as $event => $listeners) {
+ foreach ($listeners as $listener) {
+ $dispatcher->addListener($event, $listener);
+ }
+ }
+
+ return $dispatcher;
+ };
+ }
+}
diff --git a/src/Kernel/Providers/ExtensionServiceProvider.php b/src/Kernel/Providers/ExtensionServiceProvider.php
new file mode 100644
index 0000000..53bc65b
--- /dev/null
+++ b/src/Kernel/Providers/ExtensionServiceProvider.php
@@ -0,0 +1,39 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyAlipay\Kernel\Providers;
+
+use EasyAlipay\Kernel\Extension;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ExtensionServiceProvider.
+ *
+ * @author overtrue
+ */
+class ExtensionServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * Registers services on the given container.
+ *
+ * This method should only be used to configure services and parameters.
+ * It should not get services.
+ *
+ * @param Container $pimple A container instance
+ */
+ public function register(Container $pimple)
+ {
+ $pimple['extension'] = function ($app) {
+ return new Extension($app);
+ };
+ }
+}
diff --git a/src/Kernel/Providers/HttpClientServiceProvider.php b/src/Kernel/Providers/HttpClientServiceProvider.php
new file mode 100644
index 0000000..35b9363
--- /dev/null
+++ b/src/Kernel/Providers/HttpClientServiceProvider.php
@@ -0,0 +1,39 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyAlipay\Kernel\Providers;
+
+use GuzzleHttp\Client;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class HttpClientServiceProvider.
+ *
+ * @author overtrue
+ */
+class HttpClientServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * Registers services on the given container.
+ *
+ * This method should only be used to configure services and parameters.
+ * It should not get services.
+ *
+ * @param Container $pimple A container instance
+ */
+ public function register(Container $pimple)
+ {
+ $pimple['http_client'] = function ($app) {
+ return new Client($app['config']->get('http', []));
+ };
+ }
+}
diff --git a/src/Kernel/Providers/LogServiceProvider.php b/src/Kernel/Providers/LogServiceProvider.php
new file mode 100644
index 0000000..93d335a
--- /dev/null
+++ b/src/Kernel/Providers/LogServiceProvider.php
@@ -0,0 +1,79 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyAlipay\Kernel\Providers;
+
+use EasyAlipay\Kernel\Log\LogManager;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class LoggingServiceProvider.
+ *
+ * @author overtrue
+ */
+class LogServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * Registers services on the given container.
+ *
+ * This method should only be used to configure services and parameters.
+ * It should not get services.
+ *
+ * @param Container $pimple A container instance
+ */
+ public function register(Container $pimple)
+ {
+ $pimple['logger'] = $pimple['log'] = function ($app) {
+ $config = $this->formatLogConfig($app);
+
+ if (!empty($config)) {
+ $app->rebind('config', $app['config']->merge($config));
+ }
+
+ return new LogManager($app);
+ };
+ }
+
+ public function formatLogConfig($app)
+ {
+ if (!empty($app['config']->get('log.channels'))) {
+ return $app['config']->get('log');
+ }
+
+ if (empty($app['config']->get('log'))) {
+ return [
+ 'log' => [
+ 'default' => 'errorlog',
+ 'channels' => [
+ 'errorlog' => [
+ 'driver' => 'errorlog',
+ 'level' => 'debug',
+ ],
+ ],
+ ],
+ ];
+ }
+
+ return [
+ 'log' => [
+ 'default' => 'single',
+ 'channels' => [
+ 'single' => [
+ 'driver' => 'single',
+ 'path' => $app['config']->get('log.file') ?: \sys_get_temp_dir().'/logs/easyalipay.log',
+ 'level' => $app['config']->get('log.level', 'debug'),
+ ],
+ ],
+ ],
+ ];
+ }
+}
diff --git a/src/Kernel/Providers/RequestServiceProvider.php b/src/Kernel/Providers/RequestServiceProvider.php
new file mode 100644
index 0000000..6cea093
--- /dev/null
+++ b/src/Kernel/Providers/RequestServiceProvider.php
@@ -0,0 +1,39 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyAlipay\Kernel\Providers;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Class RequestServiceProvider.
+ *
+ * @author overtrue
+ */
+class RequestServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * Registers services on the given container.
+ *
+ * This method should only be used to configure services and parameters.
+ * It should not get services.
+ *
+ * @param Container $pimple A container instance
+ */
+ public function register(Container $pimple)
+ {
+ $pimple['request'] = function () {
+ return Request::createFromGlobals();
+ };
+ }
+}
diff --git a/src/Kernel/ServiceContainer.php b/src/Kernel/ServiceContainer.php
index 761a6f5..b7317c9 100755
--- a/src/Kernel/ServiceContainer.php
+++ b/src/Kernel/ServiceContainer.php
@@ -2,14 +2,22 @@
namespace EasyAlipay\Kernel;
-use EasyAlipay\Kernel\Config;
use EasyAlipay\Kernel\Providers\ConfigServiceProvider;
+use EasyAlipay\Kernel\Providers\LogServiceProvider;
+use EasyAlipay\Kernel\Providers\EventDispatcherServiceProvider;
+use EasyAlipay\Kernel\Providers\ExtensionServiceProvider;
+use EasyAlipay\Kernel\Providers\HttpClientServiceProvider;
+use EasyAlipay\Kernel\Providers\RequestServiceProvider;
use Pimple\Container;
/**
* Class ServiceContainer
*
- * @property \EasyAlipay\Kernel\Config $config
+ * @property \EasyAlipay\Kernel\Config $config
+ * @property \Symfony\Component\HttpFoundation\Request $request
+ * @property \GuzzleHttp\Client $http_client
+ * @property \Monolog\Logger $logger
+ * @property \Symfony\Component\EventDispatcher\EventDispatcher $events
*/
class ServiceContainer extends Container
{
@@ -31,20 +39,26 @@ class ServiceContainer extends Container
/**
* Constructor.
*
- * @param array $config
+ * @param array $config
*/
public function __construct(array $config = [])
{
$this->registerProviders($this->getProviders());
parent::__construct();
- $this->userConfig = $config;
- $config = new config();
- $this->defaultConfig = $config->config;
+ $this->userConfig = $config;
+ $this->events->dispatch(new Events\ApplicationInitialized($this));
}
public function getConfig()
{
- return array_replace_recursive($this->defaultConfig, $this->userConfig);
+ $base = [
+ // http://docs.guzzlephp.org/en/stable/request-options.html
+ 'http' => [
+ 'timeout' => 30.0,
+ 'base_uri' => 'https://openapi.alipay.com/gateway.do',
+ ],
+ ];
+ return array_replace_recursive($base, $this->defaultConfig, $this->userConfig);
}
@@ -57,9 +71,47 @@ class ServiceContainer extends Container
{
return array_merge([
ConfigServiceProvider::class,
+ LogServiceProvider::class,
+ RequestServiceProvider::class,
+ HttpClientServiceProvider::class,
+ ExtensionServiceProvider::class,
+ EventDispatcherServiceProvider::class,
], $this->providers);
}
+ /**
+ * @param string $id
+ * @param mixed $value
+ */
+ public function rebind($id, $value)
+ {
+ $this->offsetUnset($id);
+ $this->offsetSet($id, $value);
+ }
+
+ /**
+ * Magic get access.
+ *
+ * @param string $id
+ *
+ * @return mixed
+ */
+ public function __get($id)
+ {
+ return $this->offsetGet($id);
+ }
+
+ /**
+ * Magic set access.
+ *
+ * @param string $id
+ * @param mixed $value
+ */
+ public function __set($id, $value)
+ {
+ $this->offsetSet($id, $value);
+ }
+
/**
* @param array $providers
*/
diff --git a/src/Kernel/Support/Arr.php b/src/Kernel/Support/Arr.php
new file mode 100644
index 0000000..971c5f0
--- /dev/null
+++ b/src/Kernel/Support/Arr.php
@@ -0,0 +1,466 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyAlipay\Kernel\Support;
+
+/**
+ * Array helper from Illuminate\Support\Arr.
+ */
+class Arr
+{
+ /**
+ * Add an element to an array using "dot" notation if it doesn't exist.
+ *
+ * @param array $array
+ * @param string $key
+ * @param mixed $value
+ *
+ * @return array
+ */
+ public static function add(array $array, $key, $value)
+ {
+ if (is_null(static::get($array, $key))) {
+ static::set($array, $key, $value);
+ }
+
+ return $array;
+ }
+
+ /**
+ * Cross join the given arrays, returning all possible permutations.
+ *
+ * @param array ...$arrays
+ *
+ * @return array
+ */
+ public static function crossJoin(...$arrays)
+ {
+ $results = [[]];
+
+ foreach ($arrays as $index => $array) {
+ $append = [];
+
+ foreach ($results as $product) {
+ foreach ($array as $item) {
+ $product[$index] = $item;
+
+ $append[] = $product;
+ }
+ }
+
+ $results = $append;
+ }
+
+ return $results;
+ }
+
+ /**
+ * Divide an array into two arrays. One with keys and the other with values.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public static function divide(array $array)
+ {
+ return [array_keys($array), array_values($array)];
+ }
+
+ /**
+ * Flatten a multi-dimensional associative array with dots.
+ *
+ * @param array $array
+ * @param string $prepend
+ *
+ * @return array
+ */
+ public static function dot(array $array, $prepend = '')
+ {
+ $results = [];
+
+ foreach ($array as $key => $value) {
+ if (is_array($value) && !empty($value)) {
+ $results = array_merge($results, static::dot($value, $prepend.$key.'.'));
+ } else {
+ $results[$prepend.$key] = $value;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Get all of the given array except for a specified array of items.
+ *
+ * @param array $array
+ * @param array|string $keys
+ *
+ * @return array
+ */
+ public static function except(array $array, $keys)
+ {
+ static::forget($array, $keys);
+
+ return $array;
+ }
+
+ /**
+ * Determine if the given key exists in the provided array.
+ *
+ * @param array $array
+ * @param string|int $key
+ *
+ * @return bool
+ */
+ public static function exists(array $array, $key)
+ {
+ return array_key_exists($key, $array);
+ }
+
+ /**
+ * Return the first element in an array passing a given truth test.
+ *
+ * @param array $array
+ * @param callable|null $callback
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public static function first(array $array, callable $callback = null, $default = null)
+ {
+ if (is_null($callback)) {
+ if (empty($array)) {
+ return $default;
+ }
+
+ foreach ($array as $item) {
+ return $item;
+ }
+ }
+
+ foreach ($array as $key => $value) {
+ if (call_user_func($callback, $value, $key)) {
+ return $value;
+ }
+ }
+
+ return $default;
+ }
+
+ /**
+ * Return the last element in an array passing a given truth test.
+ *
+ * @param array $array
+ * @param callable|null $callback
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public static function last(array $array, callable $callback = null, $default = null)
+ {
+ if (is_null($callback)) {
+ return empty($array) ? $default : end($array);
+ }
+
+ return static::first(array_reverse($array, true), $callback, $default);
+ }
+
+ /**
+ * Flatten a multi-dimensional array into a single level.
+ *
+ * @param array $array
+ * @param int $depth
+ *
+ * @return array
+ */
+ public static function flatten(array $array, $depth = INF)
+ {
+ return array_reduce($array, function ($result, $item) use ($depth) {
+ $item = $item instanceof Collection ? $item->all() : $item;
+
+ if (!is_array($item)) {
+ return array_merge($result, [$item]);
+ } elseif (1 === $depth) {
+ return array_merge($result, array_values($item));
+ }
+
+ return array_merge($result, static::flatten($item, $depth - 1));
+ }, []);
+ }
+
+ /**
+ * Remove one or many array items from a given array using "dot" notation.
+ *
+ * @param array $array
+ * @param array|string $keys
+ */
+ public static function forget(array &$array, $keys)
+ {
+ $original = &$array;
+
+ $keys = (array) $keys;
+
+ if (0 === count($keys)) {
+ return;
+ }
+
+ foreach ($keys as $key) {
+ // if the exact key exists in the top-level, remove it
+ if (static::exists($array, $key)) {
+ unset($array[$key]);
+
+ continue;
+ }
+
+ $parts = explode('.', $key);
+
+ // clean up before each pass
+ $array = &$original;
+
+ while (count($parts) > 1) {
+ $part = array_shift($parts);
+
+ if (isset($array[$part]) && is_array($array[$part])) {
+ $array = &$array[$part];
+ } else {
+ continue 2;
+ }
+ }
+
+ unset($array[array_shift($parts)]);
+ }
+ }
+
+ /**
+ * Get an item from an array using "dot" notation.
+ *
+ * @param array $array
+ * @param string $key
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public static function get(array $array, $key, $default = null)
+ {
+ if (is_null($key)) {
+ return $array;
+ }
+
+ if (static::exists($array, $key)) {
+ return $array[$key];
+ }
+
+ foreach (explode('.', $key) as $segment) {
+ if (static::exists($array, $segment)) {
+ $array = $array[$segment];
+ } else {
+ return $default;
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * Check if an item or items exist in an array using "dot" notation.
+ *
+ * @param array $array
+ * @param string|array $keys
+ *
+ * @return bool
+ */
+ public static function has(array $array, $keys)
+ {
+ if (is_null($keys)) {
+ return false;
+ }
+
+ $keys = (array) $keys;
+
+ if (empty($array)) {
+ return false;
+ }
+
+ if ($keys === []) {
+ return false;
+ }
+
+ foreach ($keys as $key) {
+ $subKeyArray = $array;
+
+ if (static::exists($array, $key)) {
+ continue;
+ }
+
+ foreach (explode('.', $key) as $segment) {
+ if (static::exists($subKeyArray, $segment)) {
+ $subKeyArray = $subKeyArray[$segment];
+ } else {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Determines if an array is associative.
+ *
+ * An array is "associative" if it doesn't have sequential numerical keys beginning with zero.
+ *
+ * @param array $array
+ *
+ * @return bool
+ */
+ public static function isAssoc(array $array)
+ {
+ $keys = array_keys($array);
+
+ return array_keys($keys) !== $keys;
+ }
+
+ /**
+ * Get a subset of the items from the given array.
+ *
+ * @param array $array
+ * @param array|string $keys
+ *
+ * @return array
+ */
+ public static function only(array $array, $keys)
+ {
+ return array_intersect_key($array, array_flip((array) $keys));
+ }
+
+ /**
+ * Push an item onto the beginning of an array.
+ *
+ * @param array $array
+ * @param mixed $value
+ * @param mixed $key
+ *
+ * @return array
+ */
+ public static function prepend(array $array, $value, $key = null)
+ {
+ if (is_null($key)) {
+ array_unshift($array, $value);
+ } else {
+ $array = [$key => $value] + $array;
+ }
+
+ return $array;
+ }
+
+ /**
+ * Get a value from the array, and remove it.
+ *
+ * @param array $array
+ * @param string $key
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public static function pull(array &$array, $key, $default = null)
+ {
+ $value = static::get($array, $key, $default);
+
+ static::forget($array, $key);
+
+ return $value;
+ }
+
+ /**
+ * Get a 1 value from an array.
+ *
+ * @param array $array
+ * @param int|null $amount
+ *
+ * @return mixed
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function random(array $array, int $amount = null)
+ {
+ if (is_null($amount)) {
+ return $array[array_rand($array)];
+ }
+
+ $keys = array_rand($array, $amount);
+
+ $results = [];
+
+ foreach ((array) $keys as $key) {
+ $results[] = $array[$key];
+ }
+
+ return $results;
+ }
+
+ /**
+ * Set an array item to a given value using "dot" notation.
+ *
+ * If no key is given to the method, the entire array will be replaced.
+ *
+ * @param array $array
+ * @param string $key
+ * @param mixed $value
+ *
+ * @return array
+ */
+ public static function set(array &$array, string $key, $value)
+ {
+ $keys = explode('.', $key);
+
+ while (count($keys) > 1) {
+ $key = array_shift($keys);
+
+ // If the key doesn't exist at this depth, we will just create an empty array
+ // to hold the next value, allowing us to create the arrays to hold final
+ // values at the correct depth. Then we'll keep digging into the array.
+ if (!isset($array[$key]) || !is_array($array[$key])) {
+ $array[$key] = [];
+ }
+
+ $array = &$array[$key];
+ }
+
+ $array[array_shift($keys)] = $value;
+
+ return $array;
+ }
+
+ /**
+ * Filter the array using the given callback.
+ *
+ * @param array $array
+ * @param callable $callback
+ *
+ * @return array
+ */
+ public static function where(array $array, callable $callback)
+ {
+ return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH);
+ }
+
+ /**
+ * If the given value is not an array, wrap it in one.
+ *
+ * @param mixed $value
+ *
+ * @return array
+ */
+ public static function wrap($value)
+ {
+ return !is_array($value) ? [$value] : $value;
+ }
+}
diff --git a/src/Kernel/Support/Collection.php b/src/Kernel/Support/Collection.php
new file mode 100644
index 0000000..91810cf
--- /dev/null
+++ b/src/Kernel/Support/Collection.php
@@ -0,0 +1,420 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyAlipay\Kernel\Support;
+
+use ArrayAccess;
+use ArrayIterator;
+use Countable;
+use IteratorAggregate;
+use JsonSerializable;
+use Serializable;
+
+/**
+ * Class Collection.
+ */
+class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable, Serializable
+{
+ /**
+ * The collection data.
+ *
+ * @var array
+ */
+ protected $items = [];
+
+ /**
+ * set data.
+ *
+ * @param array $items
+ */
+ public function __construct(array $items = [])
+ {
+ foreach ($items as $key => $value) {
+ $this->set($key, $value);
+ }
+ }
+
+ /**
+ * Return all items.
+ *
+ * @return array
+ */
+ public function all()
+ {
+ return $this->items;
+ }
+
+ /**
+ * Return specific items.
+ *
+ * @param array $keys
+ *
+ * @return \EasyAlipay\Kernel\Support\Collection
+ */
+ public function only(array $keys)
+ {
+ $return = [];
+
+ foreach ($keys as $key) {
+ $value = $this->get($key);
+
+ if (!is_null($value)) {
+ $return[$key] = $value;
+ }
+ }
+
+ return new static($return);
+ }
+
+ /**
+ * Get all items except for those with the specified keys.
+ *
+ * @param mixed $keys
+ *
+ * @return static
+ */
+ public function except($keys)
+ {
+ $keys = is_array($keys) ? $keys : func_get_args();
+
+ return new static(Arr::except($this->items, $keys));
+ }
+
+ /**
+ * Merge data.
+ *
+ * @param Collection|array $items
+ *
+ * @return \EasyAlipay\Kernel\Support\Collection
+ */
+ public function merge($items)
+ {
+ $clone = new static($this->all());
+
+ foreach ($items as $key => $value) {
+ $clone->set($key, $value);
+ }
+
+ return $clone;
+ }
+
+ /**
+ * To determine Whether the specified element exists.
+ *
+ * @param string $key
+ *
+ * @return bool
+ */
+ public function has($key)
+ {
+ return !is_null(Arr::get($this->items, $key));
+ }
+
+ /**
+ * Retrieve the first item.
+ *
+ * @return mixed
+ */
+ public function first()
+ {
+ return reset($this->items);
+ }
+
+ /**
+ * Retrieve the last item.
+ *
+ * @return bool
+ */
+ public function last()
+ {
+ $end = end($this->items);
+
+ reset($this->items);
+
+ return $end;
+ }
+
+ /**
+ * add the item value.
+ *
+ * @param string $key
+ * @param mixed $value
+ */
+ public function add($key, $value)
+ {
+ Arr::set($this->items, $key, $value);
+ }
+
+ /**
+ * Set the item value.
+ *
+ * @param string $key
+ * @param mixed $value
+ */
+ public function set($key, $value)
+ {
+ Arr::set($this->items, $key, $value);
+ }
+
+ /**
+ * Retrieve item from Collection.
+ *
+ * @param string $key
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function get($key, $default = null)
+ {
+ return Arr::get($this->items, $key, $default);
+ }
+
+ /**
+ * Remove item form Collection.
+ *
+ * @param string $key
+ */
+ public function forget($key)
+ {
+ Arr::forget($this->items, $key);
+ }
+
+ /**
+ * Build to array.
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ return $this->all();
+ }
+
+ /**
+ * Build to json.
+ *
+ * @param int $option
+ *
+ * @return string
+ */
+ public function toJson($option = JSON_UNESCAPED_UNICODE)
+ {
+ return json_encode($this->all(), $option);
+ }
+
+ /**
+ * To string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toJson();
+ }
+
+ /**
+ * (PHP 5 >= 5.4.0)
+ * Specify data which should be serialized to JSON.
+ *
+ * @see http://php.net/manual/en/jsonserializable.jsonserialize.php
+ *
+ * @return mixed data which can be serialized by json_encode,
+ * which is a value of any type other than a resource
+ */
+ public function jsonSerialize()
+ {
+ return $this->items;
+ }
+
+ /**
+ * (PHP 5 >= 5.1.0)
+ * String representation of object.
+ *
+ * @see http://php.net/manual/en/serializable.serialize.php
+ *
+ * @return string the string representation of the object or null
+ */
+ public function serialize()
+ {
+ return serialize($this->items);
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Retrieve an external iterator.
+ *
+ * @see http://php.net/manual/en/iteratoraggregate.getiterator.php
+ *
+ * @return \ArrayIterator An instance of an object implementing Iterator or
+ * Traversable
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->items);
+ }
+
+ /**
+ * (PHP 5 >= 5.1.0)
+ * Count elements of an object.
+ *
+ * @see http://php.net/manual/en/countable.count.php
+ *
+ * @return int the custom count as an integer.
+ *
+ *
+ * The return value is cast to an integer
+ */
+ public function count()
+ {
+ return count($this->items);
+ }
+
+ /**
+ * (PHP 5 >= 5.1.0)
+ * Constructs the object.
+ *
+ * @see http://php.net/manual/en/serializable.unserialize.php
+ *
+ * @param string $serialized
+ * The string representation of the object.
+ *
+ *
+ * @return mixed|void
+ */
+ public function unserialize($serialized)
+ {
+ return $this->items = unserialize($serialized);
+ }
+
+ /**
+ * Get a data by key.
+ *
+ * @param string $key
+ *
+ * @return mixed
+ */
+ public function __get($key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ * Assigns a value to the specified data.
+ *
+ * @param string $key
+ * @param mixed $value
+ */
+ public function __set($key, $value)
+ {
+ $this->set($key, $value);
+ }
+
+ /**
+ * Whether or not an data exists by key.
+ *
+ * @param string $key
+ *
+ * @return bool
+ */
+ public function __isset($key)
+ {
+ return $this->has($key);
+ }
+
+ /**
+ * Unset an data by key.
+ *
+ * @param string $key
+ */
+ public function __unset($key)
+ {
+ $this->forget($key);
+ }
+
+ /**
+ * var_export.
+ *
+ * @return array
+ */
+ public function __set_state()
+ {
+ return $this->all();
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Whether a offset exists.
+ *
+ * @see http://php.net/manual/en/arrayaccess.offsetexists.php
+ *
+ * @param mixed $offset
+ * An offset to check for.
+ *
+ *
+ * @return bool true on success or false on failure.
+ * The return value will be casted to boolean if non-boolean was returned
+ */
+ public function offsetExists($offset)
+ {
+ return $this->has($offset);
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Offset to unset.
+ *
+ * @see http://php.net/manual/en/arrayaccess.offsetunset.php
+ *
+ * @param mixed $offset
+ * The offset to unset.
+ *
+ */
+ public function offsetUnset($offset)
+ {
+ if ($this->offsetExists($offset)) {
+ $this->forget($offset);
+ }
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Offset to retrieve.
+ *
+ * @see http://php.net/manual/en/arrayaccess.offsetget.php
+ *
+ * @param mixed $offset
+ * The offset to retrieve.
+ *
+ *
+ * @return mixed Can return all value types
+ */
+ public function offsetGet($offset)
+ {
+ return $this->offsetExists($offset) ? $this->get($offset) : null;
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Offset to set.
+ *
+ * @see http://php.net/manual/en/arrayaccess.offsetset.php
+ *
+ * @param mixed $offset
+ * The offset to assign the value to.
+ *
+ * @param mixed $value
+ * The value to set.
+ *
+ */
+ public function offsetSet($offset, $value)
+ {
+ $this->set($offset, $value);
+ }
+}
diff --git a/src/Kernel/Support/File.php b/src/Kernel/Support/File.php
new file mode 100644
index 0000000..d1c4e2f
--- /dev/null
+++ b/src/Kernel/Support/File.php
@@ -0,0 +1,135 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyAlipay\Kernel\Support;
+
+use finfo;
+
+/**
+ * Class File.
+ */
+class File
+{
+ /**
+ * MIME mapping.
+ *
+ * @var array
+ */
+ protected static $extensionMap = [
+ 'audio/wav' => '.wav',
+ 'audio/x-ms-wma' => '.wma',
+ 'video/x-ms-wmv' => '.wmv',
+ 'video/mp4' => '.mp4',
+ 'audio/mpeg' => '.mp3',
+ 'audio/amr' => '.amr',
+ 'application/vnd.rn-realmedia' => '.rm',
+ 'audio/mid' => '.mid',
+ 'image/bmp' => '.bmp',
+ 'image/gif' => '.gif',
+ 'image/png' => '.png',
+ 'image/tiff' => '.tiff',
+ 'image/jpeg' => '.jpg',
+ 'application/pdf' => '.pdf',
+
+ // 列举更多的文件 mime, 企业号是支持的,公众平台这边之后万一也更新了呢
+ 'application/msword' => '.doc',
+
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => '.docx',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => '.dotx',
+ 'application/vnd.ms-word.document.macroEnabled.12' => '.docm',
+ 'application/vnd.ms-word.template.macroEnabled.12' => '.dotm',
+
+ 'application/vnd.ms-excel' => '.xls',
+
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => '.xlsx',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => '.xltx',
+ 'application/vnd.ms-excel.sheet.macroEnabled.12' => '.xlsm',
+ 'application/vnd.ms-excel.template.macroEnabled.12' => '.xltm',
+ 'application/vnd.ms-excel.addin.macroEnabled.12' => '.xlam',
+ 'application/vnd.ms-excel.sheet.binary.macroEnabled.12' => '.xlsb',
+
+ 'application/vnd.ms-powerpoint' => '.ppt',
+
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => '.pptx',
+ 'application/vnd.openxmlformats-officedocument.presentationml.template' => '.potx',
+ 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => '.ppsx',
+ 'application/vnd.ms-powerpoint.addin.macroEnabled.12' => '.ppam',
+ ];
+
+ /**
+ * File header signatures.
+ *
+ * @var array
+ */
+ protected static $signatures = [
+ 'ffd8ff' => '.jpg',
+ '424d' => '.bmp',
+ '47494638' => '.gif',
+ '2f55736572732f6f7665' => '.png',
+ '89504e47' => '.png',
+ '494433' => '.mp3',
+ 'fffb' => '.mp3',
+ 'fff3' => '.mp3',
+ '3026b2758e66cf11' => '.wma',
+ '52494646' => '.wav',
+ '57415645' => '.wav',
+ '41564920' => '.avi',
+ '000001ba' => '.mpg',
+ '000001b3' => '.mpg',
+ '2321414d52' => '.amr',
+ '25504446' => '.pdf',
+ ];
+
+ /**
+ * Return steam extension.
+ *
+ * @param string $stream
+ *
+ * @return string|false
+ */
+ public static function getStreamExt($stream)
+ {
+ $ext = self::getExtBySignature($stream);
+
+ try {
+ if (empty($ext) && is_readable($stream)) {
+ $stream = file_get_contents($stream);
+ }
+ } catch (\Exception $e) {
+ }
+
+ $fileInfo = new finfo(FILEINFO_MIME);
+
+ $mime = strstr($fileInfo->buffer($stream), ';', true);
+
+ return isset(self::$extensionMap[$mime]) ? self::$extensionMap[$mime] : $ext;
+ }
+
+ /**
+ * Get file extension by file header signature.
+ *
+ * @param string $stream
+ *
+ * @return string
+ */
+ public static function getExtBySignature($stream)
+ {
+ $prefix = strval(bin2hex(mb_strcut($stream, 0, 10)));
+
+ foreach (self::$signatures as $signature => $extension) {
+ if (0 === strpos($prefix, strval($signature))) {
+ return $extension;
+ }
+ }
+
+ return '';
+ }
+}
diff --git a/src/Kernel/Support/XML.php b/src/Kernel/Support/XML.php
new file mode 100644
index 0000000..9732ba5
--- /dev/null
+++ b/src/Kernel/Support/XML.php
@@ -0,0 +1,167 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyAlipay\Kernel\Support;
+
+use SimpleXMLElement;
+
+/**
+ * Class XML.
+ */
+class XML
+{
+ /**
+ * XML to array.
+ *
+ * @param string $xml XML string
+ *
+ * @return array
+ */
+ public static function parse($xml)
+ {
+ $backup = libxml_disable_entity_loader(true);
+
+ $result = self::normalize(simplexml_load_string(self::sanitize($xml), 'SimpleXMLElement', LIBXML_COMPACT | LIBXML_NOCDATA | LIBXML_NOBLANKS));
+
+ libxml_disable_entity_loader($backup);
+
+ return $result;
+ }
+
+ /**
+ * XML encode.
+ *
+ * @param mixed $data
+ * @param string $root
+ * @param string $item
+ * @param string $attr
+ * @param string $id
+ *
+ * @return string
+ */
+ public static function build(
+ $data,
+ $root = 'xml',
+ $item = 'item',
+ $attr = '',
+ $id = 'id'
+ ) {
+ if (is_array($attr)) {
+ $_attr = [];
+
+ foreach ($attr as $key => $value) {
+ $_attr[] = "{$key}=\"{$value}\"";
+ }
+
+ $attr = implode(' ', $_attr);
+ }
+
+ $attr = trim($attr);
+ $attr = empty($attr) ? '' : " {$attr}";
+ $xml = "<{$root}{$attr}>";
+ $xml .= self::data2Xml($data, $item, $id);
+ $xml .= "{$root}>";
+
+ return $xml;
+ }
+
+ /**
+ * Build CDATA.
+ *
+ * @param string $string
+ *
+ * @return string
+ */
+ public static function cdata($string)
+ {
+ return sprintf('', $string);
+ }
+
+ /**
+ * Object to array.
+ *
+ *
+ * @param SimpleXMLElement $obj
+ *
+ * @return array
+ */
+ protected static function normalize($obj)
+ {
+ $result = null;
+
+ if (is_object($obj)) {
+ $obj = (array) $obj;
+ }
+
+ if (is_array($obj)) {
+ foreach ($obj as $key => $value) {
+ $res = self::normalize($value);
+ if (('@attributes' === $key) && ($key)) {
+ $result = $res; // @codeCoverageIgnore
+ } else {
+ $result[$key] = $res;
+ }
+ }
+ } else {
+ $result = $obj;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Array to XML.
+ *
+ * @param array $data
+ * @param string $item
+ * @param string $id
+ *
+ * @return string
+ */
+ protected static function data2Xml($data, $item = 'item', $id = 'id')
+ {
+ $xml = $attr = '';
+
+ foreach ($data as $key => $val) {
+ if (is_numeric($key)) {
+ $id && $attr = " {$id}=\"{$key}\"";
+ $key = $item;
+ }
+
+ $xml .= "<{$key}{$attr}>";
+
+ if ((is_array($val) || is_object($val))) {
+ $xml .= self::data2Xml((array) $val, $item, $id);
+ } else {
+ $xml .= is_numeric($val) ? $val : self::cdata($val);
+ }
+
+ $xml .= "{$key}>";
+ }
+
+ return $xml;
+ }
+
+ /**
+ * Delete invalid characters in XML.
+ *
+ * @see https://www.w3.org/TR/2008/REC-xml-20081126/#charsets - XML charset range
+ * @see http://php.net/manual/en/regexp.reference.escape.php - escape in UTF-8 mode
+ *
+ * @param string $xml
+ *
+ * @return string
+ */
+ public static function sanitize($xml)
+ {
+ return preg_replace('/[^\x{9}\x{A}\x{D}\x{20}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]+/u', '', $xml);
+ }
+}
diff --git a/src/Kernel/Traits/HasHttpRequests.php b/src/Kernel/Traits/HasHttpRequests.php
index 023a104..20332cb 100755
--- a/src/Kernel/Traits/HasHttpRequests.php
+++ b/src/Kernel/Traits/HasHttpRequests.php
@@ -119,11 +119,11 @@ trait HasHttpRequests
/**
* Make a request.
*
- * @param string $url
+ * @param $url
* @param string $method
- * @param array $options
- *
- * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ * @param array $options
+ * @return ResponseInterface|\EasyAlipay\Kernel\Support\Collection|array|object|string
+ * @throws \GuzzleHttp\Exception\GuzzleException
*/
public function request($url, $method = 'GET', $options = []): ResponseInterface
{
@@ -138,6 +138,7 @@ trait HasHttpRequests
}
$response = $this->getHttpClient()->request($method, $url, $options);
+
$response->getBody()->rewind();
return $response;
diff --git a/src/Kernel/Traits/ResponseCastable.php b/src/Kernel/Traits/ResponseCastable.php
new file mode 100644
index 0000000..942623f
--- /dev/null
+++ b/src/Kernel/Traits/ResponseCastable.php
@@ -0,0 +1,85 @@
+getBody()->rewind();
+
+ switch ($type ?? 'array') {
+ case 'collection':
+ return $response->toCollection();
+ case 'array':
+ return $response->toArray();
+ case 'object':
+ return $response->toObject();
+ case 'raw':
+ return $response;
+ default:
+ if (!is_subclass_of($type, Arrayable::class)) {
+ throw new InvalidConfigException(sprintf('Config key "response_type" classname must be an instanceof %s', Arrayable::class));
+ }
+
+ return new $type($response);
+ }
+ }
+
+ /**
+ * @param mixed $response
+ * @param string|null $type
+ *
+ * @return array|\EasyAlipay\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyAlipay\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyAlipay\Kernel\Exceptions\InvalidConfigException
+ */
+ protected function detectAndCastResponseToType($response, $type = null)
+ {
+ switch (true) {
+ case $response instanceof ResponseInterface:
+ $response = Response::buildFromPsrResponse($response);
+
+ break;
+ case $response instanceof Arrayable:
+ $response = new Response(200, [], json_encode($response->toArray()));
+
+ break;
+ case ($response instanceof Collection) || is_array($response) || is_object($response):
+ $response = new Response(200, [], json_encode($response));
+
+ break;
+ case is_scalar($response):
+ $response = new Response(200, [], (string)$response);
+
+ break;
+ default:
+ throw new InvalidArgumentException(sprintf('Unsupported response type "%s"', gettype($response)));
+ }
+
+ return $this->castResponseToType($response, $type);
+ }
+}
\ No newline at end of file
diff --git a/src/Marketing/Pass/ServiceProvider.php b/src/Marketing/Pass/ServiceProvider.php
index 39061c1..c8d2a4a 100755
--- a/src/Marketing/Pass/ServiceProvider.php
+++ b/src/Marketing/Pass/ServiceProvider.php
@@ -12,7 +12,6 @@ class ServiceProvider implements ServiceProviderInterface
*/
public function register(Container $app)
{
- // var_dump($app);
$app['pass'] = function ($app) {
return new Client($app);
};
diff --git a/src/Mini/Risk/ServiceProvider.php b/src/Mini/Risk/ServiceProvider.php
index a43bc3f..5e30e4e 100755
--- a/src/Mini/Risk/ServiceProvider.php
+++ b/src/Mini/Risk/ServiceProvider.php
@@ -12,7 +12,6 @@ class ServiceProvider implements ServiceProviderInterface
*/
public function register(Container $app)
{
- // var_dump($app);
$app['risk'] = function ($app) {
return new Client($app);
};