代码初始化

This commit is contained in:
2025-08-07 20:21:47 +08:00
commit 50f3a2dbb0
2191 changed files with 374790 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
<?php
namespace addons\epay\library;
class Collection extends \Yansongda\Supports\Collection
{
/**
* 创建 Collection 实例
* @access public
* @param array $items 数据
* @return static
*/
public static function make($items = [])
{
return new static($items);
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace addons\epay\library;
use think\Exception;
class OrderException extends Exception
{
public function __construct($message = "", $code = 0, $data = [])
{
$this->message = $message;
$this->code = $code;
$this->data = $data;
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace addons\epay\library;
class RedirectResponse extends \Symfony\Component\HttpFoundation\RedirectResponse implements \JsonSerializable, \Serializable
{
public function __toString()
{
return $this->getContent();
}
public function setTargetUrl($url)
{
if ('' === ($url ?? '')) {
throw new \InvalidArgumentException('无法跳转到空页面');
}
$this->targetUrl = $url;
$this->setContent(
sprintf('<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="refresh" content="0;url=\'%1$s\'" />
<title>正在跳转支付 %1$s</title>
</head>
<body>
<div id="redirect" style="display:none;">正在跳转支付 <a href="%1$s">%1$s</a></div>
<script type="text/javascript">
setTimeout(function(){
document.getElementById("redirect").style.display = "block";
}, 1000);
</script>
</body>
</html>', htmlspecialchars($url, \ENT_QUOTES, 'UTF-8')));
$this->headers->set('Location', $url);
return $this;
}
public function jsonSerialize()
{
return $this->getContent();
}
public function serialize()
{
return serialize($this->content);
}
public function unserialize($serialized)
{
return $this->content = unserialize($serialized);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace addons\epay\library;
class Response extends \Symfony\Component\HttpFoundation\Response implements \JsonSerializable, \Serializable
{
public function __toString()
{
return $this->getContent();
}
public function jsonSerialize()
{
return $this->getContent();
}
public function serialize()
{
return serialize($this->content);
}
public function unserialize($serialized)
{
return $this->content = unserialize($serialized);
}
}

View File

@@ -0,0 +1,489 @@
<?php
namespace addons\epay\library;
use addons\third\model\Third;
use app\common\library\Auth;
use Exception;
use think\Hook;
use think\Session;
use Yansongda\Pay\Pay;
use Yansongda\Supports\Str;
/**
* 订单服务类
*
* @package addons\epay\library
*/
class Service
{
public const SDK_VERSION_V2 = 'v2';
public const SDK_VERSION_V3 = 'v3';
/**
* 提交订单
* @param array|float $amount 订单金额
* @param string $orderid 订单号
* @param string $type 支付类型,可选alipay或wechat
* @param string $title 订单标题
* @param string $notifyurl 通知回调URL
* @param string $returnurl 跳转返回URL
* @param string $method 支付方法
* @param string $openid Openid
* @param array $custom 自定义微信支付宝相关配置
* @return Response|RedirectResponse|Collection
* @throws Exception
*/
public static function submitOrder($amount, $orderid = null, $type = null, $title = null, $notifyurl = null, $returnurl = null, $method = null, $openid = '', $custom = [])
{
$version = self::getSdkVersion();
$request = request();
$addonConfig = get_addon_config('epay');
if (!is_array($amount)) {
$params = [
'amount' => $amount,
'orderid' => $orderid,
'type' => $type,
'title' => $title,
'notifyurl' => $notifyurl,
'returnurl' => $returnurl,
'method' => $method,
'openid' => $openid,
'custom' => $custom,
];
} else {
$params = $amount;
}
$type = isset($params['type']) && in_array($params['type'], ['alipay', 'wechat']) ? $params['type'] : 'wechat';
$method = $params['method'] ?? 'web';
$orderid = $params['orderid'] ?? date("YmdHis") . mt_rand(100000, 999999);
$amount = $params['amount'] ?? 1;
$title = $params['title'] ?? "支付";
$auth_code = $params['auth_code'] ?? '';
$openid = $params['openid'] ?? '';
//自定义微信支付宝相关配置
$custom = $params['custom'] ?? [];
//未定义则使用默认回调和跳转
$notifyurl = !empty($params['notifyurl']) ? $params['notifyurl'] : $request->root(true) . '/addons/epay/index/notifyx/paytype/' . $type;
$returnurl = !empty($params['returnurl']) ? $params['returnurl'] : $request->root(true) . '/addons/epay/index/returnx/paytype/' . $type . '/out_trade_no/' . $orderid;
$html = '';
$config = Service::getConfig($type, array_merge($custom, ['notify_url' => $notifyurl, 'return_url' => $returnurl]));
//判断是否移动端或微信内浏览器
$isMobile = $request->isMobile();
$isWechat = strpos($request->server('HTTP_USER_AGENT'), 'MicroMessenger') !== false;
$result = null;
if ($type == 'alipay') {
//如果是PC支付,判断当前环境,进行跳转
if ($method == 'web') {
//如果是微信环境或后台配置PC使用扫码支付
if ($isWechat || $addonConfig['alipay']['scanpay']) {
Session::set("alipayorderdata", $params);
$url = addon_url('epay/api/alipay', [], true, true);
return new RedirectResponse($url);
} elseif ($isMobile) {
$method = 'wap';
}
}
//创建支付对象
$pay = Pay::alipay($config);
$params = [
'out_trade_no' => $orderid,//你的订单号
'total_amount' => $amount,//单位元
'subject' => $title,
];
switch ($method) {
case 'web':
//电脑支付
$result = $pay->web($params);
break;
case 'wap':
//手机网页支付
$result = $pay->wap($params);
break;
case 'app':
//APP支付
$result = $pay->app($params);
break;
case 'scan':
//扫码支付
$result = $pay->scan($params);
break;
case 'pos':
//刷卡支付必须要有auth_code
$params['auth_code'] = $auth_code;
$result = $pay->pos($params);
break;
case 'mini':
case 'miniapp':
//小程序支付,直接返回字符串
//小程序支付必须要有buyer_id或buyer_open_id
if (is_numeric($openid) && strlen($openid) === 16) {
$params['buyer_id'] = $openid;
} else {
$params['buyer_open_id'] = $openid;
}
$result = $pay->mini($params);
break;
default:
}
} else {
//如果是PC支付,判断当前环境,进行跳转
if ($method == 'web') {
//如果是移动端,但不是微信环境
if ($isMobile && !$isWechat) {
$method = 'wap';
} else {
Session::set("wechatorderdata", $params);
$url = addon_url('epay/api/wechat', [], true, true);
return new RedirectResponse($url);
}
}
//单位分
$total_fee = function_exists('bcmul') ? bcmul($amount, 100) : $amount * 100;
$total_fee = (int)$total_fee;
$ip = $request->ip();
//微信服务商模式时需传递sub_openid参数
$openidName = $addonConfig['wechat']['mode'] == 'service' ? 'sub_openid' : 'openid';
//创建支付对象
$pay = Pay::wechat($config);
if (self::isVersionV3()) {
//V3支付
$params = [
'out_trade_no' => $orderid,
'description' => $title,
'amount' => [
'total' => $total_fee,
]
];
switch ($method) {
case 'mp':
//公众号支付
//公众号支付必须有openid
$params['payer'] = [$openidName => $openid];
$result = $pay->mp($params);
break;
case 'wap':
//手机网页支付,跳转
$params['scene_info'] = [
'payer_client_ip' => $ip,
'h5_info' => [
'type' => 'Wap',
]
];
$result = $pay->wap($params);
break;
case 'app':
//APP支付,直接返回字符串
$result = $pay->app($params);
break;
case 'scan':
//扫码支付,直接返回字符串
$result = $pay->scan($params);
break;
case 'pos':
//刷卡支付,直接返回字符串
//刷卡支付必须要有auth_code
$params['auth_code'] = $auth_code;
$result = $pay->pos($params);
break;
case 'mini':
case 'miniapp':
//小程序支付,直接返回字符串
//小程序支付必须要有openid
$params['payer'] = [$openidName => $openid];
$result = $pay->mini($params);
break;
default:
}
} else {
//V2支付
$params = [
'out_trade_no' => $orderid,
'body' => $title,
'total_fee' => $total_fee,
];
switch ($method) {
case 'mp':
//公众号支付
//公众号支付必须有openid
$params[$openidName] = $openid;
$result = $pay->mp($params);
break;
case 'wap':
//手机网页支付,跳转
$params['spbill_create_ip'] = $ip;
$result = $pay->wap($params);
break;
case 'app':
//APP支付,直接返回字符串
$result = $pay->app($params);
break;
case 'scan':
//扫码支付,直接返回字符串
$result = $pay->scan($params);
break;
case 'pos':
//刷卡支付,直接返回字符串
//刷卡支付必须要有auth_code
$params['auth_code'] = $auth_code;
$result = $pay->pos($params);
break;
case 'mini':
case 'miniapp':
//小程序支付,直接返回字符串
//小程序支付必须要有openid
$params[$openidName] = $openid;
$result = $pay->miniapp($params);
break;
default:
}
}
}
//使用重写的Response类、RedirectResponse、Collection类
if ($result instanceof \Symfony\Component\HttpFoundation\RedirectResponse) {
$result = new RedirectResponse($result->getTargetUrl());
} elseif ($result instanceof \Symfony\Component\HttpFoundation\Response) {
$result = new Response($result->getContent());
} elseif ($result instanceof \Yansongda\Supports\Collection) {
$result = Collection::make($result->all());
} elseif ($result instanceof \GuzzleHttp\Psr7\Response) {
$result = new Response($result->getBody());
}
return $result;
}
/**
* 验证回调是否成功
* @param string $type 支付类型
* @param array $custom 自定义配置信息
* @return bool|\Yansongda\Pay\Gateways\Alipay|\Yansongda\Pay\Gateways\Wechat|\Yansongda\Pay\Provider\Wechat|\Yansongda\Pay\Provider\Alipay
*/
public static function checkNotify($type, $custom = [])
{
$type = strtolower($type);
if (!in_array($type, ['wechat', 'alipay'])) {
return false;
}
$version = self::getSdkVersion();
try {
$config = self::getConfig($type, $custom);
$pay = $type == 'wechat' ? Pay::wechat($config) : Pay::alipay($config);
$data = Service::isVersionV3() ? $pay->callback() : $pay->verify();
if ($type == 'alipay') {
if (in_array($data['trade_status'], ['TRADE_SUCCESS', 'TRADE_FINISHED'])) {
return $pay;
}
} else {
return $pay;
}
} catch (Exception $e) {
\think\Log::record("回调请求参数解析错误", "error");
return false;
}
return false;
}
/**
* 验证返回是否成功,请勿用于判断是否支付成功的逻辑验证
* 已弃用
*
* @param string $type 支付类型
* @param array $custom 自定义配置信息
* @return bool
* @deprecated 已弃用,请勿用于逻辑验证
*/
public static function checkReturn($type, $custom = [])
{
//由于PC及移动端无法获取请求的参数信息取消return验证均返回true
return true;
}
/**
* 处理证书路径
* @param array $config 配置
* @param string $field 字段
* @return void
*/
private static function processAddonsPath(&$config, $field)
{
if (isset($config[$field]) && substr($config[$field], 0, 8) == '/addons/') {
$config[$field] = ROOT_PATH . str_replace('/', DS, substr($config[$field], 1));
}
}
/**
* 获取配置
* @param string $type 支付类型
* @param array $custom 自定义配置,用于覆盖插件默认配置
* @return array
*/
public static function getConfig($type = 'wechat', $custom = [])
{
$addonConfig = get_addon_config('epay');
$config = $addonConfig[$type] ?? $addonConfig['wechat'];
// SDK版本
$version = self::getSdkVersion();
// 处理微信证书路径
if ($type === 'wechat') {
$certFields = ['cert_client', 'cert_key'];
foreach ($certFields as $field) {
self::processAddonsPath($config, $field);
}
}
// 处理支付宝证书路径
if ($type === 'alipay') {
$config['signtype'] = $config['signtype'] ?? 'publickey';
if ($config['signtype'] == 'cert') {
$certFields = ['app_cert_public_key', 'alipay_root_cert', 'ali_public_key'];
foreach ($certFields as $field) {
self::processAddonsPath($config, $field);
}
} else {
// 如果是普通公钥需要将app_cert_public_key和alipay_root_cert设置为空不然会导致错误
$config['app_cert_public_key'] = '';
$config['alipay_root_cert'] = '';
}
}
// V3支付
if (self::isVersionV3()) {
if ($type == 'wechat') {
$config['mp_app_id'] = $config['app_id'] ?? '';
$config['app_id'] = $config['appid'] ?? '';
$config['mini_app_id'] = $config['miniapp_id'] ?? '';
$config['combine_mch_id'] = $config['combine_mch_id'] ?? '';
$config['mch_secret_key'] = $config['key_v3'] ?? '';
$config['mch_secret_cert'] = $config['cert_key'];
$config['mch_public_cert_path'] = $config['cert_client'];
$config['sub_mp_app_id'] = $config['sub_appid'] ?? '';
$config['sub_app_id'] = $config['sub_app_id'] ?? '';
$config['sub_mini_app_id'] = $config['sub_miniapp_id'] ?? '';
$config['sub_mch_id'] = $config['sub_mch_id'] ?? '';
} elseif ($type == 'alipay') {
$config['app_secret_cert'] = $config['private_key'] ?? '';
$config['app_public_cert_path'] = $config['app_cert_public_key'] ?? '';
$config['alipay_public_cert_path'] = $config['ali_public_key'] ?? '';
$config['alipay_root_cert_path'] = $config['alipay_root_cert'] ?? '';
$config['service_provider_id'] = $config['pid'] ?? '';
}
$modeArr = ['normal' => 0, 'dev' => 1, 'service' => 2];
$config['mode'] = $modeArr[$config['mode']] ?? 0;
}
// 日志
if ($config['log']) {
$config['log'] = [
'enable' => true,
'file' => LOG_PATH . 'epaylogs' . DS . $type . '-' . date("Y-m-d") . '.log',
'level' => 'debug'
];
} else {
$config['log'] = [
'enable' => false,
];
}
// GuzzleHttp配置可选
$config['http'] = [
'timeout' => 10,
'connect_timeout' => 10,
// 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html)
];
$config['notify_url'] = empty($config['notify_url']) ? addon_url('epay/api/notifyx', [], false) . '/type/' . $type : $config['notify_url'];
$config['notify_url'] = !preg_match("/^(http:\/\/|https:\/\/)/i", $config['notify_url']) ? request()->root(true) . $config['notify_url'] : $config['notify_url'];
$config['return_url'] = empty($config['return_url']) ? addon_url('epay/api/returnx', [], false) . '/type/' . $type : $config['return_url'];
$config['return_url'] = !preg_match("/^(http:\/\/|https:\/\/)/i", $config['return_url']) ? request()->root(true) . $config['return_url'] : $config['return_url'];
//合并自定义配置
$config = array_merge($config, $custom);
//v3版本时返回的结构不同
if (self::isVersionV3()) {
$config = [$type => ['default' => $config], 'logger' => $config['log'], 'http' => $config['http'], '_force' => true];
}
return $config;
}
/**
* 获取微信Openid
*
* @param array $custom 自定义配置信息
* @return mixed|string
*/
public static function getOpenid($custom = [])
{
$openid = '';
$auth = Auth::instance();
if ($auth->isLogin()) {
$third = get_addon_info('third');
if ($third && $third['state']) {
$thirdInfo = Third::where('user_id', $auth->id)->where('platform', 'wechat')->where('apptype', 'mp')->find();
$openid = $thirdInfo ? $thirdInfo['openid'] : '';
}
}
if (!$openid) {
$openid = Session::get("openid");
//如果未传openid则去读取openid
if (!$openid) {
$addonConfig = get_addon_config('epay');
$wechat = new Wechat($custom['app_id'] ?? $addonConfig['wechat']['app_id'], $custom['app_secret'] ?? $addonConfig['wechat']['app_secret']);
$openid = $wechat->getOpenid();
}
}
return $openid;
}
/**
* 获取SDK版本
* @return mixed|string
*/
public static function getSdkVersion()
{
$addonConfig = get_addon_config('epay');
return $addonConfig['version'] ?? self::SDK_VERSION_V2;
}
/**
* 判断是否V2支付
* @return bool
*/
public static function isVersionV2()
{
return self::getSdkVersion() === self::SDK_VERSION_V2;
}
/**
* 判断是否V3支付
* @return bool
*/
public static function isVersionV3()
{
return self::getSdkVersion() === self::SDK_VERSION_V3;
}
}

View File

@@ -0,0 +1,110 @@
<?php
namespace addons\epay\library;
use fast\Http;
use think\Cache;
use think\Session;
/**
* 微信授权
*
*/
class Wechat
{
private $app_id = '';
private $app_secret = '';
private $scope = 'snsapi_userinfo';
public function __construct($app_id, $app_secret)
{
$this->app_id = $app_id;
$this->app_secret = $app_secret;
}
/**
* 获取微信授权链接
*
* @return string
*/
public function getAuthorizeUrl()
{
$redirect_uri = addon_url('epay/api/wechat', [], true, true);
$redirect_uri = urlencode($redirect_uri);
$state = \fast\Random::alnum();
Session::set('state', $state);
return "https://open.weixin.qq.com/connect/oauth2/authorize?appid={$this->app_id}&redirect_uri={$redirect_uri}&response_type=code&scope={$this->scope}&state={$state}#wechat_redirect";
}
/**
* 获取微信openid
*
* @return mixed|string
*/
public function getOpenid()
{
$openid = Session::get('openid');
if (!$openid) {
if (!isset($_GET['code'])) {
$url = $this->getAuthorizeUrl();
Header("Location: $url");
exit();
} else {
$state = Session::get('state');
if ($state == $_GET['state']) {
$code = $_GET['code'];
$token = $this->getAccessToken($code);
if (!isset($token['openid']) && isset($token['errmsg'])) {
exception($token['errmsg']);
}
$openid = $token['openid'] ?? '';
if ($openid) {
Session::set("openid", $openid);
}
}
}
}
return $openid;
}
/**
* 获取授权token网页授权
*
* @param string $code
* @return mixed|string
*/
public function getAccessToken($code = '')
{
$params = [
'appid' => $this->app_id,
'secret' => $this->app_secret,
'code' => $code,
'grant_type' => 'authorization_code'
];
$ret = Http::sendRequest('https://api.weixin.qq.com/sns/oauth2/access_token', $params, 'GET');
if ($ret['ret']) {
$ar = json_decode($ret['msg'], true);
return $ar;
}
return [];
}
public function getJsticket($code = '')
{
$jsticket = Session::get('jsticket');
if (!$jsticket) {
$token = $this->getAccessToken($code);
$params = [
'access_token' => 'token',
'type' => 'jsapi',
];
$ret = Http::sendRequest('https://api.weixin.qq.com/cgi-bin/ticket/getticket', $params, 'GET');
if ($ret['ret']) {
$ar = json_decode($ret['msg'], true);
return $ar;
}
}
return $jsticket;
}
}

View File

@@ -0,0 +1,2 @@
/tests export-ignore
/.github export-ignore

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Hyperf
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,40 @@
{
"name": "hyperf/context",
"description": "A coroutine context library.",
"license": "MIT",
"keywords": [
"php",
"swoole",
"hyperf",
"context"
],
"homepage": "https://hyperf.io",
"support": {
"docs": "https://hyperf.wiki",
"issues": "https://github.com/hyperf/hyperf/issues",
"pull-request": "https://github.com/hyperf/hyperf/pulls",
"source": "https://github.com/hyperf/hyperf"
},
"require": {
"php": ">=7.2",
"hyperf/engine": "^1.1"
},
"autoload": {
"psr-4": {
"Hyperf\\Context\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"HyperfTest\\Context\\": "tests/"
}
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
}
}
}

View File

@@ -0,0 +1,112 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Context;
use Hyperf\Engine\Coroutine;
class Context
{
protected static $nonCoContext = [];
public static function set(string $id, $value)
{
if (Coroutine::id() > 0) {
Coroutine::getContextFor()[$id] = $value;
} else {
static::$nonCoContext[$id] = $value;
}
return $value;
}
public static function get(string $id, $default = null, $coroutineId = null)
{
if (Coroutine::id() > 0) {
return Coroutine::getContextFor($coroutineId)[$id] ?? $default;
}
return static::$nonCoContext[$id] ?? $default;
}
public static function has(string $id, $coroutineId = null)
{
if (Coroutine::id() > 0) {
return isset(Coroutine::getContextFor($coroutineId)[$id]);
}
return isset(static::$nonCoContext[$id]);
}
/**
* Release the context when you are not in coroutine environment.
*/
public static function destroy(string $id)
{
unset(static::$nonCoContext[$id]);
}
/**
* Copy the context from a coroutine to current coroutine.
* This method will delete the origin values in current coroutine.
*/
public static function copy(int $fromCoroutineId, array $keys = []): void
{
$from = Coroutine::getContextFor($fromCoroutineId);
if ($from === null) {
return;
}
$current = Coroutine::getContextFor();
if ($keys) {
$map = array_intersect_key($from->getArrayCopy(), array_flip($keys));
} else {
$map = $from->getArrayCopy();
}
$current->exchangeArray($map);
}
/**
* Retrieve the value and override it by closure.
*/
public static function override(string $id, \Closure $closure)
{
$value = null;
if (self::has($id)) {
$value = self::get($id);
}
$value = $closure($value);
self::set($id, $value);
return $value;
}
/**
* Retrieve the value and store it if not exists.
* @param mixed $value
*/
public static function getOrSet(string $id, $value)
{
if (! self::has($id)) {
return self::set($id, value($value));
}
return self::get($id);
}
public static function getContainer()
{
if (Coroutine::id() > 0) {
return Coroutine::getContextFor();
}
return static::$nonCoContext;
}
}

View File

@@ -0,0 +1,2 @@
/tests export-ignore
/.github export-ignore

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Hyperf
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,33 @@
{
"name": "hyperf/contract",
"description": "The contracts of Hyperf.",
"license": "MIT",
"keywords": [
"php",
"swoole",
"hyperf"
],
"homepage": "https://hyperf.io",
"support": {
"docs": "https://hyperf.wiki",
"issues": "https://github.com/hyperf/hyperf/issues",
"pull-request": "https://github.com/hyperf/hyperf/pulls",
"source": "https://github.com/hyperf/hyperf"
},
"require": {
"php": ">=7.2"
},
"autoload": {
"psr-4": {
"Hyperf\\Contract\\": "src/"
}
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
}
}
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface ApplicationInterface
{
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface Castable
{
/**
* Get the name of the caster class to use when casting from / to this cast target.
*
* @return CastsAttributes|CastsInboundAttributes|string
*/
public static function castUsing();
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface CastsAttributes
{
/**
* Transform the attribute from the underlying model values.
*
* @param object $model
* @param mixed $value
* @return mixed
*/
public function get($model, string $key, $value, array $attributes);
/**
* Transform the attribute to its underlying model values.
*
* @param object $model
* @param mixed $value
* @return array|string
*/
public function set($model, string $key, $value, array $attributes);
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface CastsInboundAttributes
{
/**
* Transform the attribute to its underlying model values.
*
* @param object $model
* @param mixed $value
* @return array
*/
public function set($model, string $key, $value, array $attributes);
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface CompressInterface
{
public function compress(): UnCompressInterface;
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface ConfigInterface
{
/**
* Finds an entry of the container by its identifier and returns it.
*
* @param string $key identifier of the entry to look for
* @param mixed $default default value of the entry when does not found
* @return mixed entry
*/
public function get(string $key, $default = null);
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* @param string $keys identifier of the entry to look for
* @return bool
*/
public function has(string $keys);
/**
* Set a value to the container by its identifier.
*
* @param string $key identifier of the entry to set
* @param mixed $value the value that save to container
*/
public function set(string $key, $value);
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface ConnectionInterface
{
/**
* Get the real connection from pool.
*/
public function getConnection();
/**
* Reconnect the connection.
*/
public function reconnect(): bool;
/**
* Check the connection is valid.
*/
public function check(): bool;
/**
* Close the connection.
*/
public function close(): bool;
/**
* Release the connection to pool.
*/
public function release(): void;
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
use Psr\Container\ContainerInterface as PsrContainerInterface;
interface ContainerInterface extends PsrContainerInterface
{
/**
* Build an entry of the container by its name.
* This method behave like get() except resolves the entry again every time.
* For example if the entry is a class then a new instance will be created each time.
* This method makes the container behave like a factory.
*
* @param string $name entry name or a class name
* @param array $parameters Optional parameters to use to build the entry. Use this to force specific parameters
* to specific values. Parameters not defined in this array will be resolved using
* the container.
* @throws InvalidArgumentException the name parameter must be of type string
* @throws NotFoundException no entry found for the given name
*/
public function make(string $name, array $parameters = []);
/**
* Bind an arbitrary resolved entry to an identifier.
* Useful for testing 'get'.
*
* @param mixed $entry
*/
public function set(string $name, $entry);
/**
* Unbind an arbitrary resolved entry.
*/
public function unbind(string $name);
/**
* Bind an arbitrary definition to an identifier.
* Useful for testing 'make'.
*
* @param array|callable|string $definition
*/
public function define(string $name, $definition);
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface DispatcherInterface
{
public function dispatch(...$params);
}

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface FrequencyInterface
{
/**
* Number of hit per time.
*/
public function hit(int $number = 1): bool;
/**
* Hits per second.
*/
public function frequency(): float;
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface IdGeneratorInterface
{
public function generate();
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface LengthAwarePaginatorInterface extends PaginatorInterface
{
/**
* Create a range of pagination URLs.
*/
public function getUrlRange(int $start, int $end): array;
/**
* Determine the total number of items in the data store.
*/
public function total(): int;
/**
* Get the page number of the last available page.
*/
public function lastPage(): int;
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface MiddlewareInitializerInterface
{
public function initCoreMiddleware(string $serverName): void;
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface NormalizerInterface
{
/**
* Normalizes an object into a set of arrays/scalars.
*
* @param mixed $object
* @return null|array|\ArrayObject|bool|float|int|string
*/
public function normalize($object);
/**
* Denormalizes data back into an object of the given class.
*
* @param mixed $data Data to restore
* @param string $class The expected class to instantiate
* @return mixed|object
*/
public function denormalize($data, string $class);
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
use Swoole\Http\Response;
use Swoole\Server;
interface OnCloseInterface
{
/**
* @param Response|Server $server
*/
public function onClose($server, int $fd, int $reactorId): void;
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
use Swoole\Http\Request;
use Swoole\Http\Response;
interface OnHandShakeInterface
{
public function onHandShake(Request $request, Response $response): void;
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
use Swoole\Http\Response;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;
interface OnMessageInterface
{
/**
* @param Response|Server $server
*/
public function onMessage($server, Frame $frame): void;
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\WebSocket\Server;
interface OnOpenInterface
{
/**
* @param Response|Server $server
*/
public function onOpen($server, Request $request): void;
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
use Swoole\WebSocket\Server;
interface OnPacketInterface
{
/**
* @param Server $server
* @param mixed $data
* @param array $clientInfo
*/
public function onPacket($server, $data, $clientInfo): void;
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
use Swoole\Coroutine\Server\Connection;
use Swoole\Server as SwooleServer;
interface OnReceiveInterface
{
/**
* @param Connection|SwooleServer $server
*/
public function onReceive($server, int $fd, int $reactorId, string $data): void;
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface OnRequestInterface
{
/**
* @param mixed $request swoole request or psr server request
* @param mixed $response swoole response or swow session
*/
public function onRequest($request, $response): void;
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface PackerInterface
{
public function pack($data): string;
public function unpack(string $data);
}

View File

@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface PaginatorInterface
{
/**
* Get the URL for a given page.
*/
public function url(int $page): string;
/**
* Add a set of query string values to the paginator.
*
* @param array|string $key
* @return $this
*/
public function appends($key, ?string $value = null);
/**
* Get / set the URL fragment to be appended to URLs.
*
* @return $this|string
*/
public function fragment(?string $fragment = null);
/**
* The URL for the next page, or null.
*/
public function nextPageUrl(): ?string;
/**
* Get the URL for the previous page, or null.
*/
public function previousPageUrl(): ?string;
/**
* Get all of the items being paginated.
*/
public function items(): array;
/**
* Get the "index" of the first item being paginated.
*/
public function firstItem(): ?int;
/**
* Get the "index" of the last item being paginated.
*/
public function lastItem(): ?int;
/**
* Determine how many items are being shown per page.
*/
public function perPage(): int;
/**
* Determine the current page being paginated.
*/
public function currentPage(): int;
/**
* Determine if there are enough items to split into multiple pages.
*/
public function hasPages(): bool;
/**
* Determine if there is more items in the data store.
*/
public function hasMorePages(): bool;
/**
* Determine if the list of items is empty or not.
*/
public function isEmpty(): bool;
/**
* Determine if the list of items is not empty.
*/
public function isNotEmpty(): bool;
/**
* Render the paginator using a given view.
*/
public function render(?string $view = null, array $data = []): string;
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface PoolInterface
{
/**
* Get a connection from the connection pool.
*/
public function get(): ConnectionInterface;
/**
* Release a connection back to the connection pool.
*/
public function release(ConnectionInterface $connection): void;
/**
* Close and clear the connection pool.
*/
public function flush(): void;
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface PoolOptionInterface
{
public function getMaxConnections(): int;
public function getMinConnections(): int;
public function getConnectTimeout(): float;
public function getWaitTimeout(): float;
public function getHeartbeat(): float;
public function getMaxIdleTime(): float;
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
use Swoole\Coroutine\Http\Server as CoHttpServer;
use Swoole\Coroutine\Server as CoServer;
use Swoole\Server;
interface ProcessInterface
{
/**
* Create the process object according to process number and bind to server.
* @param CoHttpServer|CoServer|Server $server
*/
public function bind($server): void;
/**
* Determine if the process should start ?
* @param CoServer|Server $server
*/
public function isEnable($server): bool;
/**
* The logical of process will place in here.
*/
public function handle(): void;
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
use Psr\Http\Message\ResponseInterface;
interface ResponseEmitterInterface
{
/**
* @param mixed $connection swoole response or swow session
*/
public function emit(ResponseInterface $response, $connection, bool $withContent = true);
}

View File

@@ -0,0 +1,158 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface SessionInterface
{
/**
* Starts the session storage.
*
* @throws \RuntimeException if session fails to start
* @return bool True if session started
*/
public function start(): bool;
/**
* Returns the session ID.
*
* @return string The session ID
*/
public function getId(): string;
/**
* Sets the session ID.
*/
public function setId(string $id);
/**
* Returns the session name.
*/
public function getName(): string;
/**
* Sets the session name.
*/
public function setName(string $name);
/**
* Invalidates the current session.
*
* Clears all session attributes and flashes and regenerates the
* session and deletes the old session from persistence.
*
* @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
* will leave the system settings unchanged, 0 sets the cookie
* to expire with browser session. Time is in seconds, and is
* not a Unix timestamp.
*
* @return bool True if session invalidated, false if error
*/
public function invalidate(?int $lifetime = null): bool;
/**
* Migrates the current session to a new session id while maintaining all
* session attributes.
*
* @param bool $destroy Whether to delete the old session or leave it to garbage collection
* @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
* will leave the system settings unchanged, 0 sets the cookie
* to expire with browser session. Time is in seconds, and is
* not a Unix timestamp.
*
* @return bool True if session migrated, false if error
*/
public function migrate(bool $destroy = false, ?int $lifetime = null): bool;
/**
* Force the session to be saved and closed.
*
* This method is generally not required for real sessions as
* the session will be automatically saved at the end of
* code execution.
*/
public function save(): void;
/**
* Checks if an attribute is defined.
*
* @param string $name The attribute name
*
* @return bool true if the attribute is defined, false otherwise
*/
public function has(string $name): bool;
/**
* Returns an attribute.
*
* @param string $name The attribute name
* @param mixed $default The default value if not found
*/
public function get(string $name, $default = null);
/**
* Sets an attribute.
* @param mixed $value
*/
public function set(string $name, $value): void;
/**
* Put a key / value pair or array of key / value pairs in the session.
*
* @param array|string $key
* @param null|mixed $value
*/
public function put($key, $value = null): void;
/**
* Returns attributes.
*/
public function all(): array;
/**
* Sets attributes.
*/
public function replace(array $attributes): void;
/**
* Removes an attribute, returning its value.
*
* @return mixed The removed value or null when it does not exist
*/
public function remove(string $name);
/**
* Remove one or many items from the session.
*
* @param array|string $keys
*/
public function forget($keys): void;
/**
* Clears all attributes.
*/
public function clear(): void;
/**
* Checks if the session was started.
*/
public function isStarted(): bool;
/**
* Get the previous URL from the session.
*/
public function previousUrl(): ?string;
/**
* Set the "previous" URL in the session.
*/
public function setPreviousUrl(string $url): void;
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
use Psr\Log\LoggerInterface;
interface StdoutLoggerInterface extends LoggerInterface
{
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface Synchronized
{
/**
* Whether the data has been synchronized.
*/
public function isSynchronized(): bool;
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface TranslatorInterface
{
/**
* Get the translation for a given key.
*/
public function trans(string $key, array $replace = [], ?string $locale = null);
/**
* Get a translation according to an integer value.
*
* @param array|\Countable|int $number
*/
public function transChoice(string $key, $number, array $replace = [], ?string $locale = null): string;
/**
* Get the default locale being used.
*/
public function getLocale(): string;
/**
* Set the default locale.
*/
public function setLocale(string $locale);
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface TranslatorLoaderInterface
{
/**
* Load the messages for the given locale.
*/
public function load(string $locale, string $group, ?string $namespace = null): array;
/**
* Add a new namespace to the loader.
*/
public function addNamespace(string $namespace, string $hint);
/**
* Add a new JSON path to the loader.
*/
public function addJsonPath(string $path);
/**
* Get an array of all the registered namespaces.
*/
public function namespaces(): array;
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface UnCompressInterface
{
public function uncompress();
}

View File

@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
use Hyperf\Utils\Contracts\MessageBag;
use Hyperf\Utils\Contracts\MessageProvider;
interface ValidatorInterface extends MessageProvider
{
/**
* Run the validator's rules against its data.
*/
public function validate(): array;
/**
* Get the attributes and values that were validated.
*/
public function validated(): array;
/**
* Determine if the data fails the validation rules.
*/
public function fails(): bool;
/**
* Get the failed validation rules.
*/
public function failed(): array;
/**
* Add conditions to a given field based on a Closure.
*
* @param array|string $attribute
* @param array|string $rules
* @return $this
*/
public function sometimes($attribute, $rules, callable $callback);
/**
* Add an after validation callback.
*
* @param callable|string $callback
* @return $this
*/
public function after($callback);
/**
* Get all of the validation error messages.
*/
public function errors(): MessageBag;
}

View File

@@ -0,0 +1,4 @@
/.github export-ignore
/examples export-ignore
/tests export-ignore

View File

@@ -0,0 +1,4 @@
/vendor/
composer.lock
*.cache
*.log

View File

@@ -0,0 +1,89 @@
<?php
$header = <<<'EOF'
This file is part of Hyperf.
@link https://www.hyperf.io
@document https://hyperf.wiki
@contact group@hyperf.io
@license https://github.com/hyperf/hyperf/blob/master/LICENSE
EOF;
return (new PhpCsFixer\Config())
->setRiskyAllowed(true)
->setRules([
'@PSR2' => true,
'@Symfony' => true,
'@DoctrineAnnotation' => true,
'@PhpCsFixer' => true,
'header_comment' => [
'comment_type' => 'PHPDoc',
'header' => $header,
'separate' => 'none',
'location' => 'after_declare_strict',
],
'array_syntax' => [
'syntax' => 'short'
],
'list_syntax' => [
'syntax' => 'short'
],
'concat_space' => [
'spacing' => 'one'
],
'blank_line_before_statement' => [
'statements' => [
'declare',
],
],
'general_phpdoc_annotation_remove' => [
'annotations' => [
'author'
],
],
'ordered_imports' => [
'imports_order' => [
'class', 'function', 'const',
],
'sort_algorithm' => 'alpha',
],
'single_line_comment_style' => [
'comment_types' => [
],
],
'yoda_style' => [
'always_move_variable' => false,
'equal' => false,
'identical' => false,
],
'phpdoc_align' => [
'align' => 'left',
],
'multiline_whitespace_before_semicolons' => [
'strategy' => 'no_multi_line',
],
'constant_case' => [
'case' => 'lower',
],
'class_attributes_separation' => true,
'combine_consecutive_unsets' => true,
'declare_strict_types' => true,
'linebreak_after_opening_tag' => true,
'lowercase_static_reference' => true,
'no_useless_else' => true,
'no_unused_imports' => true,
'not_operator_with_successor_space' => true,
'not_operator_with_space' => false,
'ordered_class_elements' => true,
'php_unit_strict' => false,
'phpdoc_separation' => false,
'single_quote' => true,
'standardize_not_equals' => true,
'multiline_comment_opening_closing' => true,
])
->setFinder(
PhpCsFixer\Finder::create()
->exclude('vendor')
->in(__DIR__)
)
->setUsingCache(false);

View File

@@ -0,0 +1,6 @@
<?php
namespace PHPSTORM_META {
// Reflect
override(\Psr\Container\ContainerInterface::get(0), map('@'));
}

View File

@@ -0,0 +1,46 @@
# Default Dockerfile
#
# @link https://www.hyperf.io
# @document https://hyperf.wiki
# @contact group@hyperf.io
# @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
ARG PHP_VERSION
ARG ALPINE_VERSION
FROM hyperf/hyperf:${PHP_VERSION}-alpine-${ALPINE_VERSION}-swoole
LABEL maintainer="Hyperf Developers <group@hyperf.io>" version="1.0" license="MIT" app.name="Hyperf"
ARG timezone
ARG PHP_VERSION
ENV TIMEZONE=${timezone:-"Asia/Shanghai"}
ENV COMPOSER_ROOT_VERSION="v1.2.0"
# update
RUN set -ex \
# show php version and extensions
&& php -v \
&& php -m \
&& php --ri swoole \
# ---------- some config ----------
&& cd "/etc/php${PHP_VERSION%\.*}" \
# - config PHP
&& { \
echo "upload_max_filesize=128M"; \
echo "post_max_size=128M"; \
echo "memory_limit=1G"; \
echo "date.timezone=${TIMEZONE}"; \
} | tee conf.d/99_overrides.ini \
# - config timezone
&& ln -sf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime \
&& echo "${TIMEZONE}" > /etc/timezone \
# ---------- clear works ----------
&& rm -rf /var/cache/apk/* /tmp/* /usr/share/man \
&& echo -e "\033[42;37m Build Completed :).\033[0m\n"
WORKDIR /opt/www
COPY . /opt/www
RUN composer install -o

View File

@@ -0,0 +1,7 @@
# Swoole Engine
![Swoole Engine Test](https://github.com/hyperf/engine/workflows/Swoole%20Engine%20Test/badge.svg)
```
composer require hyperf/engine
```

View File

@@ -0,0 +1,49 @@
{
"name": "hyperf/engine",
"type": "library",
"license": "MIT",
"keywords": [
"php",
"hyperf"
],
"description": "",
"autoload": {
"psr-4": {
"Hyperf\\Engine\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"HyperfTest\\": "tests"
}
},
"require": {
"php": ">=7.4"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.0",
"hyperf/guzzle": "^2.2",
"phpstan/phpstan": "^1.0",
"phpunit/phpunit": "^9.4",
"swoole/ide-helper": "dev-master"
},
"suggest": {
"ext-swoole": ">=4.5"
},
"minimum-stability": "dev",
"prefer-stable": true,
"config": {
"optimize-autoloader": true,
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "1.2-dev"
}
},
"scripts": {
"test": "phpunit -c phpunit.xml --colors=always",
"analyse": "phpstan analyse --memory-limit 1024M -l 0 ./src",
"cs-fix": "php-cs-fixer fix $1"
}
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/bootstrap.php"
backupGlobals="false"
backupStaticAttributes="false"
verbose="true"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuite name="Testsuite">
<directory>./tests/</directory>
</testsuite>
</phpunit>

View File

@@ -0,0 +1,142 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine;
use Hyperf\Engine\Contract\ChannelInterface;
use Hyperf\Engine\Exception\RuntimeException;
if (PHP_VERSION_ID > 80000 && SWOOLE_VERSION_ID >= 50000) {
class Channel extends \Swoole\Coroutine\Channel implements ChannelInterface
{
protected bool $closed = false;
public function push(mixed $data, float $timeout = -1): bool
{
return parent::push($data, $timeout);
}
public function pop(float $timeout = -1): mixed
{
return parent::pop($timeout);
}
public function getCapacity(): int
{
return $this->capacity;
}
public function getLength(): int
{
return $this->length();
}
public function isAvailable(): bool
{
return ! $this->isClosing();
}
public function close(): bool
{
$this->closed = true;
return parent::close();
}
public function hasProducers(): bool
{
throw new RuntimeException('Not supported.');
}
public function hasConsumers(): bool
{
throw new RuntimeException('Not supported.');
}
public function isReadable(): bool
{
throw new RuntimeException('Not supported.');
}
public function isWritable(): bool
{
throw new RuntimeException('Not supported.');
}
public function isClosing(): bool
{
return $this->closed || $this->errCode === SWOOLE_CHANNEL_CLOSED;
}
public function isTimeout(): bool
{
return ! $this->closed && $this->errCode === SWOOLE_CHANNEL_TIMEOUT;
}
}
} else {
class Channel extends \Swoole\Coroutine\Channel implements ChannelInterface
{
/**
* @var bool
*/
protected $closed = false;
public function getCapacity(): int
{
return $this->capacity;
}
public function getLength(): int
{
return $this->length();
}
public function isAvailable(): bool
{
return ! $this->isClosing();
}
public function close(): bool
{
$this->closed = true;
return parent::close();
}
public function hasProducers(): bool
{
throw new RuntimeException('Not supported.');
}
public function hasConsumers(): bool
{
throw new RuntimeException('Not supported.');
}
public function isReadable(): bool
{
throw new RuntimeException('Not supported.');
}
public function isWritable(): bool
{
throw new RuntimeException('Not supported.');
}
public function isClosing(): bool
{
return $this->closed || $this->errCode === SWOOLE_CHANNEL_CLOSED;
}
public function isTimeout(): bool
{
return ! $this->closed && $this->errCode === SWOOLE_CHANNEL_TIMEOUT;
}
}
}

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine;
use Swoole\Coroutine\Http\Server as HttpServer;
use Swoole\Coroutine\Server;
class Constant
{
public const ENGINE = 'Swoole';
public static function isCoroutineServer($server): bool
{
return $server instanceof Server || $server instanceof HttpServer;
}
}

View File

@@ -0,0 +1,134 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Contract;
if (PHP_VERSION_ID > 80000 && SWOOLE_VERSION_ID >= 50000) {
interface ChannelInterface
{
/**
* @param float|int $timeout [optional] = -1
*/
public function push(mixed $data, float $timeout = -1): bool;
/**
* @param float $timeout seconds [optional] = -1
* @return mixed when pop failed, return false
*/
public function pop(float $timeout = -1): mixed;
/**
* Swow: When the channel is closed, all the data in it will be destroyed.
* Swoole: When the channel is closed, the data in it can still be popped out, but push behavior will no longer succeed.
*/
public function close(): bool;
public function getCapacity(): int;
public function getLength(): int;
public function isAvailable(): bool;
public function hasProducers(): bool;
public function hasConsumers(): bool;
public function isEmpty(): bool;
public function isFull(): bool;
public function isReadable(): bool;
public function isWritable(): bool;
public function isClosing(): bool;
public function isTimeout(): bool;
}
} else {
interface ChannelInterface
{
/**
* @param mixed $data [required]
* @param float|int $timeout [optional] = -1
* @return bool
*/
public function push($data, $timeout = -1);
/**
* @param float $timeout seconds [optional] = -1
* @return mixed when pop failed, return false
*/
public function pop($timeout = -1);
/**
* Swow: When the channel is closed, all the data in it will be destroyed.
* Swoole: When the channel is closed, the data in it can still be popped out, but push behavior will no longer succeed.
* @return mixed
*/
public function close(): bool;
/**
* @return int
*/
public function getCapacity();
/**
* @return int
*/
public function getLength();
/**
* @return bool
*/
public function isAvailable();
/**
* @return bool
*/
public function hasProducers();
/**
* @return bool
*/
public function hasConsumers();
/**
* @return bool
*/
public function isEmpty();
/**
* @return bool
*/
public function isFull();
/**
* @return bool
*/
public function isReadable();
/**
* @return bool
*/
public function isWritable();
/**
* @return bool
*/
public function isClosing();
/**
* @return bool
*/
public function isTimeout();
}
}

View File

@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Contract;
use Hyperf\Engine\Exception\CoroutineDestroyedException;
use Hyperf\Engine\Exception\RunningInNonCoroutineException;
interface CoroutineInterface
{
/**
* @param callable $callable [required]
*/
public function __construct(callable $callable);
/**
* @param mixed ...$data
* @return $this
*/
public function execute(...$data);
/**
* @return int
*/
public function getId();
/**
* @param callable $callable [required]
* @param mixed ...$data
* @return $this
*/
public static function create(callable $callable, ...$data);
/**
* @return int returns coroutine id from current coroutine, -1 in non coroutine environment
*/
public static function id();
/**
* Returns the parent coroutine ID.
* Returns 0 when running in the top level coroutine.
* @throws RunningInNonCoroutineException when running in non-coroutine context
* @throws CoroutineDestroyedException when the coroutine has been destroyed
*/
public static function pid(?int $id = null);
/**
* Set config to coroutine.
*/
public static function set(array $config);
/**
* @param null|int $id coroutine id
* @return null|\ArrayObject
*/
public static function getContextFor(?int $id = null);
/**
* Execute callback when coroutine destruct.
*/
public static function defer(callable $callable);
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Contract\Http;
use Hyperf\Engine\Http\RawResponse;
interface ClientInterface
{
public function set(array $settings): bool;
/**
* @param string[][] $headers
*/
public function request(string $method = 'GET', string $path = '/', array $headers = [], string $contents = '', string $version = '1.1'): RawResponse;
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Contract\WebSocket;
interface WebSocketInterface
{
public const ON_MESSAGE = 'message';
public const ON_CLOSE = 'close';
public function on(string $event, callable $callback): void;
public function start(): void;
}

View File

@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine;
use Hyperf\Engine\Contract\CoroutineInterface;
use Hyperf\Engine\Exception\CoroutineDestroyedException;
use Hyperf\Engine\Exception\RunningInNonCoroutineException;
use Hyperf\Engine\Exception\RuntimeException;
use Swoole\Coroutine as SwooleCo;
class Coroutine implements CoroutineInterface
{
/**
* @var callable
*/
private $callable;
/**
* @var int
*/
private $id;
public function __construct(callable $callable)
{
$this->callable = $callable;
}
public static function create(callable $callable, ...$data)
{
$coroutine = new static($callable);
$coroutine->execute(...$data);
return $coroutine;
}
public function execute(...$data)
{
$this->id = SwooleCo::create($this->callable, ...$data);
return $this;
}
public function getId()
{
if (is_null($this->id)) {
throw new RuntimeException('Coroutine was not be executed.');
}
return $this->id;
}
public static function id()
{
return SwooleCo::getCid();
}
public static function pid(?int $id = null)
{
if ($id) {
$cid = SwooleCo::getPcid($id);
if ($cid === false) {
throw new CoroutineDestroyedException(sprintf('Coroutine #%d has been destroyed.', $id));
}
} else {
$cid = SwooleCo::getPcid();
}
if ($cid === false) {
throw new RunningInNonCoroutineException('Non-Coroutine environment don\'t has parent coroutine id.');
}
return max(0, $cid);
}
public static function set(array $config)
{
SwooleCo::set($config);
}
/**
* @return null|\ArrayObject
*/
public static function getContextFor(?int $id = null)
{
if ($id === null) {
return SwooleCo::getContext();
}
return SwooleCo::getContext($id);
}
public static function defer(callable $callable)
{
SwooleCo::defer($callable);
}
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Exception;
class CoroutineDestroyedException extends RuntimeException
{
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Exception;
class HttpClientException extends RuntimeException
{
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Exception;
class RunningInNonCoroutineException extends RuntimeException
{
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Exception;
class RuntimeException extends \RuntimeException
{
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine;
class Extension
{
public static function isLoaded(): bool
{
return extension_loaded('Swoole');
}
}

View File

@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Http;
use Hyperf\Engine\Contract\Http\ClientInterface;
use Hyperf\Engine\Exception\HttpClientException;
use Swoole\Coroutine\Http\Client as HttpClient;
class Client extends HttpClient implements ClientInterface
{
public function set(array $settings): bool
{
return parent::set($settings);
}
/**
* @param string[][] $headers
*/
public function request(string $method = 'GET', string $path = '/', array $headers = [], string $contents = '', string $version = '1.1'): RawResponse
{
$this->setMethod($method);
$this->setData($contents);
$this->setHeaders($this->encodeHeaders($headers));
$this->execute($path);
if ($this->errCode !== 0) {
throw new HttpClientException($this->errMsg, $this->errCode);
}
return new RawResponse(
$this->statusCode,
$this->decodeHeaders($this->headers ?? []),
$this->body,
$version
);
}
/**
* @param string[] $headers
* @return string[][]
*/
private function decodeHeaders(array $headers): array
{
$result = [];
foreach ($headers as $name => $header) {
// The key of header is lower case.
$result[$name][] = $header;
}
if ($this->set_cookie_headers) {
$result['set-cookie'] = $this->set_cookie_headers;
}
return $result;
}
/**
* Swoole engine not support two dimensional array.
* @param string[][] $headers
* @return string[]
*/
private function encodeHeaders(array $headers): array
{
$result = [];
foreach ($headers as $name => $value) {
$result[$name] = is_array($value) ? implode(',', $value) : $value;
}
return $result;
}
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Http;
use Swoole\Http\Response;
class FdGetter
{
public function get(Response $response): int
{
return $response->fd;
}
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Http;
final class RawResponse
{
/**
* @var int
*/
public $statusCode = 0;
/**
* @var string[][]
*/
public $headers = [];
/**
* @var string
*/
public $body = '';
/**
* Protocol version.
* @var string
*/
public $version = '';
/**
* @param string[][] $headers
*/
public function __construct(int $statusCode, array $headers, string $body, string $version)
{
$this->statusCode = $statusCode;
$this->headers = $headers;
$this->body = $body;
$this->version = $version;
}
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine;
class Socket extends \Swoole\Coroutine\Socket
{
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine;
class WaitGroup extends \Swoole\Coroutine\WaitGroup
{
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\WebSocket;
class Frame
{
public const PING = '27890027';
public const PONG = '278a0027';
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\WebSocket;
class Opcode
{
public const CONTINUATION = 0;
public const TEXT = 1;
public const BINARY = 2;
public const CLOSE = 8;
public const PING = 9;
public const PONG = 10;
}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\WebSocket;
use Hyperf\Engine\Contract\WebSocket\WebSocketInterface;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\WebSocket\CloseFrame;
class WebSocket implements WebSocketInterface
{
/**
* @var Response
*/
protected $connection;
/**
* @var array<string, callable>
*/
protected $events = [];
public function __construct(Response $connection, Request $request)
{
$this->connection = $connection;
$this->connection->upgrade();
}
public function on(string $event, callable $callback): void
{
$this->events[$event] = $callback;
}
public function start(): void
{
while (true) {
$frame = $this->connection->recv();
if ($frame === false || $frame instanceof CloseFrame || $frame === '') {
$callback = $this->events[static::ON_CLOSE];
$callback($this->connection, $this->connection->fd);
break;
}
$callback = $this->events[static::ON_MESSAGE];
$callback($this->connection, $frame);
}
$this->connection = null;
$this->events = [];
}
}

View File

@@ -0,0 +1,2 @@
/tests export-ignore
/.github export-ignore

View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) Taylor Otwell
Copyright (c) Hyperf
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,39 @@
{
"name": "hyperf/macroable",
"description": "Hyperf Macroable package which come from illuminate/macroable",
"license": "MIT",
"keywords": [
"php",
"swoole",
"hyperf",
"macroable"
],
"homepage": "https://hyperf.io",
"support": {
"docs": "https://hyperf.wiki",
"issues": "https://github.com/hyperf/hyperf/issues",
"pull-request": "https://github.com/hyperf/hyperf/pulls",
"source": "https://github.com/hyperf/hyperf"
},
"require": {
"php": ">=7.3"
},
"autoload": {
"psr-4": {
"Hyperf\\Macroable\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"HyperfTest\\Macroable\\": "tests/"
}
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
}
}
}

View File

@@ -0,0 +1,129 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Macroable;
use BadMethodCallException;
use Closure;
use ReflectionClass;
use ReflectionMethod;
/**
* This file come from illuminate/macroable,
* thanks Laravel Team provide such a useful class.
*/
trait Macroable
{
/**
* The registered string macros.
*
* @var array
*/
protected static $macros = [];
/**
* Dynamically handle calls to the class.
*
* @param string $method
* @param array $parameters
* @throws \BadMethodCallException
* @return mixed
*/
public static function __callStatic($method, $parameters)
{
if (! static::hasMacro($method)) {
throw new BadMethodCallException(sprintf(
'Method %s::%s does not exist.',
static::class,
$method
));
}
$macro = static::$macros[$method];
if ($macro instanceof Closure) {
$macro = $macro->bindTo(null, static::class);
}
return $macro(...$parameters);
}
/**
* Dynamically handle calls to the class.
*
* @param string $method
* @param array $parameters
* @throws \BadMethodCallException
* @return mixed
*/
public function __call($method, $parameters)
{
if (! static::hasMacro($method)) {
throw new BadMethodCallException(sprintf(
'Method %s::%s does not exist.',
static::class,
$method
));
}
$macro = static::$macros[$method];
if ($macro instanceof Closure) {
$macro = $macro->bindTo($this, static::class);
}
return $macro(...$parameters);
}
/**
* Register a custom macro.
*
* @param string $name
* @param callable|object $macro
*/
public static function macro($name, $macro)
{
static::$macros[$name] = $macro;
}
/**
* Mix another object into the class.
*
* @param object $mixin
* @param bool $replace
*
* @throws \ReflectionException
*/
public static function mixin($mixin, $replace = true)
{
$methods = (new ReflectionClass($mixin))->getMethods(
ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
);
foreach ($methods as $method) {
if ($replace || ! static::hasMacro($method->name)) {
$method->setAccessible(true);
static::macro($method->name, $method->invoke($mixin));
}
}
}
/**
* Checks if macro is registered.
*
* @param string $name
* @return bool
*/
public static function hasMacro($name)
{
return isset(static::$macros[$name]);
}
}

View File

@@ -0,0 +1,2 @@
/tests export-ignore
/.github export-ignore

View File

@@ -0,0 +1,4 @@
/vendor/
composer.lock
*.cache
*.log

View File

@@ -0,0 +1,92 @@
<?php
$header = <<<'EOF'
This file is part of Hyperf.
@link https://www.hyperf.io
@document https://hyperf.wiki
@contact group@hyperf.io
@license https://github.com/hyperf/hyperf/blob/master/LICENSE
EOF;
return (new PhpCsFixer\Config())
->setRiskyAllowed(true)
->setRules([
'@PSR2' => true,
'@Symfony' => true,
'@DoctrineAnnotation' => true,
'@PhpCsFixer' => true,
'header_comment' => [
'comment_type' => 'PHPDoc',
'header' => $header,
'separate' => 'none',
'location' => 'after_declare_strict',
],
'array_syntax' => [
'syntax' => 'short'
],
'list_syntax' => [
'syntax' => 'short'
],
'concat_space' => [
'spacing' => 'one'
],
'blank_line_before_statement' => [
'statements' => [
'declare',
],
],
'general_phpdoc_annotation_remove' => [
'annotations' => [
'author'
],
],
'ordered_imports' => [
'imports_order' => [
'class', 'function', 'const',
],
'sort_algorithm' => 'alpha',
],
'single_line_comment_style' => [
'comment_types' => [
],
],
'yoda_style' => [
'always_move_variable' => false,
'equal' => false,
'identical' => false,
],
'phpdoc_align' => [
'align' => 'left',
],
'multiline_whitespace_before_semicolons' => [
'strategy' => 'no_multi_line',
],
'constant_case' => [
'case' => 'lower',
],
'class_attributes_separation' => true,
'combine_consecutive_unsets' => true,
'declare_strict_types' => true,
'linebreak_after_opening_tag' => true,
'lowercase_static_reference' => true,
'no_useless_else' => true,
'no_unused_imports' => true,
'not_operator_with_successor_space' => true,
'not_operator_with_space' => false,
'ordered_class_elements' => true,
'php_unit_strict' => false,
'phpdoc_separation' => false,
'single_quote' => true,
'standardize_not_equals' => true,
'multiline_comment_opening_closing' => true,
])
->setFinder(
PhpCsFixer\Finder::create()
->exclude('bin')
->exclude('public')
->exclude('runtime')
->exclude('vendor')
->in(__DIR__)
)
->setUsingCache(false);

View File

@@ -0,0 +1,6 @@
<?php
namespace PHPSTORM_META {
// Reflect
override(\Psr\Container\ContainerInterface::get(0), map('@'));
}

View File

@@ -0,0 +1,42 @@
language: php
sudo: required
matrix:
include:
- php: 7.2
env: SW_VERSION="4.4.17"
- php: 7.3
env: SW_VERSION="4.4.17"
- php: 7.4
env: SW_VERSION="4.4.17"
- php: master
env: SW_VERSION="4.4.17"
allow_failures:
- php: master
services:
- mysql
- redis-server
- docker
before_install:
- export PHP_MAJOR="$(`phpenv which php` -r 'echo phpversion();' | cut -d '.' -f 1)"
- export PHP_MINOR="$(`phpenv which php` -r 'echo phpversion();' | cut -d '.' -f 2)"
- echo $PHP_MAJOR
- echo $PHP_MINOR
install:
- cd $TRAVIS_BUILD_DIR
- bash ./tests/swoole.install.sh
- phpenv config-rm xdebug.ini || echo "xdebug not available"
- phpenv config-add ./tests/ci.ini
before_script:
- cd $TRAVIS_BUILD_DIR
- composer config -g process-timeout 900 && composer update
script:
- composer analyse
- composer test

View File

@@ -0,0 +1,195 @@
# Pimple Container
[![Build Status](https://travis-ci.org/hyperf-cloud/pimple-integration.svg?branch=master)](https://travis-ci.org/hyperf-cloud/pimple-integration)
`hyperf/pimple` 是基于 `pimple/pimple` 实现的轻量级符合 `PSR11 规范` 的容器组件。可以减少其他框架使用 Hyperf 组件时的成本。
## 安装
```
composer require "hyperf/pimple:1.1.*"
```
## 使用
```php
<?php
use Hyperf\Pimple\ContainerFactory;
$container = (new ContainerFactory())();
```
### `EasySwoole` 接入 `hyperf/translation`
因为 `EasySwoole` 的容器组件暂时并没有实现 `PSR11` 规范,所以无法直接使用。
1. 首先引入相关组件
```
composer require "hyperf/translation:1.1.*"
composer require "hyperf/config:1.1.*"
```
2. 添加 国际化相关的 Provider
```php
<?php
declare(strict_types=1);
namespace App\Provider;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Contract\ContainerInterface;
use Hyperf\Contract\TranslatorLoaderInterface;
use Hyperf\Pimple\ProviderInterface;
use Hyperf\Translation\FileLoader;
use Hyperf\Utils\Filesystem\Filesystem;
class TranslatorLoaderProvider implements ProviderInterface
{
public function register(ContainerInterface $container)
{
$container->set(TranslatorLoaderInterface::class, function () use ($container) {
$config = $container->get(ConfigInterface::class);
$files = $container->get(Filesystem::class);
$path = $config->get('translation.path');
return make(FileLoader::class, compact('files', 'path'));
});
}
}
```
```php
<?php
declare(strict_types=1);
namespace App\Provider;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Contract\ContainerInterface;
use Hyperf\Contract\TranslatorInterface;
use Hyperf\Contract\TranslatorLoaderInterface;
use Hyperf\Pimple\ProviderInterface;
use Hyperf\Translation\Translator;
class TranslatorProvider implements ProviderInterface
{
public function register(ContainerInterface $container)
{
$container->set(TranslatorInterface::class, function () use ($container) {
$config = $container->get(ConfigInterface::class);
$locale = $config->get('translation.locale');
$fallbackLocale = $config->get('translation.fallback_locale');
$loader = $container->get(TranslatorLoaderInterface::class);
$translator = make(Translator::class, compact('loader', 'locale'));
$translator->setFallback((string) $fallbackLocale);
return $translator;
});
}
}
```
3. `EasySwoole` 事件注册器在 `EasySwooleEvent.php` 中,所以我们需要在 `initialize()` 中初始化我们的容器和国际化组件。
> 以下 Config 组件,可以自行封装,这里方便起见直接配置。
```php
<?php
declare(strict_types=1);
namespace EasySwoole\EasySwoole;
use EasySwoole\EasySwoole\AbstractInterface\Event;
use EasySwoole\EasySwoole\Swoole\EventRegister;
use EasySwoole\Http\Request;
use EasySwoole\Http\Response;
use Hyperf\Config\Config;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Pimple\ContainerFactory;
use App\Provider\TranslatorProvider;
use App\Provider\TranslatorLoaderProvider;
class EasySwooleEvent implements Event
{
public static function initialize()
{
date_default_timezone_set('Asia/Shanghai');
$container = (new ContainerFactory([
TranslatorProvider::class,
TranslatorLoaderProvider::class,
]))();
$container->set(ConfigInterface::class, new Config([
'translation' => [
'locale' => 'zh_CN',
'fallback_locale' => 'en',
'path' => EASYSWOOLE_ROOT . '/storage/languages',
],
]));
}
}
```
4. 修改控制器,使用国际化组件
```php
<?php
declare(strict_types=1);
namespace App\HttpController;
use EasySwoole\Http\AbstractInterface\Controller;
use Hyperf\Contract\TranslatorInterface;
use Hyperf\Utils\ApplicationContext;
use Hyperf\Utils\Codec\Json;
class Index extends Controller
{
public function index()
{
$container = ApplicationContext::getContainer();
$translator = $container->get(TranslatorInterface::class);
$data = [
'message' => $translator->trans('message.hello', ['name' => 'Hyperf']),
];
$this->response()->write(Json::encode($data));
}
}
```
5. 添加国际化配置
```php
// storage/languages/en/message.php
return [
'hello' => 'Hello :name',
];
// storage/languages/zh_CN/message.php
return [
'hello' => '你好 :name',
];
```
6. 测试
```
$ curl http://127.0.0.1:9501/
{"message":"你好 Hyperf"}
```

View File

@@ -0,0 +1,53 @@
{
"name": "hyperf/pimple",
"type": "library",
"license": "MIT",
"keywords": [
"php",
"hyperf",
"container",
"psr11"
],
"description": "Pimple Container",
"autoload": {
"psr-4": {
"Hyperf\\Pimple\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"HyperfTest\\": "tests"
}
},
"require": {
"php": ">=7.4",
"doctrine/instantiator": "^1.0",
"hyperf/utils": "^2.2|^3.0",
"pimple/pimple": "^3.3"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.0",
"mockery/mockery": "^1.3",
"phpstan/phpstan": "^1.0",
"phpunit/phpunit": ">=7.0"
},
"minimum-stability": "dev",
"prefer-stable": true,
"config": {
"optimize-autoloader": true,
"sort-packages": true
},
"scripts": {
"test": "phpunit -c phpunit.xml --colors=always",
"analyse": "phpstan analyse --memory-limit 300M -l 0 ./src",
"cs-fix": "php-cs-fixer fix $1"
},
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
},
"hyperf": {
"config": "Hyperf\\Pimple\\ConfigProvider"
}
}
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/bootstrap.php"
backupGlobals="false"
backupStaticAttributes="false"
verbose="true"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuite name="Testsuite">
<directory>./tests/</directory>
</testsuite>
</phpunit>

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Pimple;
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => [
],
'commands' => [
],
'annotations' => [
'scan' => [
'paths' => [
__DIR__,
],
],
],
];
}
}

View File

@@ -0,0 +1,139 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Pimple;
use Hyperf\Contract\ContainerInterface;
use Hyperf\Pimple\Exception\InvalidDefinitionException;
use Hyperf\Pimple\Exception\NotFoundException;
use Hyperf\Pimple\Exception\NotSupportException;
use Pimple;
use Psr\Container\ContainerInterface as PsrContainerInterface;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
use ReflectionParameter;
class Container implements ContainerInterface
{
/**
* @var Pimple\Container
*/
protected $pimple;
/**
* @var ReflectionClass[]
*/
protected $reflection = [];
public function __construct(Pimple\Container $pimple)
{
$this->pimple = $pimple;
$this->pimple[ContainerInterface::class] = $this;
$this->pimple[PsrContainerInterface::class] = $this;
}
public function get($id)
{
if ($this->has($id)) {
return $this->pimple[$id];
}
return $this->pimple[$id] = $this->make($id);
}
public function has($id): bool
{
return isset($this->pimple[$id]);
}
public function make(string $name, array $parameters = [])
{
if (! class_exists($name)) {
throw new NotFoundException("Entry {$name} is not found.");
}
$ref = $this->reflection[$name] ?? new ReflectionClass($name);
$constructor = $ref->getConstructor();
$args = [];
if ($constructor && $constructor->isPublic()) {
$args = $this->resolveParameters($constructor, $parameters);
}
$instance = new $name(...$args);
$this->reflection[$name] = $ref;
return $instance;
}
public function set(string $name, $entry): void
{
$this->pimple[$name] = $entry;
}
public function define(string $name, $definition): void
{
throw new NotSupportException('Method define is not support.');
}
public function unbind(string $name): void
{
$this->pimple[$name] = null;
}
protected function resolveParameters(ReflectionMethod $method, $parameters = [])
{
$args = [];
foreach ($method->getParameters() as $index => $parameter) {
if (array_key_exists($parameter->getName(), $parameters)) {
$value = $parameters[$parameter->getName()];
} elseif (array_key_exists($index, $parameters)) {
$value = $parameters[$index];
} elseif ($parameter->getType() && $this->has($parameter->getType()->getName())) {
$value = $this->get($parameter->getType()->getName());
} else {
if ($parameter->isDefaultValueAvailable() || $parameter->isOptional()) {
$value = $this->getParameterDefaultValue($parameter, $method);
} else {
throw new InvalidDefinitionException(sprintf(
'Parameter $%s of %s has no value defined or guessable',
$parameter->getName(),
$this->getFunctionName($method)
));
}
}
$args[] = $value;
}
return $args;
}
protected function getParameterDefaultValue(ReflectionParameter $parameter, ReflectionMethod $function)
{
try {
return $parameter->getDefaultValue();
} catch (ReflectionException $e) {
throw new InvalidDefinitionException(sprintf(
'The parameter "%s" of %s has no type defined or guessable. It has a default value, '
. 'but the default value can\'t be read through Reflection because it is a PHP internal class.',
$parameter->getName(),
$this->getFunctionName($function)
));
}
}
private function getFunctionName(ReflectionMethod $method): string
{
return $method->getName() . '()';
}
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Pimple;
use Hyperf\Utils\ApplicationContext;
use Pimple;
class ContainerFactory
{
protected $providers = [];
public function __construct(array $providers = [])
{
$this->providers = $providers;
}
public function __invoke()
{
$container = new Container(new Pimple\Container());
foreach ($this->providers as $provider) {
/** @var ProviderInterface $instance */
$instance = new $provider();
$instance->register($container);
}
return ApplicationContext::setContainer($container);
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Pimple\Exception;
use Psr\Container\ContainerExceptionInterface;
class InvalidDefinitionException extends \Exception implements ContainerExceptionInterface
{
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Pimple\Exception;
use Psr\Container\NotFoundExceptionInterface;
class NotFoundException extends \RuntimeException implements NotFoundExceptionInterface
{
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Pimple\Exception;
use Psr\Container\ContainerExceptionInterface;
class NotSupportException extends \Exception implements ContainerExceptionInterface
{
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Pimple;
use Hyperf\Contract\ContainerInterface;
interface ProviderInterface
{
public function register(ContainerInterface $container);
}

View File

@@ -0,0 +1,2 @@
/tests export-ignore
/.github export-ignore

View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) Taylor Otwell
Copyright (c) Hyperf
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,58 @@
{
"name": "hyperf/utils",
"description": "A tools package that could help developer solved the problem quickly.",
"license": "MIT",
"keywords": [
"php",
"swoole",
"hyperf",
"utils"
],
"homepage": "https://hyperf.io",
"support": {
"docs": "https://hyperf.wiki",
"issues": "https://github.com/hyperf/hyperf/issues",
"pull-request": "https://github.com/hyperf/hyperf/pulls",
"source": "https://github.com/hyperf/hyperf"
},
"require": {
"php": ">=7.2",
"doctrine/inflector": "^2.0",
"hyperf/context": "~2.2.0",
"hyperf/contract": "~2.2.0",
"hyperf/engine": "^1.1",
"hyperf/macroable": "~2.2.0"
},
"suggest": {
"ext-swoole": "Required to use methods related to swoole (>=4.5).",
"symfony/var-dumper": "Required to use the dd function (^5.0).",
"symfony/serializer": "Required to use SymfonyNormalizer (^5.0)",
"symfony/property-access": "Required to use SymfonyNormalizer (^5.0)",
"hyperf/di": "Required to use ExceptionNormalizer",
"nikic/php-parser": "Required to use PhpParser. (^4.0)"
},
"autoload": {
"files": [
"src/Functions.php"
],
"psr-4": {
"Hyperf\\Utils\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"HyperfTest\\Utils\\": "tests/"
}
},
"config": {
"sort-packages": true
},
"extra": {
"hyperf": {
"config": "Hyperf\\Utils\\ConfigProvider"
},
"branch-alias": {
"dev-master": "2.2-dev"
}
}
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils;
use Psr\Container\ContainerInterface;
class ApplicationContext
{
/**
* @var null|ContainerInterface
*/
private static $container;
/**
* @throws \TypeError
*/
public static function getContainer(): ContainerInterface
{
return self::$container;
}
public static function hasContainer(): bool
{
return isset(self::$container);
}
public static function setContainer(ContainerInterface $container): ContainerInterface
{
self::$container = $container;
return $container;
}
}

View File

@@ -0,0 +1,558 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils;
use ArrayAccess;
use Hyperf\Macroable\Macroable;
use InvalidArgumentException;
/**
* Most of the methods in this file come from illuminate/support,
* thanks Laravel Team provide such a useful class.
*/
class Arr
{
use Macroable;
/**
* Determine whether the given value is array accessible.
* @param mixed $value
*/
public static function accessible($value): bool
{
return is_array($value) || $value instanceof ArrayAccess;
}
/**
* Add an element to an array using "dot" notation if it doesn't exist.
* @param mixed $value
*/
public static function add(array $array, string $key, $value): array
{
if (is_null(static::get($array, $key))) {
static::set($array, $key, $value);
}
return $array;
}
/**
* Collapse an array of arrays into a single array.
*/
public static function collapse(array $array): array
{
$results = [];
foreach ($array as $values) {
if ($values instanceof Collection) {
$values = $values->all();
} elseif (! is_array($values)) {
continue;
}
$results[] = $values;
}
return array_merge([], ...$results);
}
/**
* Cross join the given arrays, returning all possible permutations.
*
* @param array ...$arrays
*/
public static function crossJoin(...$arrays): array
{
$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)
{
return [array_keys($array), array_values($array)];
}
/**
* Flatten a multi-dimensional associative array with dots.
*/
public static function dot(array $array, string $prepend = ''): array
{
$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 keys.
*
* @param array|string $keys
*/
public static function except(array $array, $keys): array
{
static::forget($array, $keys);
return $array;
}
/**
* Determine if the given key exists in the provided array.
*
* @param array|\ArrayAccess $array
* @param int|string $key
*/
public static function exists($array, $key): bool
{
if ($array instanceof ArrayAccess) {
return $array->offsetExists($key);
}
return array_key_exists($key, $array);
}
/**
* Return the first element in an array passing a given truth test.
*
* @param null|mixed $default
*/
public static function first(array $array, callable $callback = null, $default = null)
{
if (is_null($callback)) {
if (empty($array)) {
return value($default);
}
foreach ($array as $item) {
return $item;
}
}
foreach ($array as $key => $value) {
if (call_user_func($callback, $value, $key)) {
return $value;
}
}
return value($default);
}
/**
* Return the last element in an array passing a given truth test.
*
* @param null|mixed $default
*/
public static function last(array $array, callable $callback = null, $default = null)
{
if (is_null($callback)) {
return empty($array) ? value($default) : end($array);
}
return static::first(array_reverse($array, true), $callback, $default);
}
/**
* Flatten a multi-dimensional array into a single level.
* @param float|int $depth
*/
public static function flatten(array $array, $depth = INF): array
{
$result = [];
foreach ($array as $item) {
$item = $item instanceof Collection ? $item->all() : $item;
if (! is_array($item)) {
$result[] = $item;
} elseif ($depth === 1) {
$result = array_merge($result, array_values($item));
} else {
$result = array_merge($result, static::flatten($item, $depth - 1));
}
}
return $result;
}
/**
* Remove one or many array items from a given array using "dot" notation.
*
* @param array|string $keys
*/
public static function forget(array &$array, $keys): void
{
$original = &$array;
$keys = (array) $keys;
if (count($keys) === 0) {
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('.', (string) $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|\ArrayAccess $array
* @param null|int|string $key
* @param mixed $default
*/
public static function get($array, $key = null, $default = null)
{
if (! static::accessible($array)) {
return value($default);
}
if (is_null($key)) {
return $array;
}
if (static::exists($array, $key)) {
return $array[$key];
}
if (! is_string($key) || strpos($key, '.') === false) {
return $array[$key] ?? value($default);
}
foreach (explode('.', $key) as $segment) {
if (static::accessible($array) && static::exists($array, $segment)) {
$array = $array[$segment];
} else {
return value($default);
}
}
return $array;
}
/**
* Check if an item or items exist in an array using "dot" notation.
*
* @param array|\ArrayAccess $array
* @param null|array|string $keys
*/
public static function has($array, $keys): bool
{
if (is_null($keys)) {
return false;
}
$keys = (array) $keys;
if (! $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::accessible($subKeyArray) && 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.
*/
public static function isAssoc(array $array): bool
{
$keys = array_keys($array);
return array_keys($keys) !== $keys;
}
/**
* Get a subset of the items from the given array.
*
* @param array|string $keys
*/
public static function only(array $array, $keys): array
{
return array_intersect_key($array, array_flip((array) $keys));
}
/**
* Pluck an array of values from an array.
*
* @param array|string $value
* @param null|array|string $key
*/
public static function pluck(array $array, $value, $key = null): array
{
$results = [];
[$value, $key] = static::explodePluckParameters($value, $key);
foreach ($array as $item) {
$itemValue = data_get($item, $value);
// If the key is "null", we will just append the value to the array and keep
// looping. Otherwise we will key the array using the value of the key we
// received from the developer. Then we'll return the final array form.
if (is_null($key)) {
$results[] = $itemValue;
} else {
$itemKey = data_get($item, $key);
if (is_object($itemKey) && method_exists($itemKey, '__toString')) {
$itemKey = (string) $itemKey;
}
$results[$itemKey] = $itemValue;
}
}
return $results;
}
/**
* Push an item onto the beginning of an array.
*
* @template TKey of array-key
* @template TValue
*
* @param array<TKey, TValue> $array
* @param null|TKey $key
* @param TValue $value
* @return array<TKey, TValue>
*/
public static function prepend(array $array, $value, $key = null): array
{
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 null|mixed $default
*/
public static function pull(array &$array, string $key, $default = null)
{
$value = static::get($array, $key, $default);
static::forget($array, $key);
return $value;
}
/**
* Get one or a specified number of random values from an array.
*
* @throws \InvalidArgumentException
*/
public static function random(array $array, int $number = null)
{
$requested = is_null($number) ? 1 : $number;
$count = count($array);
if ($requested > $count) {
throw new InvalidArgumentException("You requested {$requested} items, but there are only {$count} items available.");
}
if (is_null($number)) {
return $array[array_rand($array)];
}
if ((int) $number === 0) {
return [];
}
$keys = array_rand($array, $number);
$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 null|int|string $key
* @param mixed $value
*/
public static function set(array &$array, $key, $value): array
{
if (is_null($key)) {
return $array = $value;
}
if (! is_string($key)) {
$array[$key] = $value;
return $array;
}
$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;
}
/**
* Shuffle the given array and return the result.
*/
public static function shuffle(array $array, int $seed = null): array
{
if (is_null($seed)) {
shuffle($array);
} else {
srand($seed);
usort($array, function () {
return rand(-1, 1);
});
}
return $array;
}
/**
* Sort the array using the given callback or "dot" notation.
*
* @param null|callable|string $callback
*/
public static function sort(array $array, $callback = null): array
{
return Collection::make($array)->sortBy($callback)->all();
}
/**
* Recursively sort an array by keys and values.
*/
public static function sortRecursive(array $array): array
{
foreach ($array as &$value) {
if (is_array($value)) {
$value = static::sortRecursive($value);
}
}
if (static::isAssoc($array)) {
ksort($array);
} else {
sort($array);
}
return $array;
}
/**
* Convert the array into a query string.
*/
public static function query(array $array): string
{
return http_build_query($array, '', '&', PHP_QUERY_RFC3986);
}
/**
* Filter the array using the given callback.
*/
public static function where(array $array, callable $callback): array
{
return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH);
}
/**
* If the given value is not an array and not null, wrap it in one.
* @param mixed $value
*/
public static function wrap($value): array
{
if (is_null($value)) {
return [];
}
return ! is_array($value) ? [$value] : $value;
}
/**
* Make array elements unique.
*/
public static function unique(array $array): array
{
$result = [];
foreach ($array ?? [] as $key => $item) {
if (is_array($item)) {
$result[$key] = self::unique($item);
} else {
$result[$key] = $item;
}
}
if (! self::isAssoc($result)) {
return array_unique($result);
}
return $result;
}
public static function merge(array $array1, array $array2, bool $unique = true): array
{
$isAssoc = static::isAssoc($array1 ?: $array2);
if ($isAssoc) {
foreach ($array2 as $key => $value) {
if (is_array($value)) {
$array1[$key] = static::merge($array1[$key] ?? [], $value, $unique);
} else {
$array1[$key] = $value;
}
}
} else {
foreach ($array2 as $key => $value) {
if ($unique && in_array($value, $array1, true)) {
continue;
}
$array1[] = $value;
}
$array1 = array_values($array1);
}
return $array1;
}
/**
* Explode the "value" and "key" arguments passed to "pluck".
*
* @param array|string $value
* @param null|array|string $key
*/
protected static function explodePluckParameters($value, $key): array
{
$value = is_string($value) ? explode('.', $value) : $value;
$key = is_null($key) || is_array($key) ? $key : explode('.', $key);
return [$value, $key];
}
}

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils;
class Backoff
{
/**
* Max backoff.
*/
private const CAP = 60 * 1000; // 1 minute
/**
* @var int
*/
private $firstMs;
/**
* Backoff interval.
* @var int
*/
private $currentMs;
/**
* @param int the first backoff in milliseconds
*/
public function __construct(int $firstMs = 0)
{
if ($firstMs < 0) {
throw new \InvalidArgumentException(
'first backoff interval must be greater or equal than 0'
);
}
if ($firstMs > Backoff::CAP) {
throw new \InvalidArgumentException(
sprintf(
'first backoff interval must be less or equal than %d milliseconds',
self::CAP
)
);
}
$this->firstMs = $firstMs;
$this->currentMs = $firstMs;
}
/**
* Sleep until the next execution.
*/
public function sleep(): void
{
if ($this->currentMs === 0) {
return;
}
usleep($this->currentMs * 1000);
// update backoff using Decorrelated Jitter
// see: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
$this->currentMs = rand($this->firstMs, $this->currentMs * 3);
if ($this->currentMs > self::CAP) {
$this->currentMs = self::CAP;
}
}
/**
* Get the next backoff for logging, etc.
* @return int next backoff
*/
public function nextBackoff(): int
{
return $this->currentMs;
}
}

Some files were not shown because too many files have changed in this diff Show More