Browse Source

参考wasywechat

master
wuliangbo 5 years ago
parent
commit
20997f2666
  1. 9
      composer.json
  2. 2
      src/Factory.php
  3. 555
      src/Kernel/AopClient.php
  4. 216
      src/Kernel/BaseClient.php
  5. 23
      src/Kernel/Config.php
  6. 21
      src/Kernel/Contracts/Arrayable.php
  7. 35
      src/Kernel/Events/ApplicationInitialized.php
  8. 25
      src/Kernel/Events/EventHandlerInterface.php
  9. 35
      src/Kernel/Events/HttpResponseCreated.php
  10. 35
      src/Kernel/Events/ServerGuardResponseCreated.php
  11. 12
      src/Kernel/Exceptions/BadRequestException.php
  12. 15
      src/Kernel/Exceptions/Exception.php
  13. 21
      src/Kernel/Exceptions/InvalidArgumentException.php
  14. 21
      src/Kernel/Exceptions/InvalidConfigException.php
  15. 13
      src/Kernel/Exceptions/RuntimeException.php
  16. 134
      src/Kernel/Extension.php
  17. 82
      src/Kernel/Helpers.php
  18. 121
      src/Kernel/Http/Response.php
  19. 86
      src/Kernel/Http/StreamResponse.php
  20. 608
      src/Kernel/Log/LogManager.php
  21. 2
      src/Kernel/Providers/ConfigServiceProvider.php
  22. 47
      src/Kernel/Providers/EventDispatcherServiceProvider.php
  23. 39
      src/Kernel/Providers/ExtensionServiceProvider.php
  24. 39
      src/Kernel/Providers/HttpClientServiceProvider.php
  25. 79
      src/Kernel/Providers/LogServiceProvider.php
  26. 39
      src/Kernel/Providers/RequestServiceProvider.php
  27. 60
      src/Kernel/ServiceContainer.php
  28. 466
      src/Kernel/Support/Arr.php
  29. 420
      src/Kernel/Support/Collection.php
  30. 135
      src/Kernel/Support/File.php
  31. 167
      src/Kernel/Support/XML.php
  32. 7
      src/Kernel/Traits/HasHttpRequests.php
  33. 85
      src/Kernel/Traits/ResponseCastable.php
  34. 1
      src/Marketing/Pass/ServiceProvider.php
  35. 1
      src/Mini/Risk/ServiceProvider.php

9
composer.json

@ -17,12 +17,17 @@ @@ -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": {

2
src/Factory.php

@ -9,7 +9,7 @@ namespace EasyAlipay; @@ -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
{

555
src/Kernel/AopClient.php

@ -2,10 +2,12 @@ @@ -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 @@ -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 @@ -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 @@ -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 @@ -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,69 +223,15 @@ class AopClient @@ -313,69 +223,15 @@ 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私钥配置');
@ -384,7 +240,7 @@ class AopClient @@ -384,7 +240,7 @@ class AopClient
} 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 @@ -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 @@ -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 = "<form id='alipaysubmit' name='alipaysubmit' action='".$this->gatewayUrl."?charset=".trim($this->postCharset)."' method='POST'>";
while (list ($key, $val) = each ($para_temp)) {
if (false === $this->checkEmpty($val)) {
//$val = $this->characet($val, $this->postCharset);
$val = str_replace("'","&apos;",$val);
//$val = str_replace("\"","&quot;",$val);
$sHtml.= "<input type='hidden' name='".$key."' value='".$val."'/>";
}
}
//submit按钮控件请不要含有name属性
$sHtml = $sHtml."<input type='submit' value='ok' style='display:none;''></form>";
$sHtml = $sHtml."<script>document.forms['alipaysubmit'].submit();</script>";
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 @@ -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,30 +337,22 @@ class AopClient @@ -544,30 +337,22 @@ 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格式密钥
@ -577,151 +362,20 @@ class AopClient @@ -577,151 +362,20 @@ class AopClient
//调用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 "<br/>checkSign failure<br/>";
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 = "<?xml version=\"1.0\" encoding=\"$charset\"?><alipay><response>$encrypted</response><encryption_type>RSA</encryption_type><sign>$sign</sign><sign_type>$signType</sign_type></alipay>";
return $response;
}
// 加密,不签名
if ($isEncrypt && (!$isSign)) {
$encrypted = $this->rsaEncrypt($bizContent, $rsaPublicKeyPem, $charset);
$response = "<?xml version=\"1.0\" encoding=\"$charset\"?><alipay><response>$encrypted</response><encryption_type>$signType</encryption_type></alipay>";
return $response;
}
// 不加密,但签名
if ((!$isEncrypt) && $isSign) {
$sign = $this->sign($bizContent, $signType);
$response = "<?xml version=\"1.0\" encoding=\"$charset\"?><alipay><response>$bizContent</response><sign>$sign</sign><sign_type>$signType</sign_type></alipay>";
return $response;
}
// 不加密,不签名
$response = "<?xml version=\"1.0\" encoding=\"$charset\"?>$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 "<br/>" . openssl_error_string() . "<br/>";
}
$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 "<br/>" . openssl_error_string() . "<br/>";
}
$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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 "<br/>" . $content;
}

216
src/Kernel/BaseClient.php

@ -0,0 +1,216 @@ @@ -0,0 +1,216 @@
<?php
/**
* Created by PhpStorm.
* User: wuliangbo
* Date: 2020/1/6
* Time: 15:09
*/
namespace EasyAlipay\Kernel;
use EasyAlipay\Kernel\Http\Response;
use EasyAlipay\Kernel\Traits\HasHttpRequests;
use GuzzleHttp\MessageFormatter;
use GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LogLevel;
class BaseClient
{
use HasHttpRequests {
request as performRequest;
}
/**
* @var \EasyAlipay\Kernel\ServiceContainer
*/
protected $app;
/**
* @var string
*/
protected $baseUri;
/**
* BaseClient constructor.
*
* @param \EasyAlipay\Kernel\ServiceContainer $app
*/
public function __construct(ServiceContainer $app)
{
$this->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));
});
}
}

23
src/Kernel/Config.php

@ -2,26 +2,9 @@ @@ -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",
// ...
];
}

21
src/Kernel/Contracts/Arrayable.php

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
<?php
/**
* Created by PhpStorm.
* User: wuliangbo
* Date: 2020/1/6
* Time: 14:53
*/
namespace EasyAlipay\Kernel\Contracts;
use ArrayAccess;
interface Arrayable extends ArrayAccess
{
/**
* Get the instance as an array.
*
* @return array
*/
public function toArray();
}

35
src/Kernel/Events/ApplicationInitialized.php

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
<?php
/*
* This file is part of the overtrue/wechat.
*
* (c) overtrue <i@overtrue.me>
*
* 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 <mingyoungcheung@gmail.com>
*/
class ApplicationInitialized
{
/**
* @var \EasyAlipay\Kernel\ServiceContainer
*/
public $app;
/**
* @param \EasyAlipay\Kernel\ServiceContainer $app
*/
public function __construct(ServiceContainer $app)
{
$this->app = $app;
}
}

25
src/Kernel/Events/EventHandlerInterface.php

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
<?php
/*
* This file is part of the overtrue/wechat.
*
* (c) overtrue <i@overtrue.me>
*
* 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 <mingyoungcheung@gmail.com>
*/
interface EventHandlerInterface
{
/**
* @param mixed $payload
*/
public function handle($payload = null);
}

35
src/Kernel/Events/HttpResponseCreated.php

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
<?php
/*
* This file is part of the overtrue/wechat.
*
* (c) overtrue <i@overtrue.me>
*
* 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 <mingyoungcheung@gmail.com>
*/
class HttpResponseCreated
{
/**
* @var \Psr\Http\Message\ResponseInterface
*/
public $response;
/**
* @param \Psr\Http\Message\ResponseInterface $response
*/
public function __construct(ResponseInterface $response)
{
$this->response = $response;
}
}

35
src/Kernel/Events/ServerGuardResponseCreated.php

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
<?php
/*
* This file is part of the overtrue/wechat.
*
* (c) overtrue <i@overtrue.me>
*
* 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 <mingyoungcheung@gmail.com>
*/
class ServerGuardResponseCreated
{
/**
* @var \Symfony\Component\HttpFoundation\Response
*/
public $response;
/**
* @param \Symfony\Component\HttpFoundation\Response $response
*/
public function __construct(Response $response)
{
$this->response = $response;
}
}

12
src/Kernel/Exceptions/BadRequestException.php

@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
<?php
/**
* Created by PhpStorm.
* User: wuliangbo
* Date: 2020/1/6
* Time: 14:26
*/
namespace EasyAlipay\Kernel\Exceptions;
class BadRequestException extends Exception
{
}

15
src/Kernel/Exceptions/Exception.php

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
<?php
/**
* Created by PhpStorm.
* User: wuliangbo
* Date: 2020/1/6
* Time: 14:25
*/
namespace EasyAlipay\Kernel\Exceptions;
use Exception as BaseException;
class Exception extends BaseException
{
}

21
src/Kernel/Exceptions/InvalidArgumentException.php

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
<?php
/*
* This file is part of the overtrue/wechat.
*
* (c) overtrue <i@overtrue.me>
*
* 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 <i@overtrue.me>
*/
class InvalidArgumentException extends Exception
{
}

21
src/Kernel/Exceptions/InvalidConfigException.php

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
<?php
/*
* This file is part of the overtrue/wechat.
*
* (c) overtrue <i@overtrue.me>
*
* 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 <i@overtrue.me>
*/
class InvalidConfigException extends Exception
{
}

13
src/Kernel/Exceptions/RuntimeException.php

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
<?php
/**
* Created by PhpStorm.
* User: wuliangbo
* Date: 2020/1/6
* Time: 14:27
*/
namespace EasyAlipay\Kernel\Exceptions;
class RuntimeException extends Exception
{
}

134
src/Kernel/Extension.php

@ -0,0 +1,134 @@ @@ -0,0 +1,134 @@
<?php
declare(strict_types=1);
namespace EasyAlipay\Kernel;
use EasyAlipay\Kernel\Contracts\EventHandlerInterface;
use Pimple\Container;
use ReflectionClass;
class Extension
{
/**
* @var \Pimple\Container
*/
protected $app;
/**
* @var string
*/
protected $manifestPath;
/**
* @var array|null
*/
protected $manifest;
/**
* @param \Pimple\Container $app
*/
public function __construct(Container $app)
{
$this->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 : [];
}
}

82
src/Kernel/Helpers.php

@ -0,0 +1,82 @@ @@ -0,0 +1,82 @@
<?php
/**
* Created by PhpStorm.
* User: wuliangbo
* Date: 2020/1/6
* Time: 15:25
*/
namespace EasyAlipay\Kernel;
/**
* 加密方法
* @param string $str
* @return string
*/
function encrypt($str, $screct_key)
{
//AES, 128 模式加密数据 CBC
$screct_key = base64_decode($screct_key);
$str = trim($str);
$str = addPKCS7Padding($str);
//设置全0的IV
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = str_repeat("\0", $iv_size);
$encrypt_str = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $screct_key, $str, MCRYPT_MODE_CBC, $iv);
return base64_encode($encrypt_str);
}
/**
* 解密方法
* @param string $str
* @return string
*/
function decrypt($str, $screct_key)
{
//AES, 128 模式加密数据 CBC
$str = base64_decode($str);
$screct_key = base64_decode($screct_key);
//设置全0的IV
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = str_repeat("\0", $iv_size);
$decrypt_str = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $screct_key, $str, MCRYPT_MODE_CBC, $iv);
$decrypt_str = stripPKSC7Padding($decrypt_str);
return $decrypt_str;
}
/**
* 填充算法
* @param string $source
* @return string
*/
function addPKCS7Padding($source)
{
$source = trim($source);
$block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$pad = $block - (strlen($source) % $block);
if ($pad <= $block) {
$char = chr($pad);
$source .= str_repeat($char, $pad);
}
return $source;
}
/**
* 移去填充算法
* @param string $source
* @return string
*/
function stripPKSC7Padding($source)
{
$char = substr($source, -1);
$num = ord($char);
if ($num == 62) return $source;
$source = substr($source, 0, -$num);
return $source;
}

121
src/Kernel/Http/Response.php

@ -0,0 +1,121 @@ @@ -0,0 +1,121 @@
<?php
/*
* This file is part of the overtrue/wechat.
*
* (c) overtrue <i@overtrue.me>
*
* 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 <i@overtrue.me>
*/
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, '<xml')) {
return XML::parse($content);
}
$array = json_decode($content, true, 512, JSON_BIGINT_AS_STRING);
if (JSON_ERROR_NONE === json_last_error()) {
return (array) $array;
}
return [];
}
/**
* Get collection data.
*
* @return \EasyAlipay\Kernel\Support\Collection
*/
public function toCollection()
{
return new Collection($this->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);
}
}

86
src/Kernel/Http/StreamResponse.php

@ -0,0 +1,86 @@ @@ -0,0 +1,86 @@
<?php
/*
* This file is part of the overtrue/wechat.
*
* (c) overtrue <i@overtrue.me>
*
* 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 <i@overtrue.me>
*/
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="(?<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);
}
}

608
src/Kernel/Log/LogManager.php

@ -0,0 +1,608 @@ @@ -0,0 +1,608 @@
<?php
/*
* This file is part of the overtrue/wechat.
*
* (c) overtrue <i@overtrue.me>
*
* 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 <i@overtrue.me>
*/
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);
}
}

2
src/Kernel/Providers/ConfigServiceProvider.php

@ -20,7 +20,7 @@ class ConfigServiceProvider implements ServiceProviderInterface @@ -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());
};
}
}

47
src/Kernel/Providers/EventDispatcherServiceProvider.php

@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
<?php
/*
* This file is part of the overtrue/wechat.
*
* (c) overtrue <i@overtrue.me>
*
* 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 <mingyoungcheung@gmail.com>
*/
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;
};
}
}

39
src/Kernel/Providers/ExtensionServiceProvider.php

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
<?php
/*
* This file is part of the overtrue/wechat.
*
* (c) overtrue <i@overtrue.me>
*
* 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 <i@overtrue.me>
*/
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);
};
}
}

39
src/Kernel/Providers/HttpClientServiceProvider.php

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
<?php
/*
* This file is part of the overtrue/wechat.
*
* (c) overtrue <i@overtrue.me>
*
* 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 <i@overtrue.me>
*/
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', []));
};
}
}

79
src/Kernel/Providers/LogServiceProvider.php

@ -0,0 +1,79 @@ @@ -0,0 +1,79 @@
<?php
/*
* This file is part of the overtrue/wechat.
*
* (c) overtrue <i@overtrue.me>
*
* 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 <i@overtrue.me>
*/
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'),
],
],
],
];
}
}

39
src/Kernel/Providers/RequestServiceProvider.php

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
<?php
/*
* This file is part of the overtrue/wechat.
*
* (c) overtrue <i@overtrue.me>
*
* 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 <i@overtrue.me>
*/
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();
};
}
}

60
src/Kernel/ServiceContainer.php

@ -2,14 +2,22 @@ @@ -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 \Symfony\Component\HttpFoundation\Request $request
* @property \GuzzleHttp\Client $http_client
* @property \Monolog\Logger $logger
* @property \Symfony\Component\EventDispatcher\EventDispatcher $events
*/
class ServiceContainer extends Container
{
@ -38,13 +46,19 @@ class ServiceContainer extends Container @@ -38,13 +46,19 @@ class ServiceContainer extends Container
$this->registerProviders($this->getProviders());
parent::__construct();
$this->userConfig = $config;
$config = new config();
$this->defaultConfig = $config->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 @@ -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
*/

466
src/Kernel/Support/Arr.php

@ -0,0 +1,466 @@ @@ -0,0 +1,466 @@
<?php
/*
* This file is part of the overtrue/wechat.
*
* (c) overtrue <i@overtrue.me>
*
* 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;
}
}

420
src/Kernel/Support/Collection.php

@ -0,0 +1,420 @@ @@ -0,0 +1,420 @@
<?php
/*
* This file is part of the overtrue/wechat.
*
* (c) overtrue <i@overtrue.me>
*
* 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 &gt;= 5.4.0)<br/>
* 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 <b>json_encode</b>,
* which is a value of any type other than a resource
*/
public function jsonSerialize()
{
return $this->items;
}
/**
* (PHP 5 &gt;= 5.1.0)<br/>
* 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 &gt;= 5.0.0)<br/>
* Retrieve an external iterator.
*
* @see http://php.net/manual/en/iteratoraggregate.getiterator.php
*
* @return \ArrayIterator An instance of an object implementing <b>Iterator</b> or
* <b>Traversable</b>
*/
public function getIterator()
{
return new ArrayIterator($this->items);
}
/**
* (PHP 5 &gt;= 5.1.0)<br/>
* Count elements of an object.
*
* @see http://php.net/manual/en/countable.count.php
*
* @return int the custom count as an integer.
* </p>
* <p>
* The return value is cast to an integer
*/
public function count()
{
return count($this->items);
}
/**
* (PHP 5 &gt;= 5.1.0)<br/>
* Constructs the object.
*
* @see http://php.net/manual/en/serializable.unserialize.php
*
* @param string $serialized <p>
* The string representation of the object.
* </p>
*
* @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 &gt;= 5.0.0)<br/>
* Whether a offset exists.
*
* @see http://php.net/manual/en/arrayaccess.offsetexists.php
*
* @param mixed $offset <p>
* An offset to check for.
* </p>
*
* @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 &gt;= 5.0.0)<br/>
* Offset to unset.
*
* @see http://php.net/manual/en/arrayaccess.offsetunset.php
*
* @param mixed $offset <p>
* The offset to unset.
* </p>
*/
public function offsetUnset($offset)
{
if ($this->offsetExists($offset)) {
$this->forget($offset);
}
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Offset to retrieve.
*
* @see http://php.net/manual/en/arrayaccess.offsetget.php
*
* @param mixed $offset <p>
* The offset to retrieve.
* </p>
*
* @return mixed Can return all value types
*/
public function offsetGet($offset)
{
return $this->offsetExists($offset) ? $this->get($offset) : null;
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Offset to set.
*
* @see http://php.net/manual/en/arrayaccess.offsetset.php
*
* @param mixed $offset <p>
* The offset to assign the value to.
* </p>
* @param mixed $value <p>
* The value to set.
* </p>
*/
public function offsetSet($offset, $value)
{
$this->set($offset, $value);
}
}

135
src/Kernel/Support/File.php

@ -0,0 +1,135 @@ @@ -0,0 +1,135 @@
<?php
/*
* This file is part of the overtrue/wechat.
*
* (c) overtrue <i@overtrue.me>
*
* 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 '';
}
}

167
src/Kernel/Support/XML.php

@ -0,0 +1,167 @@ @@ -0,0 +1,167 @@
<?php
/*
* This file is part of the overtrue/wechat.
*
* (c) overtrue <i@overtrue.me>
*
* 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('<![CDATA[%s]]>', $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);
}
}

7
src/Kernel/Traits/HasHttpRequests.php

@ -119,11 +119,11 @@ trait HasHttpRequests @@ -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
* @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 @@ -138,6 +138,7 @@ trait HasHttpRequests
}
$response = $this->getHttpClient()->request($method, $url, $options);
$response->getBody()->rewind();
return $response;

85
src/Kernel/Traits/ResponseCastable.php

@ -0,0 +1,85 @@ @@ -0,0 +1,85 @@
<?php
namespace EasyAlipay\Kernel\Traits;
use EasyAlipay\Kernel\Contracts\Arrayable;
use EasyAlipay\Kernel\Exceptions\InvalidArgumentException;
use EasyAlipay\Kernel\Exceptions\InvalidConfigException;
use EasyAlipay\Kernel\Http\Response;
use EasyAlipay\Kernel\Support\Collection;
use Psr\Http\Message\ResponseInterface;
/**
* Created by PhpStorm.
* User: wuliangbo
* Date: 2020/1/6
* Time: 14:54
*/
trait ResponseCastable
{
/**
* @param \Psr\Http\Message\ResponseInterface $response
* @param string|null $type
*
* @return array|\EasyAlipay\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
*
* @throws \EasyAlipay\Kernel\Exceptions\InvalidConfigException
*/
protected function castResponseToType(ResponseInterface $response, $type = null)
{
$response = Response::buildFromPsrResponse($response);
$response->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);
}
}

1
src/Marketing/Pass/ServiceProvider.php

@ -12,7 +12,6 @@ class ServiceProvider implements ServiceProviderInterface @@ -12,7 +12,6 @@ class ServiceProvider implements ServiceProviderInterface
*/
public function register(Container $app)
{
// var_dump($app);
$app['pass'] = function ($app) {
return new Client($app);
};

1
src/Mini/Risk/ServiceProvider.php

@ -12,7 +12,6 @@ class ServiceProvider implements ServiceProviderInterface @@ -12,7 +12,6 @@ class ServiceProvider implements ServiceProviderInterface
*/
public function register(Container $app)
{
// var_dump($app);
$app['risk'] = function ($app) {
return new Client($app);
};

Loading…
Cancel
Save