- 框架初始化
 - 安装插件
 - 修复PHP8.4报错
This commit is contained in:
2025-04-19 17:21:20 +08:00
commit c6a4e1f5f6
5306 changed files with 967782 additions and 0 deletions

View File

@@ -0,0 +1,605 @@
<?php
namespace Yansongda\Supports;
use ArrayAccess;
/**
* Array helper from Illuminate\Support\Arr.
*/
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;
}
/**
* Build a new array using a callback.
*/
public static function build(array $array, callable $callback): array
{
$results = [];
foreach ($array as $key => $value) {
[$innerKey, $innerValue] = call_user_func($callback, $key, $value);
$results[$innerKey] = $innerValue;
}
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)) {
$results = array_merge($results, static::dot($value, $prepend.$key.'.'));
} else {
$results[$prepend.$key] = $value;
}
}
return $results;
}
/**
* Get all of the given array except for a specified array of items.
*
* @param array|string $keys
*/
public static function except(array $array, $keys): array
{
return array_diff_key($array, array_flip((array) $keys));
}
/**
* access array.
*
* if not array access, return original.
*
* @author yansongda <me@yansongda.cn>
*
* @param mixed $data
*
* @return mixed
*/
public static function access($data)
{
if (!self::accessible($data) &&
!(is_object($data) && method_exists($data, 'toArray'))) {
return $data;
}
return is_object($data) ? $data->toArray() : $data;
}
/**
* Determine if the given key exists in the provided array.
*
* @param \ArrayAccess|array $array
* @param string|int $key
*
* @return bool
*/
public static function exists($array, $key)
{
$array = self::access($array);
if ($array instanceof ArrayAccess) {
return $array->offsetExists($key);
}
return array_key_exists($key, $array);
}
/**
* Check if an item or items exist in an array using "dot" notation.
*
* @param \ArrayAccess|array $array
* @param string|array $keys
*
* @return bool
*/
public static function has($array, $keys)
{
$array = self::access($array);
$keys = (array) $keys;
if (!$array || $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;
}
/**
* Determine if any of the keys exist in an array using "dot" notation.
*
* @param \ArrayAccess|array $array
* @param string|array $keys
*
* @return bool
*/
public static function hasAny($array, $keys)
{
$array = self::access($array);
if (is_null($keys)) {
return false;
}
$keys = (array) $keys;
if (!$array) {
return false;
}
if ($keys === []) {
return false;
}
foreach ($keys as $key) {
if (static::has($array, $key)) {
return true;
}
}
return false;
}
/**
* Fetch a flattened array of a nested array element.
*/
public static function fetch(array $array, ?string $key): array
{
$results = [];
foreach (explode('.', $key) as $segment) {
$results = [];
foreach ($array as $value) {
$value = (array) $value;
$results[] = $value[$segment];
}
$array = array_values($results);
}
return array_values($results);
}
/**
* Return the first element in an array passing a given truth test.
*
* @param mixed $default
*
* @return mixed
*/
public static function first(array $array, callable $callback, $default = null)
{
foreach ($array as $key => $value) {
if (call_user_func($callback, $key, $value)) {
return $value;
}
}
return $default;
}
/**
* Return the last element in an array passing a given truth test.
*
* @param mixed $default
*
* @return mixed
*/
public static function last(array $array, callable $callback, $default = null)
{
return static::first(array_reverse($array), $callback, $default);
}
/**
* Flatten a multi-dimensional array into a single level.
*/
public static function flatten(array $array): array
{
$return = [];
array_walk_recursive(
$array,
function ($x) use (&$return) {
$return[] = $x;
}
);
return $return;
}
/**
* Remove one or many array items from a given array using "dot" notation.
*
* @param array $array
* @param array|string $keys
*/
public static function forget(&$array, $keys)
{
$original = &$array;
$keys = (array) $keys;
if (0 === count($keys)) {
return;
}
foreach ($keys as $key) {
// if the exact key exists in the top-level, remove it
if (static::exists($array, $key)) {
unset($array[$key]);
continue;
}
$parts = explode('.', $key);
// clean up before each pass
$array = &$original;
while (count($parts) > 1) {
$part = array_shift($parts);
if (isset($array[$part]) && is_array($array[$part])) {
$array = &$array[$part];
} else {
continue 2;
}
}
unset($array[array_shift($parts)]);
}
}
/**
* Get an item from an array using "dot" notation.
*
* @param mixed $default
*
* @return mixed
*/
public static function get(array $array, ?string $key, $default = null)
{
if (is_null($key)) {
return $array;
}
if (isset($array[$key])) {
return $array[$key];
}
foreach (explode('.', $key) as $segment) {
if (!is_array($array) || !array_key_exists($segment, $array)) {
return $default;
}
$array = $array[$segment];
}
return $array;
}
/**
* 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 string $key
*/
public static function pluck(array $array, ?string $value, ?string $key = null): array
{
$results = [];
foreach ($array as $item) {
$itemValue = is_object($item) ? $item->{$value} : $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 = is_object($item) ? $item->{$key} : $item[$key];
$results[$itemKey] = $itemValue;
}
}
return $results;
}
/**
* Push an item onto the beginning of an array.
*
* @param mixed $value
* @param mixed $key
*
* @return array
*/
public static function prepend(array $array, $value, $key = null)
{
if (is_null($key)) {
array_unshift($array, $value);
} else {
$array = [$key => $value] + $array;
}
return $array;
}
/**
* Get a value from the array, and remove it.
*
* @param mixed $default
*
* @return mixed
*/
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.
*
* @param array $array
* @param int|null $number
*
* @return mixed
*
* @throws \InvalidArgumentException
*/
public static function random(array $array, $number = null)
{
$requested = is_null($number) ? 1 : $number;
$count = count($array);
$number = $requested > $count ? $count : $requested;
if (is_null($number)) {
return $array[array_rand($array)];
}
if (0 === (int) $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 mixed $value
*/
public static function set(array &$array, ?string $key, $value): array
{
if (is_null($key)) {
return $array = $value;
}
$keys = explode('.', $key);
while (count($keys) > 1) {
$key = array_shift($keys);
// If the key doesn't exist at this depth, we will just create an empty array
// to hold the next value, allowing us to create the arrays to hold final
// values at the correct depth. Then we'll keep digging into the array.
if (!isset($array[$key]) || !is_array($array[$key])) {
$array[$key] = [];
}
$array = &$array[$key];
}
$array[array_shift($keys)] = $value;
return $array;
}
/**
* 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;
}
/**
* Shuffle the given array and return the result.
*
* @param array $array
* @param int|null $seed
*
* @return array
*/
public static function shuffle(array $array, $seed = null): array
{
if (is_null($seed)) {
shuffle($array);
} else {
mt_srand($seed);
shuffle($array);
mt_srand();
}
return $array;
}
/**
* Convert the array into a query string.
*/
public static function query(array $array): string
{
return http_build_query($array, null, '&', PHP_QUERY_RFC3986);
}
/**
* Filter the array using the given callback.
*/
public static function where(array $array, ?callable $callback = null): array
{
return array_filter($array, $callback ?? function ($value) use ($callback) {
if (static::accessible($value)) {
$value = static::where($value, $callback);
}
if (is_array($value) && 0 === count($value)) {
$value = null;
}
return '' !== $value && !is_null($value);
}, ARRAY_FILTER_USE_BOTH);
}
/**
* Convert encoding.
*
* @author yansongda <me@yansongda.cn>
*
* @param string $from_encoding
*/
public static function encoding(array $array, ?string $to_encoding, $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.
*
* @author yansongda <me@yansongda.cn>
*
* @param mixed $data
*
* @return mixed
*/
public static function camelCaseKey($data)
{
if (!self::accessible($data) &&
!(is_object($data) && method_exists($data, 'toArray'))) {
return $data;
}
$result = [];
$data = self::access($data);
foreach ($data as $key => $value) {
$result[is_string($key) ? Str::camel($key) : $key] = self::camelCaseKey($value);
}
return $result;
}
/**
* snakeCaseKey.
*
* @author yansongda <me@yansongda.cn>
*
* @param mixed $data
*
* @return mixed
*/
public static function snakeCaseKey($data)
{
if (!self::accessible($data) &&
!(is_object($data) && method_exists($data, 'toArray'))) {
return $data;
}
$data = self::access($data);
$result = [];
foreach ($data as $key => $value) {
$result[is_string($key) ? Str::snake($key) : $key] = self::snakeCaseKey($value);
}
return $result;
}
}

View File

@@ -0,0 +1,363 @@
<?php
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(array $items = [])
{
foreach ($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);
}
/**
* 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)
{
$keys = is_array($keys) ? $keys : func_get_args();
return new static(Arr::except($this->items, $keys));
}
/**
* Merge data.
*
* @param Collection|array $items
*/
public function merge($items): array
{
foreach ($items as $key => $value) {
$this->set($key, $value);
}
return $this->all();
}
/**
* To determine Whether the specified element exists.
*/
public function has(string $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 mixed $value
*/
public function add(string $key, $value)
{
Arr::set($this->items, $key, $value);
}
/**
* Set the item value.
*
* @param mixed $value
*/
public function set(string $key, $value)
{
Arr::set($this->items, $key, $value);
}
/**
* Retrieve item from Collection.
*
* @param string $key
* @param mixed $default
*
* @return mixed
*/
public function get(?string $key = null, $default = null)
{
return Arr::get($this->items, $key, $default);
}
/**
* Remove item form Collection.
*/
public function forget(string $key)
{
Arr::forget($this->items, $key);
}
/**
* 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()
{
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);
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Yansongda\Supports;
class Config extends Collection
{
}

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.

View File

@@ -0,0 +1,91 @@
<?php
namespace Yansongda\Supports;
/**
* @method static void emergency($message, ?array $context = array())
* @method static void alert($message, ?array $context = array())
* @method static void critical($message, ?array $context = array())
* @method static void error($message, ?array $context = array())
* @method static void warning($message, ?array $context = array())
* @method static void notice($message, ?array $context = array())
* @method static void info($message, ?array $context = array())
* @method static void debug($message, ?array $context = array())
* @method static void log($message, ?array $context = array())
*/
class Log extends Logger
{
/**
* instance.
*
* @var \Psr\Log\LoggerInterface
*/
private static $instance;
/**
* Bootstrap.
*/
private function __construct()
{
}
/**
* __call.
*
* @author yansongda <me@yansongda.cn>
*
* @param string $method
* @param array $args
*
* @throws \Exception
*/
public function __call($method, $args): void
{
call_user_func_array([self::getInstance(), $method], $args);
}
/**
* __callStatic.
*
* @author yansongda <me@yansongda.cn>
*
* @param string $method
* @param array $args
*
* @throws \Exception
*/
public static function __callStatic($method, $args): void
{
forward_static_call_array([self::getInstance(), $method], $args);
}
/**
* getInstance.
*
* @author yansongda <me@yansongda.cn>
*
* @return \Yansongda\Supports\Logger
*/
public static function getInstance(): Logger
{
if (is_null(self::$instance)) {
self::$instance = new Logger();
}
return self::$instance;
}
/**
* setInstance.
*
* @author yansongda <me@yansongda.cn>
*
* @param \Yansongda\Supports\Logger $logger
*
* @throws \Exception
*/
public static function setInstance(Logger $logger): void
{
self::$instance = $logger;
}
}

View File

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

View File

@@ -0,0 +1,36 @@
<?php
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 OutputInterface
*/
private $output;
/**
* Bootstrap.
*
* @param int $level
* @param bool $bubble
*/
public function __construct($level = Logger::DEBUG, $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,570 @@
<?php
namespace Yansongda\Supports;
use Exception;
/**
* modify from Illuminate\Support.
*/
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 = is_array($pattern) ? $pattern : (array) $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.
*
* @param string $encoding
*/
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 = function_exists('random_bytes') ? random_bytes($size) : mt_rand();
$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.
*
* @author yansongda <me@yansonga.cn>
*/
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 isset($languageSpecific[$language]) ? $languageSpecific[$language] : null;
}
}

View File

@@ -0,0 +1,142 @@
<?php
declare(strict_types=1);
namespace Yansongda\Supports\Traits;
trait Accessable
{
/**
* __get.
*
* @author yansongda <me@yansongda.cn>
*
* @return mixed
*/
public function __get(string $key)
{
return $this->get($key);
}
/**
* __set.
*
* @author yansongda <me@yansongda.cn>
*
* @param mixed $value
*
* @return mixed
*/
public function __set(string $key, $value)
{
return $this->set($key, $value);
}
/**
* get.
*
* @author yansongda <me@yansongda.cn>
*
* @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';
foreach (explode('_', $key) as $item) {
$method .= ucfirst($item);
}
if (method_exists($this, $method)) {
return $this->{$method}();
}
return $default;
}
/**
* set.
*
* @author yansongda <me@yansongda.cn>
*
* @param mixed $value
*
* @return $this
*/
public function set(string $key, $value)
{
$method = 'set';
foreach (explode('_', $key) as $item) {
$method .= ucfirst($item);
}
if (method_exists($this, $method)) {
return $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,32 @@
<?php
declare(strict_types=1);
namespace Yansongda\Supports\Traits;
use ReflectionClass;
use Yansongda\Supports\Str;
trait Arrayable
{
/**
* toArray.
*
* @author yansongda <me@yansongda.cn>
*
* @throws \ReflectionException
*/
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,229 @@
<?php
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.
*
* @author yansongda <me@yansongda.cn>
*
* @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.
*
* @author yansongda <me@yansongda.cn>
*
* @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.
*
* @author yansongda <me@yansongda.cn>
*
* @return array|string
*/
public function request(string $method, ?string $endpoint, ?array $options = [])
{
return $this->unwrapResponse($this->getHttpClient()->{$method}($endpoint, $options));
}
/**
* Set http client.
*
* @author yansongda <me@yansongda.cn>
*
* @return $this
*/
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.
*
* @author yansongda <me@yansongda.cn>
*/
public function getDefaultHttpClient(): Client
{
return new Client($this->getOptions());
}
/**
* setBaseUri.
*
* @author yansongda <me@yansongda.cn>
*
* @return $this
*/
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.
*
* @author yansongda <me@yansongda.cn>
*/
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.
*
* @author yansongda <me@yansongda.cn>
*/
public function getOptions(): array
{
return array_merge([
'base_uri' => $this->getBaseUri(),
'timeout' => $this->getTimeout(),
'connect_timeout' => $this->getConnectTimeout(),
], $this->getHttpOptions());
}
/**
* setOptions.
*
* @author yansongda <me@yansongda.cn>
*
* @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.
*
* @author yansongda <me@yansongda.cn>
*
* @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,85 @@
<?php
declare(strict_types=1);
namespace Yansongda\Supports\Traits;
use RuntimeException;
trait Serializable
{
/**
* toJson.
*
* @author yansongda <me@yansongda.cn>
*
* @return string
*/
public function toJson()
{
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');
}
foreach ($data as $key => $item) {
if (method_exists($this, 'set')) {
$this->set($key, $item);
}
}
}
}

View File

@@ -0,0 +1,147 @@
<?php
namespace Yansongda\Supports\Traits;
use Predis\Client;
/**
* Trait ShouldThrottle.
*
* @property Client $redis
*/
trait ShouldThrottle
{
/**
* _throttle.
*
* @var array
*/
protected $_throttle = [
'limit' => 60,
'period' => 60,
'count' => 0,
'reset_time' => 0,
];
/**
* isThrottled.
*
* @author yansongda <me@yansongda.cn>
*
* @param string $key
* @param int $limit
* @param int $period
* @param bool $auto_add
*
* @return bool
*/
public function isThrottled($key, $limit = 60, $period = 60, $auto_add = false)
{
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 ($auto_add) {
$this->throttleAdd($key, $period);
}
return false;
}
return true;
}
/**
* 限流 + 1.
*
* @author yansongda <me@yansongda.cn>
*
* @param string $key
* @param int $period
*/
public function throttleAdd($key, $period = 60)
{
$now = microtime(true) * 1000;
$this->redis->zadd($key, [$now => $now]);
$this->redis->expire($key, $period * 2);
}
/**
* getResetTime.
*
* @author yansongda <me@yansongda.cn>
*
* @param $key
* @param $now
*
* @return int
*/
public function getThrottleResetTime($key, $now)
{
$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($data[0] / 1000) + $this->_throttle['period'];
}
/**
* 获取限流相关信息.
*
* @author yansongda <me@yansongda.cn>
*
* @param string|null $key
* @param mixed|null $default
*
* @return array|null
*/
public function getThrottleInfo($key = null, $default = null)
{
if (is_null($key)) {
return $this->_throttle;
}
if (isset($this->_throttle[$key])) {
return $this->_throttle[$key];
}
return $default;
}
/**
* 获取已使用次数.
*
* @author yansongda <me@yansongda.cn>
*
* @param string $key
* @param int $period
*
* @return string
*/
public function getThrottleCounts($key, $period = 60)
{
$now = microtime(true) * 1000;
return $this->redis->zcount($key, $now - $period * 1000, $now);
}
}