first commit

This commit is contained in:
Mr.Qin
2022-08-19 19:48:37 +08:00
commit afdd648b65
3275 changed files with 631084 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
<?php
$finder = PhpCsFixer\Finder::create()
->exclude('tests')
->exclude('vendor')
->in(__DIR__);
return (new PhpCsFixer\Config())
->setUsingCache(false)
->setRules([
'@Symfony' => true,
'class_attributes_separation' => true,
'ordered_class_elements' => true,
'ordered_imports' => ['sort_algorithm' => 'alpha'],
'line_ending' => true,
'single_quote' => true,
'array_syntax' => ['syntax' => 'short'],
'general_phpdoc_annotation_remove' => [
'annotations' => [
'author'
],
],
])
->setFinder($finder);

20
vendor/yansongda/supports/LICENSE vendored Normal file
View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2017 yansongda <me@yansongda.cn>
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.

72
vendor/yansongda/supports/README.md vendored Normal file
View File

@@ -0,0 +1,72 @@
<h1 align="center">Supports</h1>
[![Linter Status](https://github.com/yansongda/supports/workflows/Linter/badge.svg)](https://github.com/yansongda/supports/actions)
[![Tester Status](https://github.com/yansongda/supports/workflows/Tester/badge.svg)](https://github.com/yansongda/supports/actions)
[![Latest Stable Version](https://poser.pugx.org/yansongda/supports/v/stable)](https://packagist.org/packages/yansongda/supports)
[![Total Downloads](https://poser.pugx.org/yansongda/supports/downloads)](https://packagist.org/packages/yansongda/supports)
[![Latest Unstable Version](https://poser.pugx.org/yansongda/supports/v/unstable)](https://packagist.org/packages/yansongda/supports)
[![License](https://poser.pugx.org/yansongda/supports/license)](https://packagist.org/packages/yansongda/supports)
handle with array/config/log/guzzle etc.
## About log
```PHP
use Yansongda\Supports\Logger as Log;
use Monolog\Logger;
class ApplicationLogger
{
private static $logger;
/**
* Forward call.
*
* @author yansongda <me@yansongda.cn>
*
* @return mixed
*/
public static function __callStatic(string $method, array $args)
{
return call_user_func_array([self::getLogger(), $method], $args);
}
/**
* Forward call.
*
* @author yansongda <me@yansongda.cn>
*
* @return mixed
*/
public function __call(string $method, array $args)
{
return call_user_func_array([self::getLogger(), $method], $args);
}
/**
* Make a default log instance.
*
* @author yansongda <me@yansongda.cn>
*
* @return Log
*/
public static function getLogger()
{
if (! self::$logger instanceof Logger) {
self::$logger = new Log();
}
return self::$logger;
}
}
```
### Usage
After registerLog, you can use Log service:
```PHP
ApplicationLogger::debug('test', ['test log']);
```

53
vendor/yansongda/supports/composer.json vendored Normal file
View File

@@ -0,0 +1,53 @@
{
"name": "yansongda/supports",
"description": "common components",
"keywords": ["support", "array", "collection", "config", "http", "guzzle", "throttle"],
"support": {
"issues": "https://github.com/yansongda/supports/issues",
"source": "https://github.com/yansongda/supports"
},
"authors": [
{
"name": "yansongda",
"email": "me@yansongda.cn"
}
],
"require": {
"php": ">=7.3"
},
"require-dev": {
"predis/predis": "^1.1",
"phpunit/phpunit": "^9.0",
"mockery/mockery": "^1.4",
"friendsofphp/php-cs-fixer": "^3.0",
"phpstan/phpstan": "^1.1.0",
"symfony/console": "^5.1",
"guzzlehttp/guzzle": "^7.0",
"monolog/monolog": "^2.0"
},
"autoload": {
"files": [
"src/Functions.php"
],
"psr-4": {
"Yansongda\\Supports\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Yansongda\\Supports\\Tests\\": "tests/"
}
},
"suggest": {
"predis/predis": "Allows to use throttle feature",
"symfony/console": "Use stdout logger",
"guzzlehttp/guzzle": "Use http trait",
"monolog/monolog": "Use logger"
},
"scripts": {
"test": "./vendor/bin/phpunit -c phpunit.xml --colors=always",
"cs-fix": "php-cs-fixer fix --dry-run --diff 1>&2",
"analyse": "phpstan analyse --memory-limit 300M -l 5 -c phpstan.neon ./src"
},
"license": "MIT"
}

View File

@@ -0,0 +1,7 @@
parameters:
reportUnmatchedIgnoredErrors: false
ignoreErrors:
-
message: '#Unsafe usage of new static\(\)#'
paths:
- src/Collection.php

624
vendor/yansongda/supports/src/Arr.php vendored Normal file
View File

@@ -0,0 +1,624 @@
<?php
declare(strict_types=1);
namespace Yansongda\Supports;
use ArrayAccess;
use InvalidArgumentException;
/**
* Most of the methods in this file come from illuminate/support and hyperf/support,
* thanks provide such a useful class.
*/
class Arr
{
/**
* 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.
*/
public static function divide(array $array): 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 mixed|null $default
*/
public static function first(array $array, callable $callback = null, $default = null)
{
if (is_null($callback)) {
if (empty($array)) {
return $default;
}
foreach ($array as $item) {
return $item;
}
}
foreach ($array as $key => $value) {
if (call_user_func($callback, $value, $key)) {
return $value;
}
}
return $default;
}
/**
* Return the last element in an array passing a given truth test.
*
* @param mixed|null $default
*/
public static function last(array $array, callable $callback = null, $default = null)
{
if (is_null($callback)) {
return empty($array) ? $default : end($array);
}
return static::first(array_reverse($array, true), $callback, $default);
}
/**
* Flatten a multi-dimensional array into a single level.
*
* @param 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 (1 === $depth) {
$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 (0 === count($keys)) {
return;
}
foreach ($keys as $key) {
// if the exact key exists in the top-level, remove it
if (static::exists($array, $key)) {
unset($array[$key]);
continue;
}
$parts = explode('.', $key);
// clean up before each pass
$array = &$original;
while (count($parts) > 1) {
$part = array_shift($parts);
if (isset($array[$part]) && is_array($array[$part])) {
$array = &$array[$part];
} else {
continue 2;
}
}
unset($array[array_shift($parts)]);
}
}
/**
* Get an item from an array using "dot" notation.
*
* @param array|\ArrayAccess $array
* @param int|string|null $key
* @param mixed $default
*/
public static function get($array, $key = null, $default = null)
{
if (!static::accessible($array)) {
return $default;
}
if (is_null($key)) {
return $array;
}
if (static::exists($array, $key)) {
return $array[$key];
}
if (!is_string($key) || false === strpos($key, '.')) {
return $array[$key] ?? $default;
}
foreach (explode('.', $key) as $segment) {
if (static::accessible($array) && static::exists($array, $segment)) {
$array = $array[$segment];
} else {
return $default;
}
}
return $array;
}
/**
* Check if an item or items exist in an array using "dot" notation.
*
* @param array|\ArrayAccess $array
* @param array|string|null $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 array|string|null $key
*/
public static function pluck(array $array, $value, $key = null): array
{
$results = [];
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.
*
* @param mixed|null $key
* @param mixed $value
*/
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 mixed|null $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 = 1): array
{
$count = count($array);
if ($number > $count) {
throw new InvalidArgumentException("You requested $number items, but there are only $count items available.");
}
if (0 === $number) {
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 int|string|null $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 Closure.
*/
public static function sort(array $array, callable $callback): array
{
$results = [];
foreach ($array as $key => $value) {
$results[$key] = $callback($value);
}
return $results;
}
/**
* 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;
}
public static function query(array $array, int $encodingType = PHP_QUERY_RFC1738): string
{
return http_build_query($array, '', '&', $encodingType);
}
public static function toString(array $array, string $separator = '&'): string
{
$result = '';
foreach ($array as $key => $value) {
$result .= $key.'='.$value.$separator;
}
return substr($result, 0, 0 - Str::length($separator));
}
/**
* 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 $value) {
if ($unique && in_array($value, $array1, true)) {
continue;
}
$array1[] = $value;
}
$array1 = array_values($array1);
}
return $array1;
}
/**
* Convert encoding.
*/
public static function encoding(array $array, string $to_encoding, string $from_encoding = 'gb2312'): array
{
$encoded = [];
foreach ($array as $key => $value) {
$encoded[$key] = is_array($value) ? self::encoding($value, $to_encoding, $from_encoding) :
mb_convert_encoding($value, $to_encoding, $from_encoding);
}
return $encoded;
}
/**
* camelCaseKey.
*
* @param mixed $data
*
* @return mixed
*/
public static function camelCaseKey($data)
{
if (!self::accessible($data) &&
!(is_object($data) && method_exists($data, 'toArray'))) {
return $data;
}
$result = [];
foreach ((is_object($data) ? $data->toArray() : $data) as $key => $value) {
$result[is_string($key) ? Str::camel($key) : $key] = self::camelCaseKey($value);
}
return $result;
}
/**
* snakeCaseKey.
*
* @param mixed $data
*
* @return mixed
*/
public static function snakeCaseKey($data)
{
if (!self::accessible($data) &&
!(is_object($data) && method_exists($data, 'toArray'))) {
return $data;
}
$result = [];
foreach ((is_object($data) ? $data->toArray() : $data) as $key => $value) {
$result[is_string($key) ? Str::snake($key) : $key] = self::snakeCaseKey($value);
}
return $result;
}
}

View File

@@ -0,0 +1,663 @@
<?php
declare(strict_types=1);
namespace Yansongda\Supports;
use ArrayAccess;
use ArrayIterator;
use Countable;
use IteratorAggregate;
use JsonSerializable;
use Serializable;
class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable, Serializable
{
/**
* The collection data.
*
* @var array
*/
protected $items = [];
/**
* set data.
*
* @param mixed $items
*/
public function __construct($items = [])
{
foreach ($this->getArrayableItems($items) as $key => $value) {
$this->set($key, $value);
}
}
/**
* To string.
*/
public function __toString(): string
{
return $this->toJson();
}
/**
* Get a data by key.
*
* @return mixed
*/
public function __get(string $key)
{
return $this->get($key);
}
/**
* Assigns a value to the specified data.
*
* @param mixed $value
*/
public function __set(string $key, $value)
{
$this->set($key, $value);
}
/**
* Whether or not an data exists by key.
*/
public function __isset(string $key): bool
{
return $this->has($key);
}
/**
* Unsets an data by key.
*/
public function __unset(string $key)
{
$this->forget($key);
}
/**
* Wrap the given value in a collection if applicable.
*
* @param mixed $value
*/
public static function wrap($value): self
{
return $value instanceof self ? new static($value) : new static(Arr::wrap($value));
}
/**
* Get the underlying items from the given collection if applicable.
*
* @param array|static $value
*/
public static function unwrap($value): array
{
return $value instanceof self ? $value->all() : $value;
}
/**
* Return all items.
*/
public function all(): array
{
return $this->items;
}
/**
* Return specific items.
*/
public function only(array $keys): array
{
$return = [];
foreach ($keys as $key) {
$value = $this->get($key);
if (!is_null($value)) {
$return[$key] = $value;
}
}
return $return;
}
/**
* Get all items except for those with the specified keys.
*
* @param mixed $keys
*
* @return static
*/
public function except($keys): Collection
{
$keys = is_array($keys) ? $keys : func_get_args();
return new static(Arr::except($this->items, $keys));
}
/**
* Run a filter over each of the items.
*/
public function filter(callable $callback = null): self
{
if ($callback) {
return new static(Arr::where($this->items, $callback));
}
return new static(array_filter($this->items));
}
/**
* Merge the collection with the given items.
*
* @param mixed $items
*/
public function merge($items): self
{
return new static(array_merge($this->items, $this->getArrayableItems($items)));
}
/**
* To determine Whether the specified element exists.
*
* @param string|int $key
*/
public function has($key): bool
{
return !is_null(Arr::get($this->items, $key));
}
/**
* Retrieve the first item.
*
* @return mixed
*/
public function first()
{
return reset($this->items);
}
/**
* Retrieve the last item.
*
* @return mixed
*/
public function last()
{
$end = end($this->items);
reset($this->items);
return $end;
}
/**
* add the item value.
*
* @param string|int|null $key
* @param mixed $value
*/
public function add($key, $value)
{
Arr::set($this->items, $key, $value);
}
/**
* Set the item value.
*
* @param string|int|null $key
* @param mixed $value
*/
public function set($key, $value)
{
Arr::set($this->items, $key, $value);
}
/**
* Retrieve item from Collection.
*
* @param string|int|null $key
* @param mixed $default
*
* @return mixed
*/
public function get($key = null, $default = null)
{
return Arr::get($this->items, $key, $default);
}
/**
* Remove item form Collection.
*
* @param string|int $key
*/
public function forget($key)
{
Arr::forget($this->items, $key);
}
/**
* Get a flattened array of the items in the collection.
*
* @param float|int $depth
*/
public function flatten($depth = INF): self
{
return new static(Arr::flatten($this->items, $depth));
}
/**
* Run a map over each of the items.
*/
public function map(callable $callback): self
{
$keys = array_keys($this->items);
$items = array_map($callback, $this->items, $keys);
return new static(array_combine($keys, $items));
}
/**
* Get and remove the last item from the collection.
*/
public function pop()
{
return array_pop($this->items);
}
/**
* Push an item onto the beginning of the collection.
*
* @param mixed|null $key
* @param mixed $value
*/
public function prepend($value, $key = null): self
{
$this->items = Arr::prepend($this->items, $value, $key);
return $this;
}
/**
* Push an item onto the end of the collection.
*
* @param mixed $value
*/
public function push($value): self
{
$this->offsetSet(null, $value);
return $this;
}
/**
* Get and remove an item from the collection.
*
* @param mixed|null $default
* @param mixed $key
*/
public function pull($key, $default = null)
{
return Arr::pull($this->items, $key, $default);
}
/**
* Put an item in the collection by key.
*
* @param mixed $key
* @param mixed $value
*/
public function put($key, $value): self
{
$this->offsetSet($key, $value);
return $this;
}
/**
* Get one or a specified number of items randomly from the collection.
*
* @throws \InvalidArgumentException
*/
public function random(?int $number = null): self
{
return new static(Arr::random($this->items, $number ?? 1));
}
/**
* Reduce the collection to a single value.
*
* @param mixed|null $initial
*/
public function reduce(callable $callback, $initial = null)
{
return array_reduce($this->items, $callback, $initial);
}
/**
* Reset the keys on the underlying array.
*/
public function values(): self
{
return new static(array_values($this->items));
}
/**
* Determine if all items in the collection pass the given test.
*
* @param callable|string $key
*/
public function every($key): bool
{
$callback = $this->valueRetriever($key);
foreach ($this->items as $k => $v) {
if (!$callback($v, $k)) {
return false;
}
}
return true;
}
/**
* Chunk the underlying collection array.
*/
public function chunk(int $size): self
{
if ($size <= 0) {
return new static();
}
$chunks = [];
foreach (array_chunk($this->items, $size, true) as $chunk) {
$chunks[] = new static($chunk);
}
return new static($chunks);
}
/**
* Sort through each item with a callback.
*/
public function sort(callable $callback = null): self
{
$items = $this->items;
$callback ? uasort($items, $callback) : asort($items);
return new static($items);
}
/**
* Sort the collection using the given callback.
*
* @param callable|string $callback
*/
public function sortBy($callback, int $options = SORT_REGULAR, bool $descending = false): self
{
$results = [];
$callback = $this->valueRetriever($callback);
// First we will loop through the items and get the comparator from a callback
// function which we were given. Then, we will sort the returned values and
// and grab the corresponding values for the sorted keys from this array.
foreach ($this->items as $key => $value) {
$results[$key] = $callback($value, $key);
}
$descending ? arsort($results, $options) : asort($results, $options);
// Once we have sorted all of the keys in the array, we will loop through them
// and grab the corresponding model so we can set the underlying items list
// to the sorted version. Then we'll just return the collection instance.
foreach (array_keys($results) as $key) {
$results[$key] = $this->items[$key];
}
return new static($results);
}
/**
* Sort the collection in descending order using the given callback.
*
* @param callable|string $callback
*/
public function sortByDesc($callback, int $options = SORT_REGULAR): self
{
return $this->sortBy($callback, $options, true);
}
/**
* Sort the collection keys.
*/
public function sortKeys(int $options = SORT_REGULAR, bool $descending = false): self
{
$items = $this->items;
$descending ? krsort($items, $options) : ksort($items, $options);
return new static($items);
}
/**
* Sort the collection keys in descending order.
*/
public function sortKeysDesc(int $options = SORT_REGULAR): self
{
return $this->sortKeys($options, true);
}
public function query(int $encodingType = PHP_QUERY_RFC1738): string
{
return Arr::query($this->all(), $encodingType);
}
public function toString(string $separator = '&'): string
{
return Arr::toString($this->all(), $separator);
}
/**
* Build to array.
*/
public function toArray(): array
{
return $this->all();
}
/**
* Build to json.
*/
public function toJson(int $option = JSON_UNESCAPED_UNICODE): string
{
return json_encode($this->all(), $option);
}
/**
* (PHP 5 &gt;= 5.4.0)<br/>
* Specify data which should be serialized to JSON.
*
* @see http://php.net/manual/en/jsonserializable.jsonserialize.php
*
* @return mixed data which can be serialized by <b>json_encode</b>,
* which is a value of any type other than a resource
*/
public function jsonSerialize()
{
return $this->items;
}
/**
* (PHP 5 &gt;= 5.1.0)<br/>
* String representation of object.
*
* @see http://php.net/manual/en/serializable.serialize.php
*
* @return string the string representation of the object or null
*/
public function serialize(): string
{
return serialize($this->items);
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Retrieve an external iterator.
*
* @see http://php.net/manual/en/iteratoraggregate.getiterator.php
*
* @return ArrayIterator An instance of an object implementing <b>Iterator</b> or
* <b>ArrayIterator</b>
*/
public function getIterator()
{
return new ArrayIterator($this->items);
}
/**
* (PHP 5 &gt;= 5.1.0)<br/>
* Count elements of an object.
*
* @see http://php.net/manual/en/countable.count.php
*
* @return int The custom count as an integer.
* </p>
* <p>
* The return value is cast to an integer
*/
public function count()
{
return count($this->items);
}
/**
* (PHP 5 &gt;= 5.1.0)<br/>
* Constructs the object.
*
* @see http://php.net/manual/en/serializable.unserialize.php
*
* @param string $serialized <p>
* The string representation of the object.
* </p>
*
* @return mixed|void
*/
public function unserialize($serialized)
{
return $this->items = unserialize($serialized);
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Whether a offset exists.
*
* @see http://php.net/manual/en/arrayaccess.offsetexists.php
*
* @param mixed $offset <p>
* An offset to check for.
* </p>
*
* @return bool true on success or false on failure.
* The return value will be casted to boolean if non-boolean was returned
*/
public function offsetExists($offset)
{
return $this->has($offset);
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Offset to unset.
*
* @see http://php.net/manual/en/arrayaccess.offsetunset.php
*
* @param mixed $offset <p>
* The offset to unset.
* </p>
*/
public function offsetUnset($offset)
{
if ($this->offsetExists($offset)) {
$this->forget($offset);
}
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Offset to retrieve.
*
* @see http://php.net/manual/en/arrayaccess.offsetget.php
*
* @param mixed $offset <p>
* The offset to retrieve.
* </p>
*
* @return mixed Can return all value types
*/
public function offsetGet($offset)
{
return $this->offsetExists($offset) ? $this->get($offset) : null;
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Offset to set.
*
* @see http://php.net/manual/en/arrayaccess.offsetset.php
*
* @param mixed $offset <p>
* The offset to assign the value to.
* </p>
* @param mixed $value <p>
* The value to set.
* </p>
*/
public function offsetSet($offset, $value)
{
$this->set($offset, $value);
}
/**
* Determine if the given value is callable, but not a string.
*
* @param mixed $value
*/
protected function useAsCallable($value): bool
{
return !is_string($value) && is_callable($value);
}
/**
* Get a value retrieving callback.
*
* @param mixed $value
*/
protected function valueRetriever($value): callable
{
if ($this->useAsCallable($value)) {
return $value;
}
return function ($item) use ($value) {
return data_get($item, $value);
};
}
/**
* Results array of items from Collection or Arrayable.
*
* @param mixed $items
*/
protected function getArrayableItems($items): array
{
if (is_array($items)) {
return $items;
}
if ($items instanceof self) {
return $items->all();
}
if ($items instanceof JsonSerializable) {
return $items->jsonSerialize();
}
return (array) $items;
}
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Yansongda\Supports;
class Config extends Collection
{
}

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
use Yansongda\Supports\Arr;
use Yansongda\Supports\Collection;
if (!function_exists('collect')) {
/**
* Create a collection from the given value.
*/
function collect(array $value = []): Collection
{
return new Collection($value);
}
}
if (!function_exists('value')) {
/**
* Return the default value of the given value.
*
* @param mixed $value
*
* @return mixed
*/
function value($value)
{
return $value instanceof Closure ? $value() : $value;
}
}
if (!function_exists('data_get')) {
/**
* Get an item from an array or object using "dot" notation.
*
* @param array|int|string|null $key
* @param mixed|null $default
* @param mixed $target
*/
function data_get($target, $key, $default = null)
{
if (is_null($key)) {
return $target;
}
$key = is_array($key) ? $key : explode('.', is_int($key) ? (string) $key : $key);
while (!is_null($segment = array_shift($key))) {
if ('*' === $segment) {
if ($target instanceof Collection) {
$target = $target->all();
} elseif (!is_array($target)) {
return value($default);
}
$result = [];
foreach ($target as $item) {
$result[] = data_get($item, $key);
}
return in_array('*', $key) ? Arr::collapse($result) : $result;
}
if (Arr::accessible($target) && Arr::exists($target, $segment)) {
$target = $target[$segment];
} elseif (is_object($target) && isset($target->{$segment})) {
$target = $target->{$segment};
} else {
return value($default);
}
}
return $target;
}
}

206
vendor/yansongda/supports/src/Logger.php vendored Normal file
View File

@@ -0,0 +1,206 @@
<?php
declare(strict_types=1);
namespace Yansongda\Supports;
use Exception;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Logger as BaseLogger;
use Psr\Log\LoggerInterface;
/**
* @method static void emergency($message, array $context = [])
* @method static void alert($message, array $context = [])
* @method static void critical($message, array $context = [])
* @method static void error($message, array $context = [])
* @method static void warning($message, array $context = [])
* @method static void notice($message, array $context = [])
* @method static void info($message, array $context = [])
* @method static void debug($message, array $context = [])
* @method static void log($message, array $context = [])
*/
class Logger
{
/**
* Logger instance.
*
* @var \Psr\Log\LoggerInterface|null
*/
protected $logger;
/**
* formatter.
*
* @var \Monolog\Formatter\FormatterInterface|null
*/
protected $formatter;
/**
* handler.
*
* @var \Monolog\Handler\AbstractProcessingHandler|null
*/
protected $handler;
/**
* config.
*
* @var array
*/
protected $config = [
'file' => null,
'identify' => 'yansongda.supports',
'level' => BaseLogger::DEBUG,
'type' => 'daily',
'max_files' => 30,
];
/**
* Bootstrap.
*/
public function __construct(array $config = [])
{
$this->setConfig($config);
}
/**
* Forward call.
*
* @throws Exception
*/
public function __call(string $method, array $args): void
{
call_user_func_array([$this->getLogger(), $method], $args);
}
/**
* Set logger.
*/
public function setLogger(LoggerInterface $logger): Logger
{
$this->logger = $logger;
return $this;
}
/**
* Return the logger instance.
*
* @throws Exception
*/
public function getLogger(): LoggerInterface
{
if (is_null($this->logger)) {
$this->logger = $this->createLogger();
}
return $this->logger;
}
public function createLogger(): BaseLogger
{
$handler = $this->getHandler();
$handler->setFormatter($this->getFormatter());
$logger = new BaseLogger($this->config['identify']);
$logger->pushHandler($handler);
return $logger;
}
/**
* setFormatter.
*
* @return $this
*/
public function setFormatter(FormatterInterface $formatter): self
{
$this->formatter = $formatter;
return $this;
}
/**
* getFormatter.
*/
public function getFormatter(): FormatterInterface
{
if (is_null($this->formatter)) {
$this->formatter = $this->createFormatter();
}
return $this->formatter;
}
/**
* createFormatter.
*/
public function createFormatter(): LineFormatter
{
return new LineFormatter(
"%datetime% > %channel%.%level_name% > %message% %context% %extra%\n\n",
null,
false,
true
);
}
/**
* setHandler.
*
* @return $this
*/
public function setHandler(AbstractProcessingHandler $handler): self
{
$this->handler = $handler;
return $this;
}
public function getHandler(): AbstractProcessingHandler
{
if (is_null($this->handler)) {
$this->handler = $this->createHandler();
}
return $this->handler;
}
public function createHandler(): AbstractProcessingHandler
{
$file = $this->config['file'] ?? sys_get_temp_dir().'/logs/'.$this->config['identify'].'.log';
if ('single' === $this->config['type']) {
return new StreamHandler($file, $this->config['level']);
}
return new RotatingFileHandler($file, $this->config['max_files'], $this->config['level']);
}
/**
* setConfig.
*
* @return $this
*/
public function setConfig(array $config): self
{
$this->config = array_merge($this->config, $config);
return $this;
}
/**
* getConfig.
*/
public function getConfig(): array
{
return $this->config;
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Yansongda\Supports\Logger;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Logger;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
class StdoutHandler extends AbstractProcessingHandler
{
/**
* @var \Symfony\Component\Console\Output\OutputInterface
*/
private $output;
public function __construct($level = Logger::DEBUG, bool $bubble = true, ?OutputInterface $output = null)
{
$this->output = $output ?? new ConsoleOutput();
parent::__construct($level, $bubble);
}
/**
* Writes the record down to the log of the implementing handler.
*/
protected function write(array $record): void
{
$this->output->writeln($record['formatted']);
}
}

View File

@@ -0,0 +1,164 @@
<?php
declare(strict_types=1);
namespace Yansongda\Supports;
use Closure;
use Psr\Container\ContainerInterface;
/**
* This file mostly code come from illuminate/pipe and hyperf/utils,
* thanks provide such a useful class.
*/
class Pipeline
{
/**
* The container implementation.
*
* @var \Psr\Container\ContainerInterface
*/
protected $container;
/**
* The object being passed through the pipeline.
*
* @var mixed
*/
protected $passable;
/**
* The array of class pipes.
*
* @var array
*/
protected $pipes = [];
/**
* The method to call on each pipe.
*
* @var string
*/
protected $method = 'handle';
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* Set the object being sent through the pipeline.
*
* @param mixed $passable
*/
public function send($passable): self
{
$this->passable = $passable;
return $this;
}
/**
* Set the array of pipes.
*
* @param array|mixed $pipes
*/
public function through($pipes): self
{
$this->pipes = is_array($pipes) ? $pipes : func_get_args();
return $this;
}
/**
* Set the method to call on the pipes.
*/
public function via(string $method): self
{
$this->method = $method;
return $this;
}
/**
* Run the pipeline with a final destination callback.
*/
public function then(Closure $destination)
{
$pipeline = array_reduce(array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination));
return $pipeline($this->passable);
}
/**
* Get the final piece of the Closure onion.
*/
protected function prepareDestination(Closure $destination): Closure
{
return static function ($passable) use ($destination) {
return $destination($passable);
};
}
/**
* Get a Closure that represents a slice of the application onion.
*/
protected function carry(): Closure
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
if (is_callable($pipe)) {
// If the pipe is an instance of a Closure, we will just call it directly but
// otherwise we'll resolve the pipes out of the container and call it with
// the appropriate method and arguments, returning the results back out.
return $pipe($passable, $stack);
}
if (!is_object($pipe)) {
[$name, $parameters] = $this->parsePipeString($pipe);
// If the pipe is a string we will parse the string and resolve the class out
// of the dependency injection container. We can then build a callable and
// execute the pipe function giving in the parameters that are required.
$pipe = $this->container->get($name);
$parameters = array_merge([$passable, $stack], $parameters);
} else {
// If the pipe is already an object we'll just make a callable and pass it to
// the pipe as-is. There is no need to do any extra parsing and formatting
// since the object we're given was already a fully instantiated object.
$parameters = [$passable, $stack];
}
$carry = method_exists($pipe, $this->method) ? $pipe->{$this->method}(...$parameters) : $pipe(...$parameters);
return $this->handleCarry($carry);
};
};
}
/**
* Parse full pipe string to get name and parameters.
*/
protected function parsePipeString(string $pipe): array
{
[$name, $parameters] = array_pad(explode(':', $pipe, 2), 2, []);
if (is_string($parameters)) {
$parameters = explode(',', $parameters);
}
return [$name, $parameters];
}
/**
* Handle the value returned from each pipe before passing it to the next.
*
* @param mixed $carry
*
* @return mixed
*/
protected function handleCarry($carry)
{
return $carry;
}
}

569
vendor/yansongda/supports/src/Str.php vendored Normal file
View File

@@ -0,0 +1,569 @@
<?php
declare(strict_types=1);
namespace Yansongda\Supports;
use Exception;
/**
* Most of the methods in this file come from illuminate/support.
* thanks provide such a useful class.
*/
class Str
{
/**
* The cache of snake-cased words.
*
* @var array
*/
protected static $snakeCache = [];
/**
* The cache of camel-cased words.
*
* @var array
*/
protected static $camelCache = [];
/**
* The cache of studly-cased words.
*
* @var array
*/
protected static $studlyCache = [];
/**
* Return the remainder of a string after a given value.
*/
public static function after(string $subject, string $search): string
{
return '' === $search ? $subject : array_reverse(explode($search, $subject, 2))[0];
}
/**
* Transliterate a UTF-8 value to ASCII.
*/
public static function ascii(string $value, string $language = 'en'): string
{
$languageSpecific = static::languageSpecificCharsArray($language);
if (!is_null($languageSpecific)) {
$value = str_replace($languageSpecific[0], $languageSpecific[1], $value);
}
foreach (static::charsArray() as $key => $val) {
$value = str_replace($val, $key, $value);
}
return preg_replace('/[^\x20-\x7E]/u', '', $value);
}
/**
* Get the portion of a string before a given value.
*/
public static function before(string $subject, string $search): string
{
return '' === $search ? $subject : explode($search, $subject)[0];
}
/**
* Convert a value to camel case.
*/
public static function camel(string $value): string
{
if (isset(static::$camelCache[$value])) {
return static::$camelCache[$value];
}
return static::$camelCache[$value] = lcfirst(static::studly($value));
}
/**
* Determine if a given string contains a given substring.
*
* @param string|array $needles
*/
public static function contains(string $haystack, $needles): bool
{
foreach ((array) $needles as $needle) {
if ('' !== $needle && false !== mb_strpos($haystack, $needle)) {
return true;
}
}
return false;
}
/**
* Determine if a given string ends with a given substring.
*
* @param string|array $needles
*/
public static function endsWith(string $haystack, $needles): bool
{
foreach ((array) $needles as $needle) {
if (substr($haystack, -strlen($needle)) === (string) $needle) {
return true;
}
}
return false;
}
/**
* Cap a string with a single instance of a given value.
*/
public static function finish(string $value, string $cap): string
{
$quoted = preg_quote($cap, '/');
return preg_replace('/(?:'.$quoted.')+$/u', '', $value).$cap;
}
/**
* Determine if a given string matches a given pattern.
*
* @param string|array $pattern
*/
public static function is($pattern, string $value): bool
{
$patterns = Arr::wrap($pattern);
if (empty($patterns)) {
return false;
}
foreach ($patterns as $pattern) {
// If the given value is an exact match we can of course return true right
// from the beginning. Otherwise, we will translate asterisks and do an
// actual pattern match against the two strings to see if they match.
if ($pattern == $value) {
return true;
}
$pattern = preg_quote($pattern, '#');
// Asterisks are translated into zero-or-more regular expression wildcards
// to make it convenient to check if the strings starts with the given
// pattern such as "library/*", making any string check convenient.
$pattern = str_replace('\*', '.*', $pattern);
if (1 === preg_match('#^'.$pattern.'\z#u', $value)) {
return true;
}
}
return false;
}
/**
* Convert a string to kebab case.
*/
public static function kebab(string $value): string
{
return static::snake($value, '-');
}
/**
* Return the length of the given string.
*/
public static function length(string $value, ?string $encoding = null): int
{
if (null !== $encoding) {
return mb_strlen($value, $encoding);
}
return mb_strlen($value);
}
/**
* Limit the number of characters in a string.
*/
public static function limit(string $value, int $limit = 100, string $end = '...'): string
{
if (mb_strwidth($value, 'UTF-8') <= $limit) {
return $value;
}
return rtrim(mb_strimwidth($value, 0, $limit, '', 'UTF-8')).$end;
}
/**
* Convert the given string to lower-case.
*/
public static function lower(string $value): string
{
return mb_strtolower($value, 'UTF-8');
}
/**
* Limit the number of words in a string.
*/
public static function words(string $value, int $words = 100, string $end = '...'): string
{
preg_match('/^\s*+\S++\s*+{1,'.$words.'}/u', $value, $matches);
if (!isset($matches[0]) || static::length($value) === static::length($matches[0])) {
return $value;
}
return rtrim($matches[0]).$end;
}
/**
* Parse a Class.
*/
public static function parseCallback(string $callback, ?string $default = null): array
{
return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default];
}
/**
* Generate a more truly "random" alpha-numeric string.
*
* @throws Exception
*/
public static function random(int $length = 16): string
{
$string = '';
while (($len = strlen($string)) < $length) {
$size = $length - $len;
$bytes = random_bytes($size);
$string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size);
}
return $string;
}
/**
* Replace a given value in the string sequentially with an array.
*/
public static function replaceArray(string $search, array $replace, string $subject): string
{
foreach ($replace as $value) {
$subject = static::replaceFirst($search, $value, $subject);
}
return $subject;
}
/**
* Replace the first occurrence of a given value in the string.
*/
public static function replaceFirst(string $search, string $replace, string $subject): string
{
if ('' == $search) {
return $subject;
}
$position = strpos($subject, $search);
if (false !== $position) {
return substr_replace($subject, $replace, $position, strlen($search));
}
return $subject;
}
/**
* Replace the last occurrence of a given value in the string.
*/
public static function replaceLast(string $search, string $replace, string $subject): string
{
$position = strrpos($subject, $search);
if (false !== $position) {
return substr_replace($subject, $replace, $position, strlen($search));
}
return $subject;
}
/**
* Begin a string with a single instance of a given value.
*/
public static function start(string $value, string $prefix): string
{
$quoted = preg_quote($prefix, '/');
return $prefix.preg_replace('/^(?:'.$quoted.')+/u', '', $value);
}
/**
* Convert the given string to upper-case.
*/
public static function upper(string $value): string
{
return mb_strtoupper($value, 'UTF-8');
}
/**
* Convert the given string to title case.
*/
public static function title(string $value): string
{
return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8');
}
/**
* Generate a URL friendly "slug" from a given string.
*/
public static function slug(string $title, string $separator = '-', string $language = 'en'): string
{
$title = static::ascii($title, $language);
// Convert all dashes/underscores into separator
$flip = '-' == $separator ? '_' : '-';
$title = preg_replace('!['.preg_quote($flip).']+!u', $separator, $title);
// Replace @ with the word 'at'
$title = str_replace('@', $separator.'at'.$separator, $title);
// Remove all characters that are not the separator, letters, numbers, or whitespace.
$title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', mb_strtolower($title));
// Replace all separator characters and whitespace by a single separator
$title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title);
return trim($title, $separator);
}
/**
* Convert a string to snake case.
*/
public static function snake(string $value, string $delimiter = '_'): string
{
$key = $value;
if (isset(static::$snakeCache[$key][$delimiter])) {
return static::$snakeCache[$key][$delimiter];
}
if (!ctype_lower($value)) {
$value = preg_replace('/\s+/u', '', ucwords($value));
$value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1'.$delimiter, $value));
}
return static::$snakeCache[$key][$delimiter] = $value;
}
/**
* Determine if a given string starts with a given substring.
*
* @param string|array $needles
*/
public static function startsWith(string $haystack, $needles): bool
{
foreach ((array) $needles as $needle) {
if ('' !== $needle && substr($haystack, 0, strlen($needle)) === (string) $needle) {
return true;
}
}
return false;
}
/**
* Convert a value to studly caps case.
*/
public static function studly(string $value): string
{
$key = $value;
if (isset(static::$studlyCache[$key])) {
return static::$studlyCache[$key];
}
$value = ucwords(str_replace(['-', '_'], ' ', $value));
return static::$studlyCache[$key] = str_replace(' ', '', $value);
}
/**
* Returns the portion of string specified by the start and length parameters.
*/
public static function substr(string $string, int $start, ?int $length = null): string
{
return mb_substr($string, $start, $length, 'UTF-8');
}
/**
* Make a string's first character uppercase.
*/
public static function ucfirst(string $string): string
{
return static::upper(static::substr($string, 0, 1)).static::substr($string, 1);
}
/**
* Convert string's encoding.
*/
public static function encoding(string $string, string $to = 'utf-8', string $from = 'gb2312'): string
{
return mb_convert_encoding($string, $to, $from);
}
/**
* Returns the replacements for the ascii method.
*
* Note: Adapted from Stringy\Stringy.
*
* @see https://github.com/danielstjules/Stringy/blob/3.1.0/LICENSE.txt
*/
protected static function charsArray(): array
{
static $charsArray;
if (isset($charsArray)) {
return $charsArray;
}
return $charsArray = [
'0' => ['°', '₀', '۰', ''],
'1' => ['¹', '₁', '۱', ''],
'2' => ['²', '₂', '۲', ''],
'3' => ['³', '₃', '۳', ''],
'4' => ['⁴', '₄', '۴', '٤', ''],
'5' => ['⁵', '₅', '۵', '٥', ''],
'6' => ['⁶', '₆', '۶', '٦', ''],
'7' => ['⁷', '₇', '۷', ''],
'8' => ['⁸', '₈', '۸', ''],
'9' => ['⁹', '₉', '۹', ''],
'a' => ['à', 'á', 'ả', 'ã', 'ạ', 'ă', 'ắ', 'ằ', 'ẳ', 'ẵ', 'ặ', 'â', 'ấ', 'ầ', 'ẩ', 'ẫ', 'ậ', 'ā', 'ą', 'å', 'α', 'ά', 'ἀ', 'ἁ', 'ἂ', 'ἃ', 'ἄ', 'ἅ', 'ἆ', 'ἇ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ὰ', 'ά', 'ᾰ', 'ᾱ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'а', 'أ', 'အ', 'ာ', 'ါ', 'ǻ', 'ǎ', 'ª', 'ა', 'अ', 'ا', '', 'ä'],
'b' => ['б', 'β', 'ب', 'ဗ', 'ბ', ''],
'c' => ['ç', 'ć', 'č', 'ĉ', 'ċ', ''],
'd' => ['ď', 'ð', 'đ', 'ƌ', 'ȡ', 'ɖ', 'ɗ', 'ᵭ', 'ᶁ', 'ᶑ', 'д', 'δ', 'د', 'ض', 'ဍ', 'ဒ', 'დ', ''],
'e' => ['é', 'è', 'ẻ', 'ẽ', 'ẹ', 'ê', 'ế', 'ề', 'ể', 'ễ', 'ệ', 'ë', 'ē', 'ę', 'ě', 'ĕ', 'ė', 'ε', 'έ', 'ἐ', 'ἑ', 'ἒ', 'ἓ', 'ἔ', 'ἕ', 'ὲ', 'έ', 'е', 'ё', 'э', 'є', 'ə', 'ဧ', 'ေ', 'ဲ', 'ე', 'ए', 'إ', 'ئ', ''],
'f' => ['ф', 'φ', 'ف', 'ƒ', 'ფ', ''],
'g' => ['ĝ', 'ğ', 'ġ', 'ģ', 'г', 'ґ', 'γ', 'ဂ', 'გ', 'گ', ''],
'h' => ['ĥ', 'ħ', 'η', 'ή', 'ح', 'ه', 'ဟ', 'ှ', 'ჰ', ''],
'i' => ['í', 'ì', 'ỉ', 'ĩ', 'ị', 'î', 'ï', 'ī', 'ĭ', 'į', 'ı', 'ι', 'ί', 'ϊ', 'ΐ', 'ἰ', 'ἱ', 'ἲ', 'ἳ', 'ἴ', 'ἵ', 'ἶ', 'ἷ', 'ὶ', 'ί', 'ῐ', 'ῑ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'і', 'ї', 'и', 'ဣ', 'ိ', 'ီ', 'ည်', 'ǐ', 'ი', 'इ', 'ی', ''],
'j' => ['ĵ', 'ј', 'Ј', 'ჯ', 'ج', ''],
'k' => ['ķ', 'ĸ', 'к', 'κ', 'Ķ', 'ق', 'ك', 'က', 'კ', 'ქ', 'ک', ''],
'l' => ['ł', 'ľ', 'ĺ', 'ļ', 'ŀ', 'л', 'λ', 'ل', 'လ', 'ლ', ''],
'm' => ['м', 'μ', 'م', 'မ', 'მ', ''],
'n' => ['ñ', 'ń', 'ň', 'ņ', 'ʼn', 'ŋ', 'ν', 'н', 'ن', 'န', 'ნ', ''],
'o' => ['ó', 'ò', 'ỏ', 'õ', 'ọ', 'ô', 'ố', 'ồ', 'ổ', 'ỗ', 'ộ', 'ơ', 'ớ', 'ờ', 'ở', 'ỡ', 'ợ', 'ø', 'ō', 'ő', 'ŏ', 'ο', 'ὀ', 'ὁ', 'ὂ', 'ὃ', 'ὄ', 'ὅ', 'ὸ', 'ό', 'о', 'و', 'θ', 'ို', 'ǒ', 'ǿ', 'º', 'ო', 'ओ', '', 'ö'],
'p' => ['п', 'π', 'ပ', 'პ', 'پ', ''],
'q' => ['', ''],
'r' => ['ŕ', 'ř', 'ŗ', 'р', 'ρ', 'ر', 'რ', ''],
's' => ['ś', 'š', 'ş', 'с', 'σ', 'ș', 'ς', 'س', 'ص', 'စ', 'ſ', 'ს', ''],
't' => ['ť', 'ţ', 'т', 'τ', 'ț', 'ت', 'ط', 'ဋ', 'တ', 'ŧ', 'თ', 'ტ', ''],
'u' => ['ú', 'ù', 'ủ', 'ũ', 'ụ', 'ư', 'ứ', 'ừ', 'ử', 'ữ', 'ự', 'û', 'ū', 'ů', 'ű', 'ŭ', 'ų', 'µ', 'у', 'ဉ', 'ု', 'ူ', 'ǔ', 'ǖ', 'ǘ', 'ǚ', 'ǜ', 'უ', 'उ', '', 'ў', 'ü'],
'v' => ['в', 'ვ', 'ϐ', ''],
'w' => ['ŵ', 'ω', 'ώ', '', 'ွ', ''],
'x' => ['χ', 'ξ', ''],
'y' => ['ý', 'ỳ', 'ỷ', 'ỹ', 'ỵ', 'ÿ', 'ŷ', 'й', 'ы', 'υ', 'ϋ', 'ύ', 'ΰ', 'ي', 'ယ', ''],
'z' => ['ź', 'ž', 'ż', 'з', 'ζ', 'ز', 'ဇ', 'ზ', ''],
'aa' => ['ع', 'आ', 'آ'],
'ae' => ['æ', 'ǽ'],
'ai' => ['ऐ'],
'ch' => ['ч', 'ჩ', 'ჭ', 'چ'],
'dj' => ['ђ', 'đ'],
'dz' => ['џ', 'ძ'],
'ei' => ['ऍ'],
'gh' => ['غ', 'ღ'],
'ii' => ['ई'],
'ij' => ['ij'],
'kh' => ['х', 'خ', 'ხ'],
'lj' => ['љ'],
'nj' => ['њ'],
'oe' => ['ö', 'œ', 'ؤ'],
'oi' => ['ऑ'],
'oii' => ['ऒ'],
'ps' => ['ψ'],
'sh' => ['ш', 'შ', 'ش'],
'shch' => ['щ'],
'ss' => ['ß'],
'sx' => ['ŝ'],
'th' => ['þ', 'ϑ', 'ث', 'ذ', 'ظ'],
'ts' => ['ц', 'ც', 'წ'],
'ue' => ['ü'],
'uu' => ['ऊ'],
'ya' => ['я'],
'yu' => ['ю'],
'zh' => ['ж', 'ჟ', 'ژ'],
'(c)' => ['©'],
'A' => ['Á', 'À', 'Ả', 'Ã', 'Ạ', 'Ă', 'Ắ', 'Ằ', 'Ẳ', 'Ẵ', 'Ặ', 'Â', 'Ấ', 'Ầ', 'Ẩ', 'Ẫ', 'Ậ', 'Å', 'Ā', 'Ą', 'Α', 'Ά', 'Ἀ', 'Ἁ', 'Ἂ', 'Ἃ', 'Ἄ', 'Ἅ', 'Ἆ', 'Ἇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'Ᾰ', 'Ᾱ', 'Ὰ', 'Ά', 'ᾼ', 'А', 'Ǻ', 'Ǎ', '', 'Ä'],
'B' => ['Б', 'Β', 'ब', ''],
'C' => ['Ç', 'Ć', 'Č', 'Ĉ', 'Ċ', ''],
'D' => ['Ď', 'Ð', 'Đ', 'Ɖ', 'Ɗ', 'Ƌ', 'ᴅ', 'ᴆ', 'Д', 'Δ', ''],
'E' => ['É', 'È', 'Ẻ', 'Ẽ', 'Ẹ', 'Ê', 'Ế', 'Ề', 'Ể', 'Ễ', 'Ệ', 'Ë', 'Ē', 'Ę', 'Ě', 'Ĕ', 'Ė', 'Ε', 'Έ', 'Ἐ', 'Ἑ', 'Ἒ', 'Ἓ', 'Ἔ', 'Ἕ', 'Έ', 'Ὲ', 'Е', 'Ё', 'Э', 'Є', 'Ə', ''],
'F' => ['Ф', 'Φ', ''],
'G' => ['Ğ', 'Ġ', 'Ģ', 'Г', 'Ґ', 'Γ', ''],
'H' => ['Η', 'Ή', 'Ħ', ''],
'I' => ['Í', 'Ì', 'Ỉ', 'Ĩ', 'Ị', 'Î', 'Ï', 'Ī', 'Ĭ', 'Į', 'İ', 'Ι', 'Ί', 'Ϊ', 'Ἰ', 'Ἱ', 'Ἳ', 'Ἴ', 'Ἵ', 'Ἶ', 'Ἷ', 'Ῐ', 'Ῑ', 'Ὶ', 'Ί', 'И', 'І', 'Ї', 'Ǐ', 'ϒ', ''],
'J' => [''],
'K' => ['К', 'Κ', ''],
'L' => ['Ĺ', 'Ł', 'Л', 'Λ', 'Ļ', 'Ľ', 'Ŀ', 'ल', ''],
'M' => ['М', 'Μ', ''],
'N' => ['Ń', 'Ñ', 'Ň', 'Ņ', 'Ŋ', 'Н', 'Ν', ''],
'O' => ['Ó', 'Ò', 'Ỏ', 'Õ', 'Ọ', 'Ô', 'Ố', 'Ồ', 'Ổ', 'Ỗ', 'Ộ', 'Ơ', 'Ớ', 'Ờ', 'Ở', 'Ỡ', 'Ợ', 'Ø', 'Ō', 'Ő', 'Ŏ', 'Ο', 'Ό', 'Ὀ', 'Ὁ', 'Ὂ', 'Ὃ', 'Ὄ', 'Ὅ', 'Ὸ', 'Ό', 'О', 'Θ', 'Ө', 'Ǒ', 'Ǿ', '', 'Ö'],
'P' => ['П', 'Π', ''],
'Q' => [''],
'R' => ['Ř', 'Ŕ', 'Р', 'Ρ', 'Ŗ', ''],
'S' => ['Ş', 'Ŝ', 'Ș', 'Š', 'Ś', 'С', 'Σ', ''],
'T' => ['Ť', 'Ţ', 'Ŧ', 'Ț', 'Т', 'Τ', ''],
'U' => ['Ú', 'Ù', 'Ủ', 'Ũ', 'Ụ', 'Ư', 'Ứ', 'Ừ', 'Ử', 'Ữ', 'Ự', 'Û', 'Ū', 'Ů', 'Ű', 'Ŭ', 'Ų', 'У', 'Ǔ', 'Ǖ', 'Ǘ', 'Ǚ', 'Ǜ', '', 'Ў', 'Ü'],
'V' => ['В', ''],
'W' => ['Ω', 'Ώ', 'Ŵ', ''],
'X' => ['Χ', 'Ξ', ''],
'Y' => ['Ý', 'Ỳ', 'Ỷ', 'Ỹ', 'Ỵ', 'Ÿ', 'Ῠ', 'Ῡ', 'Ὺ', 'Ύ', 'Ы', 'Й', 'Υ', 'Ϋ', 'Ŷ', ''],
'Z' => ['Ź', 'Ž', 'Ż', 'З', 'Ζ', ''],
'AE' => ['Æ', 'Ǽ'],
'Ch' => ['Ч'],
'Dj' => ['Ђ'],
'Dz' => ['Џ'],
'Gx' => ['Ĝ'],
'Hx' => ['Ĥ'],
'Ij' => ['IJ'],
'Jx' => ['Ĵ'],
'Kh' => ['Х'],
'Lj' => ['Љ'],
'Nj' => ['Њ'],
'Oe' => ['Œ'],
'Ps' => ['Ψ'],
'Sh' => ['Ш'],
'Shch' => ['Щ'],
'Ss' => ['ẞ'],
'Th' => ['Þ'],
'Ts' => ['Ц'],
'Ya' => ['Я'],
'Yu' => ['Ю'],
'Zh' => ['Ж'],
' ' => ["\xC2\xA0", "\xE2\x80\x80", "\xE2\x80\x81", "\xE2\x80\x82", "\xE2\x80\x83", "\xE2\x80\x84", "\xE2\x80\x85", "\xE2\x80\x86", "\xE2\x80\x87", "\xE2\x80\x88", "\xE2\x80\x89", "\xE2\x80\x8A", "\xE2\x80\xAF", "\xE2\x81\x9F", "\xE3\x80\x80", "\xEF\xBE\xA0"],
];
}
/**
* Returns the language specific replacements for the ascii method.
*
* Note: Adapted from Stringy\Stringy.
*
* @see https://github.com/danielstjules/Stringy/blob/3.1.0/LICENSE.txt
*/
protected static function languageSpecificCharsArray(string $language): ?array
{
static $languageSpecific;
if (!isset($languageSpecific)) {
$languageSpecific = [
'bg' => [
['х', 'Х', 'щ', 'Щ', 'ъ', 'Ъ', 'ь', 'Ь'],
['h', 'H', 'sht', 'SHT', 'a', 'А', 'y', 'Y'],
],
'de' => [
['ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü'],
['ae', 'oe', 'ue', 'AE', 'OE', 'UE'],
],
];
}
return $languageSpecific[$language] ?? null;
}
}

View File

@@ -0,0 +1,126 @@
<?php
declare(strict_types=1);
namespace Yansongda\Supports\Traits;
use Yansongda\Supports\Str;
trait Accessable
{
/**
* __get.
*
* @return mixed
*/
public function __get(string $key)
{
return $this->get($key);
}
/**
* __set.
*
* @param mixed $value
*/
public function __set(string $key, $value): void
{
$this->set($key, $value);
}
/**
* get.
*
* @param mixed $default
*
* @return mixed
*/
public function get(?string $key = null, $default = null)
{
if (is_null($key)) {
return method_exists($this, 'toArray') ? $this->toArray() : $default;
}
$method = 'get'.Str::studly($key);
if (method_exists($this, $method)) {
return $this->{$method}();
}
return $default;
}
/**
* set.
*
* @param mixed $value
*/
public function set(string $key, $value): self
{
$method = 'set'.Str::studly($key);
if (method_exists($this, $method)) {
$this->{$method}($value);
}
return $this;
}
/**
* Whether a offset exists.
*
* @see https://php.net/manual/en/arrayaccess.offsetexists.php
*
* @param mixed $offset an offset to check for
*
* @return bool true on success or false on failure.
*
* The return value will be casted to boolean if non-boolean was returned.
*/
public function offsetExists($offset)
{
return !is_null($this->get($offset));
}
/**
* Offset to retrieve.
*
* @see https://php.net/manual/en/arrayaccess.offsetget.php
*
* @param mixed $offset the offset to retrieve
*
* @return mixed can return all value types
*/
public function offsetGet($offset)
{
return $this->get($offset);
}
/**
* Offset to set.
*
* @see https://php.net/manual/en/arrayaccess.offsetset.php
*
* @param mixed $offset the offset to assign the value to
* @param mixed $value the value to set
*
* @return void
*/
public function offsetSet($offset, $value)
{
$this->set($offset, $value);
}
/**
* Offset to unset.
*
* @see https://php.net/manual/en/arrayaccess.offsetunset.php
*
* @param mixed $offset the offset to unset
*
* @return void
*/
public function offsetUnset($offset)
{
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Yansongda\Supports\Traits;
use ReflectionClass;
use Yansongda\Supports\Str;
trait Arrayable
{
/**
* toArray.
*/
public function toArray(): array
{
$result = [];
foreach ((new ReflectionClass($this))->getProperties() as $item) {
$k = $item->getName();
$method = 'get'.Str::studly($k);
$result[Str::snake($k)] = method_exists($this, $method) ? $this->{$method}() : $this->{$k};
}
return $result;
}
}

View File

@@ -0,0 +1,207 @@
<?php
declare(strict_types=1);
namespace Yansongda\Supports\Traits;
use GuzzleHttp\Client;
use Psr\Http\Message\ResponseInterface;
/**
* Trait HasHttpRequest.
*
* @property string $baseUri
* @property float $timeout
* @property float $connectTimeout
*/
trait HasHttpRequest
{
/**
* Http client.
*
* @var Client|null
*/
protected $httpClient = null;
/**
* Http client options.
*
* @var array
*/
protected $httpOptions = [];
/**
* Send a GET request.
*
* @return array|string
*/
public function get(string $endpoint, array $query = [], array $headers = [])
{
return $this->request('get', $endpoint, [
'headers' => $headers,
'query' => $query,
]);
}
/**
* Send a POST request.
*
* @param string|array $data
*
* @return array|string
*/
public function post(string $endpoint, $data, array $options = [])
{
if (!is_array($data)) {
$options['body'] = $data;
} else {
$options['form_params'] = $data;
}
return $this->request('post', $endpoint, $options);
}
/**
* Send request.
*
* @return array|string
*/
public function request(string $method, string $endpoint, array $options = [])
{
return $this->unwrapResponse($this->getHttpClient()->{$method}($endpoint, $options));
}
/**
* Set http client.
*/
public function setHttpClient(Client $client): self
{
$this->httpClient = $client;
return $this;
}
/**
* Return http client.
*/
public function getHttpClient(): Client
{
if (is_null($this->httpClient)) {
$this->httpClient = $this->getDefaultHttpClient();
}
return $this->httpClient;
}
/**
* Get default http client.
*/
public function getDefaultHttpClient(): Client
{
return new Client($this->getOptions());
}
/**
* setBaseUri.
*/
public function setBaseUri(string $url): self
{
if (property_exists($this, 'baseUri')) {
$parsedUrl = parse_url($url);
$this->baseUri = ($parsedUrl['scheme'] ?? 'http').'://'.
$parsedUrl['host'].(isset($parsedUrl['port']) ? (':'.$parsedUrl['port']) : '');
}
return $this;
}
/**
* getBaseUri.
*/
public function getBaseUri(): string
{
return property_exists($this, 'baseUri') ? $this->baseUri : '';
}
public function getTimeout(): float
{
return property_exists($this, 'timeout') ? $this->timeout : 5.0;
}
public function setTimeout(float $timeout): self
{
if (property_exists($this, 'timeout')) {
$this->timeout = $timeout;
}
return $this;
}
public function getConnectTimeout(): float
{
return property_exists($this, 'connectTimeout') ? $this->connectTimeout : 3.0;
}
public function setConnectTimeout(float $connectTimeout): self
{
if (property_exists($this, 'connectTimeout')) {
$this->connectTimeout = $connectTimeout;
}
return $this;
}
/**
* Get default options.
*/
public function getOptions(): array
{
return array_merge([
'base_uri' => $this->getBaseUri(),
'timeout' => $this->getTimeout(),
'connect_timeout' => $this->getConnectTimeout(),
], $this->getHttpOptions());
}
/**
* setOptions.
*
* @return $this
*/
public function setOptions(array $options): self
{
return $this->setHttpOptions($options);
}
public function getHttpOptions(): array
{
return $this->httpOptions;
}
public function setHttpOptions(array $httpOptions): self
{
$this->httpOptions = $httpOptions;
return $this;
}
/**
* Convert response.
*
* @return array|string
*/
public function unwrapResponse(ResponseInterface $response)
{
$contentType = $response->getHeaderLine('Content-Type');
$contents = $response->getBody()->getContents();
if (false !== stripos($contentType, 'json') || stripos($contentType, 'javascript')) {
return json_decode($contents, true);
} elseif (false !== stripos($contentType, 'xml')) {
return json_decode(json_encode(simplexml_load_string($contents, 'SimpleXMLElement', LIBXML_NOCDATA), JSON_UNESCAPED_UNICODE), true);
}
return $contents;
}
}

View File

@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace Yansongda\Supports\Traits;
use RuntimeException;
trait Serializable
{
/**
* toJson.
*/
public function toJson(): string
{
return $this->serialize();
}
/**
* Specify data which should be serialized to JSON.
*
* @see https://php.net/manual/en/jsonserializable.jsonserialize.php
*
* @return mixed data which can be serialized by <b>json_encode</b>,
* which is a value of any type other than a resource
*
* @since 5.4.0
*/
public function jsonSerialize()
{
if (method_exists($this, 'toArray')) {
return $this->toArray();
}
return [];
}
/**
* String representation of object.
*
* @see https://php.net/manual/en/serializable.serialize.php
*
* @return string the string representation of the object or null
*
* @since 5.1.0
*/
public function serialize()
{
if (method_exists($this, 'toArray')) {
return json_encode($this->toArray());
}
return json_encode([]);
}
/**
* Constructs the object.
*
* @see https://php.net/manual/en/serializable.unserialize.php
*
* @param string $serialized <p>
* The string representation of the object.
* </p>
*
* @since 5.1.0
*/
public function unserialize($serialized)
{
$data = json_decode($serialized, true);
if (JSON_ERROR_NONE !== json_last_error()) {
throw new RuntimeException('Invalid Json Format');
}
$this->unserializeArray($data);
}
public function unserializeArray(array $data): void
{
foreach ($data as $key => $item) {
if (method_exists($this, 'set')) {
$this->set($key, $item);
}
}
}
}

View File

@@ -0,0 +1,116 @@
<?php
declare(strict_types=1);
namespace Yansongda\Supports\Traits;
/**
* Trait ShouldThrottle.
*
* @property \Redis $redis
*/
trait ShouldThrottle
{
/**
* @var array
*/
protected $throttle = [
'limit' => 60,
'period' => 60,
'count' => 0,
'reset_time' => 0,
];
/**
* isThrottled.
*/
public function isThrottled(string $key, int $limit = 60, int $period = 60, bool $autoAdd = false): bool
{
if (-1 === $limit) {
return false;
}
$now = microtime(true) * 1000;
$this->redis->zRemRangeByScore($key, 0, $now - $period * 1000);
$this->throttle = [
'limit' => $limit,
'period' => $period,
'count' => $this->getThrottleCounts($key, $period),
'reset_time' => $this->getThrottleResetTime($key, $now),
];
if ($this->throttle['count'] < $limit) {
if ($autoAdd) {
$this->throttleAdd($key, $period);
}
return false;
}
return true;
}
/**
* 限流 + 1.
*/
public function throttleAdd(string $key, int $period = 60): void
{
$now = microtime(true) * 1000;
$this->redis->zAdd($key, $now, $now);
$this->redis->expire($key, $period * 2);
}
/**
* 获取下次重置时间.
*
* @param float $now 现在的毫秒时间
*/
public function getThrottleResetTime(string $key, float $now): int
{
$data = $this->redis->zRangeByScore(
$key,
$now - $this->throttle['period'] * 1000,
$now,
['limit' => [0, 1]]
);
if (0 === count($data)) {
return $this->throttle['reset_time'] = time() + $this->throttle['period'];
}
return intval(reset($data) / 1000) + $this->throttle['period'];
}
/**
* 获取限流相关信息.
*
* @param mixed $default
*
* @return mixed
*/
public function getThrottleInfo(?string $key = null, $default = null)
{
if (is_null($key)) {
return $this->throttle;
}
if (isset($this->throttle[$key])) {
return $this->throttle[$key];
}
return $default;
}
/**
* 获取已使用次数.
*/
public function getThrottleCounts(string $key, int $period = 60): int
{
$now = microtime(true) * 1000;
return $this->redis->zCount($key, $now - $period * 1000, $now);
}
}