代码初始化

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Channel;
use Closure;
use Hyperf\Engine\Channel;
use Hyperf\Utils\Exception\ChannelClosedException;
use Hyperf\Utils\Exception\WaitTimeoutException;
class Caller
{
/**
* @var null|Channel
*/
protected $channel;
/**
* @var float wait seconds
*/
protected $waitTimeout;
/**
* @var null|Closure
*/
protected $closure;
public function __construct(Closure $closure, float $waitTimeout = 10)
{
$this->closure = $closure;
$this->waitTimeout = $waitTimeout;
$this->initInstance();
}
public function call(Closure $closure)
{
$release = true;
$channel = $this->channel;
try {
$instance = $channel->pop($this->waitTimeout);
if ($instance === false) {
if ($channel->isClosing()) {
throw new ChannelClosedException('The channel was closed.');
}
if ($channel->isTimeout()) {
throw new WaitTimeoutException('The instance pop from channel timeout.');
}
}
$result = $closure($instance);
} catch (ChannelClosedException|WaitTimeoutException $exception) {
$release = false;
throw $exception;
} finally {
$release && $channel->push($instance ?? null);
}
return $result;
}
public function initInstance(): void
{
if ($this->channel) {
$this->channel->close();
}
$this->channel = new Channel(1);
$this->channel->push($this->closure->__invoke());
}
}

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Channel;
use Hyperf\Engine\Channel;
class ChannelManager
{
/**
* @var Channel[]
*/
protected $channels = [];
/**
* @var int
*/
protected $size = 1;
public function __construct(int $size = 1)
{
$this->size = $size;
}
public function get(int $id, bool $initialize = false): ?Channel
{
if (isset($this->channels[$id])) {
return $this->channels[$id];
}
if ($initialize) {
return $this->channels[$id] = $this->make($this->size);
}
return null;
}
public function make(int $limit): Channel
{
return new Channel($limit);
}
public function close(int $id): void
{
if ($channel = $this->channels[$id] ?? null) {
$channel->close();
}
unset($this->channels[$id]);
}
public function getChannels(): array
{
return $this->channels;
}
public function flush(): void
{
$channels = $this->getChannels();
foreach ($channels as $id => $channel) {
$this->close($id);
}
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils;
use Swoole\Coroutine\Channel;
class ChannelPool extends \SplQueue
{
/**
* @var ChannelPool
*/
private static $instance;
public static function getInstance(): self
{
return static::$instance ?? (static::$instance = new self());
}
public function get(): Channel
{
return $this->isEmpty() ? new Channel(1) : $this->pop();
}
public function release(Channel $channel)
{
$channel->errCode = 0;
$this->push($channel);
}
}

View File

@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils;
class ClearStatCache
{
/**
* Interval at which to clear fileystem stat cache. Values below 1 indicate
* the stat cache should ALWAYS be cleared. Otherwise, the value is the number
* of seconds between clear operations.
*
* @var int
*/
private static $interval = 1;
/**
* When the filesystem stat cache was last cleared.
*
* @var int
*/
private static $lastCleared;
public static function clear(?string $filename = null): void
{
$now = time();
if (1 > self::$interval
|| self::$lastCleared
|| (self::$lastCleared + self::$interval < $now)
) {
self::forceClear($filename);
self::$lastCleared = $now;
}
}
public static function forceClear(?string $filename = null): void
{
if ($filename !== null) {
clearstatcache(true, $filename);
} else {
clearstatcache();
}
}
public static function getInterval(): int
{
return self::$interval;
}
public static function setInterval(int $interval)
{
self::$interval = $interval;
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\CodeGen;
use Jean85\PrettyVersions;
class Package
{
public static function getPrettyVersion(string $package): string
{
try {
return (string) PrettyVersions::getVersion($package);
} catch (\Throwable $exception) {
return 'unknown';
}
}
}

View File

@@ -0,0 +1,247 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\CodeGen;
use PhpDocReader\AnnotationException;
use PhpDocReader\PhpParser\UseStatementParser;
use ReflectionClass;
use ReflectionMethod;
use ReflectionParameter;
use ReflectionProperty;
use Reflector;
/**
* @see https://github.com/PHP-DI/PhpDocReader
*/
class PhpDocReader
{
private const PRIMITIVE_TYPES = [
'bool' => 'bool',
'boolean' => 'bool',
'string' => 'string',
'int' => 'int',
'integer' => 'int',
'float' => 'float',
'double' => 'float',
'array' => 'array',
'object' => 'object',
'callable' => 'callable',
'resource' => 'resource',
'mixed' => 'mixed',
'iterable' => 'iterable',
];
/**
* @var null|PhpDocReader
*/
protected static $instance;
/** @var UseStatementParser */
private $parser;
/** @var bool */
private $ignorePhpDocErrors;
/**
* @param bool $ignorePhpDocErrors enable or disable throwing errors when PhpDoc errors occur (when parsing annotations)
*/
public function __construct(bool $ignorePhpDocErrors = false)
{
$this->parser = new UseStatementParser();
$this->ignorePhpDocErrors = $ignorePhpDocErrors;
}
public static function getInstance(): PhpDocReader
{
if (static::$instance) {
return static::$instance;
}
return static::$instance = new static();
}
/**
* Parse the docblock of the property to get the type (class or primitive type) of the param annotation.
*
* @throws AnnotationException
*/
public function getReturnType(ReflectionMethod $method, bool $withoutNamespace = false): array
{
return $this->readReturnClass($method, true, $withoutNamespace);
}
/**
* Parse the docblock of the property to get the class of the param annotation.
*
* @throws AnnotationException
*/
public function getReturnClass(ReflectionMethod $method, bool $withoutNamespace = false): array
{
return $this->readReturnClass($method, false, $withoutNamespace);
}
protected function readReturnClass(ReflectionMethod $method, bool $allowPrimitiveTypes, bool $withoutNamespace = false): array
{
// Use reflection
$returnType = $method->getReturnType();
if ($returnType instanceof \ReflectionNamedType) {
if (! $returnType->isBuiltin() || $allowPrimitiveTypes) {
return [($returnType->allowsNull() ? '?' : '') . $returnType->getName()];
}
}
$docComment = $method->getDocComment();
if (! $docComment) {
return ['mixed'];
}
if (preg_match('/@return\s+([^\s]+)\s+/', $docComment, $matches)) {
[, $type] = $matches;
} else {
return ['mixed'];
}
$result = [];
$class = $method->getDeclaringClass();
$types = explode('|', $type);
foreach ($types as $type) {
// Ignore primitive types
if (isset(self::PRIMITIVE_TYPES[$type])) {
if ($allowPrimitiveTypes) {
$result[] = self::PRIMITIVE_TYPES[$type];
}
continue;
}
// Ignore types containing special characters ([], <> ...)
if (! preg_match('/^[a-zA-Z0-9\\\\_]+$/', $type)) {
continue;
}
// If the class name is not fully qualified (i.e. doesn't start with a \)
if ($type[0] !== '\\' && ! $withoutNamespace) {
// Try to resolve the FQN using the class context
$resolvedType = $this->tryResolveFqn($type, $class, $method);
if (! $resolvedType && ! $this->ignorePhpDocErrors) {
throw new AnnotationException(sprintf(
'The @return annotation for parameter "%s" of %s::%s contains a non existent class "%s". '
. 'Did you maybe forget to add a "use" statement for this annotation?',
$method,
$class->name,
$method->name,
$type
));
}
$type = $resolvedType;
}
if (! $this->ignorePhpDocErrors && ! $withoutNamespace && ! $this->classExists($type)) {
throw new AnnotationException(sprintf(
'The @return annotation for parameter "%s" of %s::%s contains a non existent class "%s"',
$method,
$class->name,
$method->name,
$type
));
}
// Remove the leading \ (FQN shouldn't contain it)
$result[] = is_string($type) ? ltrim($type, '\\') : null;
}
return $result;
}
/**
* Attempts to resolve the FQN of the provided $type based on the $class and $member context.
*
* @return null|string Fully qualified name of the type, or null if it could not be resolved
*/
protected function tryResolveFqn(string $type, ReflectionClass $class, Reflector $member): ?string
{
$alias = ($pos = strpos($type, '\\')) === false ? $type : substr($type, 0, $pos);
$loweredAlias = strtolower($alias);
// Retrieve "use" statements
$uses = $this->parser->parseUseStatements($class);
if (isset($uses[$loweredAlias])) {
// Imported classes
if ($pos !== false) {
return $uses[$loweredAlias] . substr($type, $pos);
}
return $uses[$loweredAlias];
}
if ($this->classExists($class->getNamespaceName() . '\\' . $type)) {
return $class->getNamespaceName() . '\\' . $type;
}
if (isset($uses['__NAMESPACE__']) && $this->classExists($uses['__NAMESPACE__'] . '\\' . $type)) {
// Class namespace
return $uses['__NAMESPACE__'] . '\\' . $type;
}
if ($this->classExists($type)) {
// No namespace
return $type;
}
// If all fail, try resolving through related traits
return $this->tryResolveFqnInTraits($type, $class, $member);
}
/**
* Attempts to resolve the FQN of the provided $type based on the $class and $member context, specifically searching
* through the traits that are used by the provided $class.
*
* @return null|string Fully qualified name of the type, or null if it could not be resolved
*/
protected function tryResolveFqnInTraits(string $type, ReflectionClass $class, Reflector $member): ?string
{
/** @var ReflectionClass[] $traits */
$traits = [];
// Get traits for the class and its parents
while ($class) {
$traits = array_merge($traits, $class->getTraits());
$class = $class->getParentClass();
}
foreach ($traits as $trait) {
// Eliminate traits that don't have the property/method/parameter
if ($member instanceof ReflectionProperty && ! $trait->hasProperty($member->name)) {
continue;
}
if ($member instanceof ReflectionMethod && ! $trait->hasMethod($member->name)) {
continue;
}
if ($member instanceof ReflectionParameter && ! $trait->hasMethod($member->getDeclaringFunction()->name)) {
continue;
}
// Run the resolver again with the ReflectionClass instance for the trait
$resolvedType = $this->tryResolveFqn($type, $trait, $member);
if ($resolvedType) {
return $resolvedType;
}
}
return null;
}
protected function classExists(string $class): bool
{
return class_exists($class) || interface_exists($class);
}
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\CodeGen;
use PhpDocReader\PhpDocReader;
class PhpDocReaderManager
{
/**
* @var null|PhpDocReader
*/
protected static $instance;
public static function getInstance(): PhpDocReader
{
if (static::$instance) {
return static::$instance;
}
return static::$instance = new PhpDocReader();
}
}

View File

@@ -0,0 +1,156 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\CodeGen;
use Hyperf\Utils\Exception\InvalidArgumentException;
use PhpParser\Node;
use PhpParser\Parser;
use PhpParser\ParserFactory;
use ReflectionClass;
use ReflectionParameter;
class PhpParser
{
public const TYPES = [
'int',
'float',
'string',
'bool',
'array',
'object',
'resource',
'mixed',
];
/**
* @var null|PhpParser
*/
protected static $instance;
/**
* @var Parser
*/
protected $parser;
public function __construct()
{
$parserFactory = new ParserFactory();
$this->parser = $parserFactory->create(ParserFactory::ONLY_PHP7);
}
public static function getInstance(): PhpParser
{
if (static::$instance) {
return static::$instance;
}
return static::$instance = new static();
}
/**
* @return null|Node\Stmt[]
*/
public function getNodesFromReflectionClass(ReflectionClass $reflectionClass): ?array
{
$code = file_get_contents($reflectionClass->getFileName());
return $this->parser->parse($code);
}
public function getNodeFromReflectionParameter(ReflectionParameter $parameter): Node\Param
{
$result = new Node\Param(
new Node\Expr\Variable($parameter->getName())
);
if ($parameter->isDefaultValueAvailable()) {
$result->default = $this->getExprFromValue($parameter->getDefaultValue());
}
if ($parameter->hasType()) {
/** @var \ReflectionNamedType|\ReflectionUnionType $reflection */
$reflection = $parameter->getType();
if ($reflection instanceof \ReflectionUnionType) {
$unionType = [];
foreach ($reflection->getTypes() as $objType) {
$type = $objType->getName();
if (! in_array($type, static::TYPES)) {
$unionType[] = new Node\Name('\\' . $type);
} else {
$unionType[] = new Node\Identifier($type);
}
}
$result->type = new Node\UnionType($unionType);
} else {
$type = $reflection->getName();
if (! in_array($type, static::TYPES)) {
$result->type = new Node\Name('\\' . $type);
} else {
$result->type = new Node\Identifier($type);
}
}
}
if ($parameter->isPassedByReference()) {
$result->byRef = true;
}
if ($parameter->isVariadic()) {
$result->variadic = true;
}
return $result;
}
public function getExprFromValue($value): Node\Expr
{
switch (gettype($value)) {
case 'array':
return new Node\Expr\Array_($value);
case 'string':
return new Node\Scalar\String_($value);
case 'integer':
return new Node\Scalar\LNumber($value);
case 'double':
return new Node\Scalar\DNumber($value);
case 'NULL':
return new Node\Expr\ConstFetch(new Node\Name('null'));
case 'boolean':
return new Node\Expr\ConstFetch(new Node\Name($value ? 'true' : 'false'));
default:
throw new InvalidArgumentException($value . ' is invalid');
}
}
/**
* @return Node\Stmt\ClassMethod[]
*/
public function getAllMethodsFromStmts(array $stmts): array
{
$methods = [];
foreach ($stmts as $namespace) {
if (! $namespace instanceof Node\Stmt\Namespace_) {
continue;
}
foreach ($namespace->stmts as $class) {
if (! $class instanceof Node\Stmt\Class_ && ! $class instanceof Node\Stmt\Interface_) {
continue;
}
foreach ($class->getMethods() as $method) {
$methods[] = $method;
}
}
}
return $methods;
}
}

View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\CodeGen;
use Hyperf\Utils\Composer;
use Hyperf\Utils\Str;
/**
* Read composer.json autoload psr-4 rules to figure out the namespace or path.
*/
class Project
{
public function namespace(string $path): string
{
$ext = pathinfo($path, PATHINFO_EXTENSION);
if ($ext !== '') {
$path = substr($path, 0, -(strlen($ext) + 1));
} else {
$path = trim($path, '/') . '/';
}
foreach ($this->getAutoloadRules() as $prefix => $prefixPath) {
if ($this->isRootNamespace($prefix) || strpos($path, $prefixPath) === 0) {
return $prefix . str_replace('/', '\\', substr($path, strlen($prefixPath)));
}
}
throw new \RuntimeException("Invalid project path: {$path}");
}
public function className(string $path): string
{
return $this->namespace($path);
}
public function path(string $name, $extension = '.php'): string
{
if (Str::endsWith($name, '\\')) {
$extension = '';
}
foreach ($this->getAutoloadRules() as $prefix => $prefixPath) {
if ($this->isRootNamespace($prefix) || strpos($name, $prefix) === 0) {
return $prefixPath . str_replace('\\', '/', substr($name, strlen($prefix))) . $extension;
}
}
throw new \RuntimeException("Invalid class name: {$name}");
}
protected function isRootNamespace(string $namespace): bool
{
return $namespace === '';
}
protected function getAutoloadRules(): array
{
return data_get(Composer::getJsonContent(), 'autoload.psr-4', []);
}
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Codec;
class Base62
{
public const CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
public const BASE = 62;
public static function encode(int $number): string
{
$chars = [];
while ($number > 0) {
$remain = $number % self::BASE;
$chars[] = self::CHARS[$remain];
$number = ($number - $remain) / self::BASE;
}
return implode('', array_reverse($chars));
}
public static function decode(string $data): int
{
return array_reduce(array_map(function ($character) {
return strpos(self::CHARS, $character);
}, str_split($data)), function ($result, $remain) {
return $result * self::BASE + $remain;
});
}
}

View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Codec;
use Hyperf\Utils\Contracts\Arrayable;
use Hyperf\Utils\Contracts\Jsonable;
use Hyperf\Utils\Exception\InvalidArgumentException;
class Json
{
/**
* @param mixed $data
* @throws InvalidArgumentException
*/
public static function encode($data, int $flags = JSON_UNESCAPED_UNICODE, int $depth = 512): string
{
if ($data instanceof Jsonable) {
return (string) $data;
}
if ($data instanceof Arrayable) {
$data = $data->toArray();
}
try {
$json = json_encode($data, $flags | JSON_THROW_ON_ERROR, $depth);
} catch (\Throwable $exception) {
throw new InvalidArgumentException($exception->getMessage(), $exception->getCode());
}
return $json;
}
/**
* @throws InvalidArgumentException
*/
public static function decode(string $json, bool $assoc = true, int $depth = 512, int $flags = 0)
{
try {
$decode = json_decode($json, $assoc, $depth, $flags | JSON_THROW_ON_ERROR);
} catch (\Throwable $exception) {
throw new InvalidArgumentException($exception->getMessage(), $exception->getCode());
}
return $decode;
}
}

View File

@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Codec;
use Hyperf\Utils\Contracts\Arrayable;
use Hyperf\Utils\Contracts\Xmlable;
use Hyperf\Utils\Exception\InvalidArgumentException;
use SimpleXMLElement;
class Xml
{
public static function toXml($data, $parentNode = null, $root = 'root')
{
if ($data instanceof Xmlable) {
return (string) $data;
}
if ($data instanceof Arrayable) {
$data = $data->toArray();
} else {
$data = (array) $data;
}
if ($parentNode === null) {
$xml = new SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?>' . "<{$root}></{$root}>");
} else {
$xml = $parentNode;
}
foreach ($data as $key => $value) {
if (is_array($value)) {
self::toXml($value, $xml->addChild($key));
} else {
if (is_numeric($key)) {
$xml->addChild('item' . $key, (string) $value);
} else {
$xml->addChild($key, (string) $value);
}
}
}
return trim($xml->asXML());
}
public static function toArray($xml)
{
// For PHP 8.0, libxml_disable_entity_loader() has been deprecated.
// As libxml 2.9.0 is now required, external entity loading is guaranteed to be disabled by default.
// And this function is no longer needed to protect against XXE attacks, unless the (still vulnerable). LIBXML_NOENT is used.
// In that case, it is recommended to refactor the code using libxml_set_external_entity_loader() to suppress loading of external entities.
if (\PHP_VERSION_ID < 80000) {
$disableLibxmlEntityLoader = libxml_disable_entity_loader(true);
$respObject = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA | LIBXML_NOERROR);
libxml_disable_entity_loader($disableLibxmlEntityLoader);
} else {
$respObject = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA | LIBXML_NOERROR);
}
if ($respObject === false) {
throw new InvalidArgumentException('Syntax error.');
}
return json_decode(json_encode($respObject), true);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,159 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils;
use Composer\Autoload\ClassLoader;
class Composer
{
/**
* @var null|Collection
*/
private static $content;
/**
* @var null|Collection
*/
private static $json;
/**
* @var array
*/
private static $extra = [];
/**
* @var array
*/
private static $scripts = [];
/**
* @var array
*/
private static $versions = [];
/**
* @var null|ClassLoader
*/
private static $classLoader;
/**
* @throws \RuntimeException When composer.lock does not exist.
*/
public static function getLockContent(): Collection
{
if (! self::$content) {
$path = self::discoverLockFile();
if (! $path) {
throw new \RuntimeException('composer.lock not found.');
}
self::$content = collect(json_decode(file_get_contents($path), true));
$packages = self::$content->offsetGet('packages') ?? [];
$packagesDev = self::$content->offsetGet('packages-dev') ?? [];
foreach (array_merge($packages, $packagesDev) as $package) {
$packageName = '';
foreach ($package ?? [] as $key => $value) {
if ($key === 'name') {
$packageName = $value;
continue;
}
switch ($key) {
case 'extra':
$packageName && self::$extra[$packageName] = $value;
break;
case 'scripts':
$packageName && self::$scripts[$packageName] = $value;
break;
case 'version':
$packageName && self::$versions[$packageName] = $value;
break;
}
}
}
}
return self::$content;
}
public static function getJsonContent(): Collection
{
if (! self::$json) {
$path = BASE_PATH . '/composer.json';
if (! is_readable($path)) {
throw new \RuntimeException('composer.json is not readable.');
}
self::$json = collect(json_decode(file_get_contents($path), true));
}
return self::$json;
}
public static function discoverLockFile(): string
{
$path = '';
if (is_readable(BASE_PATH . '/composer.lock')) {
$path = BASE_PATH . '/composer.lock';
}
return $path;
}
public static function getMergedExtra(string $key = null)
{
if (! self::$extra) {
self::getLockContent();
}
if ($key === null) {
return self::$extra;
}
$extra = [];
foreach (self::$extra ?? [] as $project => $config) {
foreach ($config ?? [] as $configKey => $item) {
if ($key === $configKey && $item) {
foreach ($item ?? [] as $k => $v) {
if (is_array($v)) {
$extra[$k] = array_merge($extra[$k] ?? [], $v);
} else {
$extra[$k][] = $v;
}
}
}
}
}
return $extra;
}
public static function getLoader(): ClassLoader
{
if (! self::$classLoader) {
self::$classLoader = self::findLoader();
}
return self::$classLoader;
}
public static function setLoader(ClassLoader $classLoader): ClassLoader
{
self::$classLoader = $classLoader;
return $classLoader;
}
private static function findLoader(): ClassLoader
{
$composerClass = '';
foreach (get_declared_classes() as $declaredClass) {
if (strpos($declaredClass, 'ComposerAutoloaderInit') === 0 && method_exists($declaredClass, 'getLoader')) {
$composerClass = $declaredClass;
break;
}
}
if (! $composerClass) {
throw new \RuntimeException('Composer loader not found.');
}
return $composerClass::getLoader();
}
}

View File

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

View File

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

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Contracts;
/**
* @template TKey of array-key
* @template TValue
*/
interface Arrayable
{
/**
* @return array<TKey, TValue>
*/
public function toArray(): array;
}

View File

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

View File

@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Contracts;
interface MessageBag
{
/**
* Get the keys present in the message bag.
*/
public function keys(): array;
/**
* Add a message to the bag.
*/
public function add(string $key, string $message): MessageBag;
/**
* Merge a new array of messages into the bag.
*
* @param array|MessageProvider $messages
* @return $this
*/
public function merge($messages);
/**
* Determine if messages exist for a given key.
*
* @param array|string $key
*/
public function has($key): bool;
/**
* Get the first message from the bag for a given key.
*/
public function first(?string $key = null, ?string $format = null): string;
/**
* Get all of the messages from the bag for a given key.
*/
public function get(string $key, ?string $format = null): array;
/**
* Get all of the messages for every key in the bag.
*/
public function all(?string $format = null): array;
/**
* Get the raw messages in the container.
*/
public function getMessages(): array;
/**
* Get the default message format.
*/
public function getFormat(): string;
/**
* Set the default message format.
*
* @return $this
*/
public function setFormat(string $format = ':message');
/**
* Determine if the message bag has any messages.
*/
public function isEmpty(): bool;
/**
* Determine if the message bag has any messages.
*/
public function isNotEmpty(): bool;
/**
* Get the number of messages in the container.
*/
public function count(): int;
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Contracts;
interface MessageProvider
{
/**
* Get the messages for the instance.
*/
public function getMessageBag(): MessageBag;
}

View File

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

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Coordinator;
class Constants
{
/**
* Swoole onWorkerStart event.
*/
public const WORKER_START = 'workerStart';
/**
* Swoole onWorkerExit event.
*/
public const WORKER_EXIT = 'workerExit';
}

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Coordinator;
use Hyperf\Engine\Channel;
class Coordinator
{
/**
* @var Channel
*/
private $channel;
public function __construct()
{
$this->channel = new Channel(1);
}
/**
* Yield the current coroutine for a given timeout,
* unless the coordinator is woke up from outside.
*
* @param float|int $timeout
* @return bool returns true if the coordinator has been woken up
*/
public function yield($timeout = -1): bool
{
$this->channel->pop((float) $timeout);
return $this->channel->isClosing();
}
/**
* Wakeup all coroutines yielding for this coordinator.
*/
public function resume(): void
{
$this->channel->close();
}
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Coordinator;
class CoordinatorManager
{
/**
* A container that is used for storing coordinator.
*
* @var array
*/
private static $container = [];
/**
* You should initialize a Coordinator with the identifier before use it.
*/
public static function initialize(string $identifier): void
{
static::$container[$identifier] = new Coordinator();
}
/**
* Get a Coordinator from container by the identifier.
*
* @throws \RuntimeException when the Coordinator with the identifier has not initialization
*/
public static function until(string $identifier): Coordinator
{
if (! isset(static::$container[$identifier])) {
static::$container[$identifier] = new Coordinator();
}
return static::$container[$identifier];
}
/**
* Remove the Coordinator by the identifier from container after used,
* otherwise memory leaks will occur.
*/
public static function clear(string $identifier): void
{
unset(static::$container[$identifier]);
}
}

View File

@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Engine\Coroutine as Co;
use Hyperf\Engine\Exception\CoroutineDestroyedException;
use Hyperf\Engine\Exception\RunningInNonCoroutineException;
use Hyperf\ExceptionHandler\Formatter\FormatterInterface;
use Psr\Log\LoggerInterface;
use Throwable;
class Coroutine
{
/**
* Returns the current coroutine ID.
* Returns -1 when running in non-coroutine context.
*/
public static function id(): int
{
return Co::id();
}
public static function defer(callable $callable)
{
Co::defer($callable);
}
public static function sleep(float $seconds)
{
usleep(intval($seconds * 1000 * 1000));
}
/**
* Returns the parent coroutine ID.
* Returns 0 when running in the top level coroutine.
* @throws RunningInNonCoroutineException when running in non-coroutine context
* @throws CoroutineDestroyedException when the coroutine has been destroyed
*/
public static function parentId(?int $coroutineId = null): int
{
return Co::pid($coroutineId);
}
/**
* @return int Returns the coroutine ID of the coroutine just created.
* Returns -1 when coroutine create failed.
*/
public static function create(callable $callable): int
{
$coroutine = Co::create(function () use ($callable) {
try {
call($callable);
} catch (Throwable $throwable) {
if (ApplicationContext::hasContainer()) {
$container = ApplicationContext::getContainer();
if ($container->has(StdoutLoggerInterface::class)) {
/* @var LoggerInterface $logger */
$logger = $container->get(StdoutLoggerInterface::class);
/* @var FormatterInterface $formatter */
if ($container->has(FormatterInterface::class)) {
$formatter = $container->get(FormatterInterface::class);
$logger->warning($formatter->format($throwable));
} else {
$logger->warning(sprintf('Uncaptured exception[%s] detected in %s::%d.', get_class($throwable), $throwable->getFile(), $throwable->getLine()));
}
}
}
}
});
try {
return $coroutine->getId();
} catch (\Throwable $exception) {
return -1;
}
}
public static function inCoroutine(): bool
{
return Co::id() > 0;
}
}

View File

@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Coroutine;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Engine\Channel;
use Hyperf\ExceptionHandler\Formatter\FormatterInterface;
use Hyperf\Utils\ApplicationContext;
use Hyperf\Utils\Coroutine;
/**
* @method bool isFull()
* @method bool isEmpty()
*/
class Concurrent
{
/**
* @var Channel
*/
protected $channel;
/**
* @var int
*/
protected $limit;
public function __construct(int $limit)
{
$this->limit = $limit;
$this->channel = new Channel($limit);
}
public function __call($name, $arguments)
{
if (in_array($name, ['isFull', 'isEmpty'])) {
return $this->channel->{$name}(...$arguments);
}
}
public function getLimit(): int
{
return $this->limit;
}
public function length(): int
{
return $this->channel->getLength();
}
public function getLength(): int
{
return $this->channel->getLength();
}
public function getRunningCoroutineCount(): int
{
return $this->getLength();
}
public function getChannel(): Channel
{
return $this->channel;
}
public function create(callable $callable): void
{
$this->channel->push(true);
Coroutine::create(function () use ($callable) {
try {
$callable();
} catch (\Throwable $exception) {
if (ApplicationContext::hasContainer()) {
$container = ApplicationContext::getContainer();
if ($container->has(StdoutLoggerInterface::class) && $container->has(FormatterInterface::class)) {
$logger = $container->get(StdoutLoggerInterface::class);
$formatter = $container->get(FormatterInterface::class);
$logger->error($formatter->format($exception));
}
}
} finally {
$this->channel->pop();
}
});
}
}

View File

@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Coroutine;
use Hyperf\Utils\Coroutine;
use Hyperf\Utils\Traits\Container;
use Swoole\Coroutine as SwooleCoroutine;
class Locker
{
use Container;
/**
* @var array
*/
protected static $container = [];
public static function add($key, $id): void
{
self::$container[$key][] = $id;
}
public static function clear($key): void
{
unset(self::$container[$key]);
}
public static function lock($key): bool
{
if (! self::has($key)) {
self::add($key, 0);
return true;
}
self::add($key, Coroutine::id());
SwooleCoroutine::suspend();
return false;
}
public static function unlock($key): void
{
if (self::has($key)) {
$ids = self::get($key);
foreach ($ids as $id) {
if ($id > 0) {
SwooleCoroutine::resume($id);
}
}
self::clear($key);
}
}
}

View File

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

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Exception;
use Throwable;
final class ExceptionThrower
{
/**
* @var Throwable
*/
private $throwable;
public function __construct(Throwable $throwable)
{
$this->throwable = $throwable;
}
public function getThrowable(): Throwable
{
return $this->throwable;
}
}

View File

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

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Exception;
class ParallelExecutionException extends \RuntimeException
{
/**
* @var array
*/
private $results;
/**
* @var array
*/
private $throwables;
public function getResults()
{
return $this->results;
}
public function setResults(array $results)
{
$this->results = $results;
}
public function getThrowables()
{
return $this->throwables;
}
public function setThrowables(array $throwables)
{
return $this->throwables = $throwables;
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,554 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Filesystem;
use ErrorException;
use FilesystemIterator;
use Hyperf\Macroable\Macroable;
use Hyperf\Utils\Coroutine;
use Symfony\Component\Finder\Finder;
/**
* Most of the methods in this file come from illuminate/filesystem,
* thanks Laravel Team provide such a useful class.
*/
class Filesystem
{
use Macroable;
/**
* Determine if a file or directory exists.
*/
public function exists(string $path): bool
{
return file_exists($path);
}
/**
* Get the contents of a file.
*
* @throws \Hyperf\Utils\Filesystem\FileNotFoundException
*/
public function get(string $path, bool $lock = false): string
{
if ($this->isFile($path)) {
return $lock ? $this->sharedGet($path) : file_get_contents($path);
}
throw new FileNotFoundException("File does not exist at path {$path}");
}
/**
* Get contents of a file with shared access.
*/
public function sharedGet(string $path): string
{
return $this->atomic($path, function ($path) {
$contents = '';
$handle = fopen($path, 'rb');
if ($handle) {
$wouldBlock = false;
flock($handle, LOCK_SH | LOCK_NB, $wouldBlock);
while ($wouldBlock) {
usleep(1000);
flock($handle, LOCK_SH | LOCK_NB, $wouldBlock);
}
try {
clearstatcache(true, $path);
$contents = fread($handle, $this->size($path) ?: 1);
} finally {
flock($handle, LOCK_UN);
fclose($handle);
}
}
return $contents;
});
}
/**
* Get the returned value of a file.
*
* @throws \Hyperf\Utils\Filesystem\FileNotFoundException
*/
public function getRequire(string $path)
{
if ($this->isFile($path)) {
return require $path;
}
throw new FileNotFoundException("File does not exist at path {$path}");
}
/**
* Require the given file once.
*
* @return mixed
*/
public function requireOnce(string $file)
{
require_once $file;
}
/**
* Get the MD5 hash of the file at the given path.
*/
public function hash(string $path): string
{
return md5_file($path);
}
/**
* Clears file status cache.
*/
public function clearStatCache(string $path): void
{
clearstatcache(true, $path);
}
/**
* Write the contents of a file.
*
* @param resource|string $contents
* @return bool|int
*/
public function put(string $path, $contents, bool $lock = false)
{
if ($lock) {
return $this->atomic($path, function ($path) use ($contents) {
$handle = fopen($path, 'w+');
if ($handle) {
$wouldBlock = false;
flock($handle, LOCK_EX | LOCK_NB, $wouldBlock);
while ($wouldBlock) {
usleep(1000);
flock($handle, LOCK_EX | LOCK_NB, $wouldBlock);
}
try {
fwrite($handle, $contents);
} finally {
flock($handle, LOCK_UN);
fclose($handle);
}
}
});
}
return file_put_contents($path, $contents);
}
/**
* Write the contents of a file, replacing it atomically if it already exists.
*/
public function replace(string $path, string $content)
{
// If the path already exists and is a symlink, get the real path...
clearstatcache(true, $path);
$path = realpath($path) ?: $path;
$tempPath = tempnam(dirname($path), basename($path));
// Fix permissions of tempPath because `tempnam()` creates it with permissions set to 0600...
chmod($tempPath, 0777 - umask());
file_put_contents($tempPath, $content);
rename($tempPath, $path);
}
/**
* Prepend to a file.
*/
public function prepend(string $path, string $data): int
{
if ($this->exists($path)) {
return $this->put($path, $data . $this->get($path));
}
return $this->put($path, $data);
}
/**
* Append to a file.
*/
public function append(string $path, string $data): int
{
return file_put_contents($path, $data, FILE_APPEND);
}
/**
* Get or set UNIX mode of a file or directory.
*/
public function chmod(string $path, ?int $mode = null)
{
if ($mode) {
return chmod($path, $mode);
}
return substr(sprintf('%o', fileperms($path)), -4);
}
/**
* Delete the file at a given path.
*
* @param array|string $paths
*/
public function delete($paths): bool
{
$paths = is_array($paths) ? $paths : func_get_args();
$success = true;
foreach ($paths as $path) {
try {
if (! @unlink($path)) {
$success = false;
}
} catch (ErrorException $e) {
$success = false;
}
}
return $success;
}
/**
* Move a file to a new location.
*/
public function move(string $path, string $target): bool
{
return rename($path, $target);
}
/**
* Copy a file to a new location.
*/
public function copy(string $path, string $target): bool
{
return copy($path, $target);
}
/**
* Create a hard link to the target file or directory.
*/
public function link(string $target, string $link)
{
if (! $this->windowsOs()) {
return symlink($target, $link);
}
$mode = $this->isDirectory($target) ? 'J' : 'H';
exec("mklink /{$mode} \"{$link}\" \"{$target}\"");
}
/**
* Extract the file name from a file path.
*/
public function name(string $path): string
{
return pathinfo($path, PATHINFO_FILENAME);
}
/**
* Extract the trailing name component from a file path.
*/
public function basename(string $path): string
{
return pathinfo($path, PATHINFO_BASENAME);
}
/**
* Extract the parent directory from a file path.
*/
public function dirname(string $path): string
{
return pathinfo($path, PATHINFO_DIRNAME);
}
/**
* Extract the file extension from a file path.
*/
public function extension(string $path): string
{
return pathinfo($path, PATHINFO_EXTENSION);
}
/**
* Get the file type of a given file.
*/
public function type(string $path): string
{
return filetype($path);
}
/**
* Get the mime-type of a given file.
*
* @return false|string
*/
public function mimeType(string $path)
{
return finfo_file(finfo_open(FILEINFO_MIME_TYPE), $path);
}
/**
* Get the file size of a given file.
*/
public function size(string $path): int
{
return filesize($path);
}
/**
* Get the file's last modification time.
*/
public function lastModified(string $path): int
{
return filemtime($path);
}
/**
* Determine if the given path is a directory.
*/
public function isDirectory(string $directory): bool
{
return is_dir($directory);
}
/**
* Determine if the given path is readable.
*/
public function isReadable(string $path): bool
{
return is_readable($path);
}
/**
* Determine if the given path is writable.
*/
public function isWritable(string $path): bool
{
return is_writable($path);
}
/**
* Determine if the given path is a file.
*/
public function isFile(string $file): bool
{
return is_file($file);
}
/**
* Find path names matching a given pattern.
*/
public function glob(string $pattern, int $flags = 0): array
{
return glob($pattern, $flags);
}
/**
* Get an array of all files in a directory.
*
* @return \Symfony\Component\Finder\SplFileInfo[]
*/
public function files(string $directory, bool $hidden = false): array
{
return iterator_to_array(
Finder::create()->files()->ignoreDotFiles(! $hidden)->in($directory)->depth(0)->sortByName(),
false
);
}
/**
* Get all of the files from the given directory (recursive).
* @return \Symfony\Component\Finder\SplFileInfo[]
*/
public function allFiles(string $directory, bool $hidden = false): array
{
return iterator_to_array(
Finder::create()->files()->ignoreDotFiles(! $hidden)->in($directory)->sortByName(),
false
);
}
/**
* Get all of the directories within a given directory.
*/
public function directories(string $directory): array
{
$directories = [];
foreach (Finder::create()->in($directory)->directories()->depth(0)->sortByName() as $dir) {
$directories[] = $dir->getPathname();
}
return $directories;
}
/**
* Create a directory.
*/
public function makeDirectory(string $path, int $mode = 0755, bool $recursive = false, bool $force = false): bool
{
if ($force) {
return @mkdir($path, $mode, $recursive);
}
return mkdir($path, $mode, $recursive);
}
/**
* Move a directory.
*/
public function moveDirectory(string $from, string $to, bool $overwrite = false): bool
{
if ($overwrite && $this->isDirectory($to) && ! $this->deleteDirectory($to)) {
return false;
}
return @rename($from, $to) === true;
}
/**
* Copy a directory from one location to another.
*/
public function copyDirectory(string $directory, string $destination, int $options = null): bool
{
if (! $this->isDirectory($directory)) {
return false;
}
$options = $options ?: FilesystemIterator::SKIP_DOTS;
// If the destination directory does not actually exist, we will go ahead and
// create it recursively, which just gets the destination prepared to copy
// the files over. Once we make the directory we'll proceed the copying.
if (! $this->isDirectory($destination)) {
$this->makeDirectory($destination, 0777, true);
}
$items = new FilesystemIterator($directory, $options);
foreach ($items as $item) {
// As we spin through items, we will check to see if the current file is actually
// a directory or a file. When it is actually a directory we will need to call
// back into this function recursively to keep copying these nested folders.
$target = $destination . DIRECTORY_SEPARATOR . $item->getBasename();
if ($item->isDir()) {
$path = $item->getPathname();
if (! $this->copyDirectory($path, $target, $options)) {
return false;
}
}
// If the current items is just a regular file, we will just copy this to the new
// location and keep looping. If for some reason the copy fails we'll bail out
// and return false, so the developer is aware that the copy process failed.
else {
if (! $this->copy($item->getPathname(), $target)) {
return false;
}
}
}
return true;
}
/**
* Recursively delete a directory.
*
* The directory itself may be optionally preserved.
*/
public function deleteDirectory(string $directory, bool $preserve = false): bool
{
if (! $this->isDirectory($directory)) {
return false;
}
$items = new FilesystemIterator($directory);
foreach ($items as $item) {
// If the item is a directory, we can just recurse into the function and
// delete that sub-directory otherwise we'll just delete the file and
// keep iterating through each file until the directory is cleaned.
if ($item->isDir() && ! $item->isLink()) {
$this->deleteDirectory($item->getPathname());
}
// If the item is just a file, we can go ahead and delete it since we're
// just looping through and waxing all of the files in this directory
// and calling directories recursively, so we delete the real path.
else {
$this->delete($item->getPathname());
}
}
if (! $preserve) {
@rmdir($directory);
}
return true;
}
/**
* Remove all of the directories within a given directory.
*/
public function deleteDirectories(string $directory): bool
{
$allDirectories = $this->directories($directory);
if (! empty($allDirectories)) {
foreach ($allDirectories as $directoryName) {
$this->deleteDirectory($directoryName);
}
return true;
}
return false;
}
/**
* Empty the specified directory of all files and folders.
*/
public function cleanDirectory(string $directory): bool
{
return $this->deleteDirectory($directory, true);
}
/**
* Detect whether it's Windows.
*/
public function windowsOs(): bool
{
return stripos(PHP_OS, 'win') === 0;
}
protected function atomic($path, $callback)
{
if (Coroutine::inCoroutine()) {
try {
while (! Coroutine\Locker::lock($path)) {
usleep(1000);
}
return $callback($path);
} finally {
Coroutine\Locker::unlock($path);
}
} else {
return $callback($path);
}
}
}

View File

@@ -0,0 +1,200 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils;
use ArrayAccess;
use Hyperf\Utils\Contracts\Arrayable;
use Hyperf\Utils\Contracts\Jsonable;
use JsonSerializable;
/**
* Most of the methods in this file come from illuminate/support,
* thanks Laravel Team provide such a useful class.
*/
class Fluent implements ArrayAccess, Arrayable, Jsonable, JsonSerializable
{
/**
* All of the attributes set on the fluent instance.
*
* @var array
*/
protected $attributes = [];
/**
* Create a new fluent instance.
*
* @param array|object $attributes
*/
public function __construct($attributes = [])
{
foreach ($attributes as $key => $value) {
$this->attributes[$key] = $value;
}
}
/**
* Handle dynamic calls to the fluent instance to set attributes.
*
* @param string $method
* @param array $parameters
* @return $this
*/
public function __call($method, $parameters)
{
$this->attributes[$method] = count($parameters) > 0 ? $parameters[0] : true;
return $this;
}
/**
* Dynamically retrieve the value of an attribute.
*
* @param string $key
*/
public function __get($key)
{
return $this->get($key);
}
/**
* Dynamically set the value of an attribute.
*
* @param string $key
* @param mixed $value
*/
public function __set($key, $value)
{
$this->offsetSet($key, $value);
}
/**
* Dynamically check if an attribute is set.
*
* @param string $key
* @return bool
*/
public function __isset($key)
{
return $this->offsetExists($key);
}
/**
* Dynamically unset an attribute.
*
* @param string $key
*/
public function __unset($key)
{
$this->offsetUnset($key);
}
public function __toString(): string
{
return $this->toJson();
}
/**
* Get an attribute from the fluent instance.
*
* @param string $key
* @param null|mixed $default
*/
public function get($key, $default = null)
{
if (array_key_exists($key, $this->attributes)) {
return $this->attributes[$key];
}
return value($default);
}
/**
* Get the attributes from the fluent instance.
*
* @return array
*/
public function getAttributes()
{
return $this->attributes;
}
/**
* Convert the fluent instance to an array.
*/
public function toArray(): array
{
return $this->attributes;
}
/**
* Convert the object into something JSON serializable.
*
* @return array
*/
public function jsonSerialize()
{
return $this->toArray();
}
/**
* Convert the fluent instance to JSON.
*
* @param int $options
* @return string
*/
public function toJson($options = 0)
{
return json_encode($this->jsonSerialize(), $options);
}
/**
* Determine if the given offset exists.
*
* @param string $offset
* @return bool
*/
public function offsetExists($offset)
{
return isset($this->attributes[$offset]);
}
/**
* Get the value for a given offset.
*
* @param string $offset
*/
public function offsetGet($offset)
{
return $this->get($offset);
}
/**
* Set the value at the given offset.
*
* @param string $offset
* @param mixed $value
*/
public function offsetSet($offset, $value)
{
$this->attributes[$offset] = $value;
}
/**
* Unset the value at the given offset.
*
* @param string $offset
*/
public function offsetUnset($offset)
{
unset($this->attributes[$offset]);
}
}

View File

@@ -0,0 +1,486 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
use Hyperf\Utils\ApplicationContext;
use Hyperf\Utils\Arr;
use Hyperf\Utils\Backoff;
use Hyperf\Utils\Collection;
use Hyperf\Utils\Coroutine;
use Hyperf\Utils\HigherOrderTapProxy;
use Hyperf\Utils\Optional;
use Hyperf\Utils\Parallel;
use Hyperf\Utils\Str;
use Hyperf\Utils\Waiter;
if (! function_exists('value')) {
/**
* Return the default value of the given value.
*
* @param mixed $value
*/
function value($value, ...$args)
{
return $value instanceof Closure ? $value(...$args) : $value;
}
}
if (! function_exists('env')) {
/**
* Gets the value of an environment variable.
*
* @param string $key
* @param null|mixed $default
*/
function env($key, $default = null)
{
$value = getenv($key);
if ($value === false) {
return value($default);
}
switch (strtolower($value)) {
case 'true':
case '(true)':
return true;
case 'false':
case '(false)':
return false;
case 'empty':
case '(empty)':
return '';
case 'null':
case '(null)':
return;
}
if (($valueLength = strlen($value)) > 1 && $value[0] === '"' && $value[$valueLength - 1] === '"') {
return substr($value, 1, -1);
}
return $value;
}
}
if (! function_exists('retry')) {
/**
* Retry an operation a given number of times.
*
* @param float|int $times
* @param int $sleep millisecond
* @throws \Throwable
*/
function retry($times, callable $callback, int $sleep = 0)
{
$attempts = 0;
$backoff = new Backoff($sleep);
beginning:
try {
return $callback(++$attempts);
} catch (\Throwable $e) {
if (--$times < 0) {
throw $e;
}
$backoff->sleep();
goto beginning;
}
}
}
if (! function_exists('with')) {
/**
* Return the given value, optionally passed through the given callback.
*
* @param mixed $value
*/
function with($value, callable $callback = null)
{
return is_null($callback) ? $value : $callback($value);
}
}
if (! function_exists('collect')) {
/**
* Create a collection from the given value.
*
* @param null|mixed $value
* @return Collection
*/
function collect($value = null)
{
return new Collection($value);
}
}
if (! function_exists('data_fill')) {
/**
* Fill in data where it's missing.
*
* @param mixed $target
* @param array|string $key
* @param mixed $value
*/
function data_fill(&$target, $key, $value)
{
return data_set($target, $key, $value, false);
}
}
if (! function_exists('data_get')) {
/**
* Get an item from an array or object using "dot" notation.
*
* @param null|array|int|string $key
* @param null|mixed $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;
}
}
if (! function_exists('data_set')) {
/**
* Set an item on an array or object using dot notation.
*
* @param mixed $target
* @param array|string $key
* @param bool $overwrite
* @param mixed $value
*/
function data_set(&$target, $key, $value, $overwrite = true)
{
$segments = is_array($key) ? $key : explode('.', $key);
if (($segment = array_shift($segments)) === '*') {
if (! Arr::accessible($target)) {
$target = [];
}
if ($segments) {
foreach ($target as &$inner) {
data_set($inner, $segments, $value, $overwrite);
}
} elseif ($overwrite) {
foreach ($target as &$inner) {
$inner = $value;
}
}
} elseif (Arr::accessible($target)) {
if ($segments) {
if (! Arr::exists($target, $segment)) {
$target[$segment] = [];
}
data_set($target[$segment], $segments, $value, $overwrite);
} elseif ($overwrite || ! Arr::exists($target, $segment)) {
$target[$segment] = $value;
}
} elseif (is_object($target)) {
if ($segments) {
if (! isset($target->{$segment})) {
$target->{$segment} = [];
}
data_set($target->{$segment}, $segments, $value, $overwrite);
} elseif ($overwrite || ! isset($target->{$segment})) {
$target->{$segment} = $value;
}
} else {
$target = [];
if ($segments) {
$target[$segment] = [];
data_set($target[$segment], $segments, $value, $overwrite);
} elseif ($overwrite) {
$target[$segment] = $value;
}
}
return $target;
}
}
if (! function_exists('head')) {
/**
* Get the first element of an array. Useful for method chaining.
*
* @param array $array
*/
function head($array)
{
return reset($array);
}
}
if (! function_exists('last')) {
/**
* Get the last element from an array.
*
* @param array $array
*/
function last($array)
{
return end($array);
}
}
if (! function_exists('tap')) {
/**
* Call the given Closure with the given value then return the value.
*
* @param null|callable $callback
* @param mixed $value
*/
function tap($value, $callback = null)
{
if (is_null($callback)) {
return new HigherOrderTapProxy($value);
}
$callback($value);
return $value;
}
}
if (! function_exists('call')) {
/**
* Call a callback with the arguments.
*
* @param mixed $callback
* @return null|mixed
*/
function call($callback, array $args = [])
{
$result = null;
if ($callback instanceof \Closure) {
$result = $callback(...$args);
} elseif (is_object($callback) || (is_string($callback) && function_exists($callback))) {
$result = $callback(...$args);
} elseif (is_array($callback)) {
[$object, $method] = $callback;
$result = is_object($object) ? $object->{$method}(...$args) : $object::$method(...$args);
} else {
$result = call_user_func_array($callback, $args);
}
return $result;
}
}
if (! function_exists('go')) {
/**
* @return bool|int
*/
function go(callable $callable)
{
$id = Coroutine::create($callable);
return $id > 0 ? $id : false;
}
}
if (! function_exists('co')) {
/**
* @return bool|int
*/
function co(callable $callable)
{
$id = Coroutine::create($callable);
return $id > 0 ? $id : false;
}
}
if (! function_exists('defer')) {
function defer(callable $callable): void
{
Coroutine::defer($callable);
}
}
if (! function_exists('class_basename')) {
/**
* Get the class "basename" of the given object / class.
*
* @param object|string $class
* @return string
*/
function class_basename($class)
{
$class = is_object($class) ? get_class($class) : $class;
return basename(str_replace('\\', '/', $class));
}
}
if (! function_exists('trait_uses_recursive')) {
/**
* Returns all traits used by a trait and its traits.
*
* @param string $trait
* @return array
*/
function trait_uses_recursive($trait)
{
$traits = class_uses($trait);
foreach ($traits as $trait) {
$traits += trait_uses_recursive($trait);
}
return $traits;
}
}
if (! function_exists('class_uses_recursive')) {
/**
* Returns all traits used by a class, its parent classes and trait of their traits.
*
* @param object|string $class
* @return array
*/
function class_uses_recursive($class)
{
if (is_object($class)) {
$class = get_class($class);
}
$results = [];
/* @phpstan-ignore-next-line */
foreach (array_reverse(class_parents($class)) + [$class => $class] as $class) {
$results += trait_uses_recursive($class);
}
return array_unique($results);
}
}
if (! function_exists('setter')) {
/**
* Create a setter string.
*/
function setter(string $property): string
{
return 'set' . Str::studly($property);
}
}
if (! function_exists('getter')) {
/**
* Create a getter string.
*/
function getter(string $property): string
{
return 'get' . Str::studly($property);
}
}
if (! function_exists('parallel')) {
/**
* @param callable[] $callables
* @param int $concurrent if $concurrent is equal to 0, that means unlimit
*/
function parallel(array $callables, int $concurrent = 0)
{
$parallel = new Parallel($concurrent);
foreach ($callables as $key => $callable) {
$parallel->add($callable, $key);
}
return $parallel->wait();
}
}
if (! function_exists('make')) {
/**
* Create an object instance, if the DI container exist in ApplicationContext,
* then the object will be created by DI container via `make()` method, if not,
* the object will create by `new` keyword.
*/
function make(string $name, array $parameters = [])
{
if (ApplicationContext::hasContainer()) {
$container = ApplicationContext::getContainer();
if (method_exists($container, 'make')) {
return $container->make($name, $parameters);
}
}
$parameters = array_values($parameters);
return new $name(...$parameters);
}
}
if (! function_exists('run')) {
/**
* Run callable in non-coroutine environment, all hook functions by Swoole only available in the callable.
*
* @param array|callable $callbacks
*/
function run($callbacks, int $flags = SWOOLE_HOOK_ALL): bool
{
if (Coroutine::inCoroutine()) {
throw new RuntimeException('Function \'run\' only execute in non-coroutine environment.');
}
\Swoole\Runtime::enableCoroutine($flags);
$result = \Swoole\Coroutine\Run(...(array) $callbacks);
\Swoole\Runtime::enableCoroutine(false);
return $result;
}
}
if (! function_exists('swoole_hook_flags')) {
/**
* Return the default swoole hook flags, you can rewrite it by defining `SWOOLE_HOOK_FLAGS`.
*/
function swoole_hook_flags(): int
{
return defined('SWOOLE_HOOK_FLAGS') ? SWOOLE_HOOK_FLAGS : SWOOLE_HOOK_ALL;
}
}
if (! function_exists('optional')) {
/**
* Provide access to optional objects.
*
* @param mixed $value
* @return mixed
*/
function optional($value = null, callable $callback = null)
{
if (is_null($callback)) {
return new Optional($value);
}
if (! is_null($value)) {
return $callback($value);
}
}
}
if (! function_exists('wait')) {
function wait(Closure $closure, ?float $timeout = null)
{
if (ApplicationContext::hasContainer()) {
$waiter = ApplicationContext::getContainer()->get(Waiter::class);
return $waiter->wait($closure, $timeout);
}
return (new Waiter())->wait($closure, $timeout);
}
}

View File

@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils;
/**
* @mixin Collection
* Most of the methods in this file come from illuminate/support,
* thanks Laravel Team provide such a useful class.
*/
class HigherOrderCollectionProxy
{
/**
* The collection being operated on.
*
* @var Collection
*/
protected $collection;
/**
* The method being proxied.
*
* @var string
*/
protected $method;
/**
* Create a new proxy instance.
*/
public function __construct(Collection $collection, string $method)
{
$this->method = $method;
$this->collection = $collection;
}
/**
* Proxy accessing an attribute onto the collection items.
*/
public function __get(string $key)
{
return $this->collection->{$this->method}(function ($value) use ($key) {
return is_array($value) ? $value[$key] : $value->{$key};
});
}
/**
* Proxy a method call onto the collection items.
*/
public function __call(string $method, array $parameters)
{
return $this->collection->{$this->method}(function ($value) use ($method, $parameters) {
return $value->{$method}(...$parameters);
});
}
}

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils;
class HigherOrderTapProxy
{
/**
* The target being tapped.
*
* @var mixed
*/
public $target;
/**
* Create a new tap proxy instance.
*
* @param mixed $target
*/
public function __construct($target)
{
$this->target = $target;
}
/**
* Dynamically pass method calls to the target.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
$this->target->{$method}(...$parameters);
return $this->target;
}
}

View File

@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils;
use Carbon\Carbon;
use DateInterval;
use DateTimeInterface;
trait InteractsWithTime
{
/**
* Get the number of seconds until the given DateTime.
*
* @param \DateInterval|\DateTimeInterface|int $delay
*/
protected function secondsUntil($delay): int
{
$delay = $this->parseDateInterval($delay);
return $delay instanceof DateTimeInterface
? max(0, $delay->getTimestamp() - $this->currentTime())
: (int) $delay;
}
/**
* Get the "available at" UNIX timestamp.
*
* @param \DateInterval|\DateTimeInterface|int $delay
*/
protected function availableAt($delay = 0): int
{
$delay = $this->parseDateInterval($delay);
return $delay instanceof DateTimeInterface
? $delay->getTimestamp()
: Carbon::now()->addSeconds($delay)->getTimestamp();
}
/**
* If the given value is an interval, convert it to a DateTime instance.
*
* @param \DateInterval|\DateTimeInterface|int $delay
* @return \DateTimeInterface|int
*/
protected function parseDateInterval($delay)
{
if ($delay instanceof DateInterval) {
$delay = Carbon::now()->add($delay);
}
return $delay;
}
/**
* Get the current system time as a UNIX timestamp.
*/
protected function currentTime(): int
{
return Carbon::now()->getTimestamp();
}
}

View File

@@ -0,0 +1,351 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils;
use Countable;
use Hyperf\Utils\Contracts\Arrayable;
use Hyperf\Utils\Contracts\Jsonable;
use Hyperf\Utils\Contracts\MessageBag as MessageBagContract;
use Hyperf\Utils\Contracts\MessageProvider;
use JsonSerializable;
class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, MessageBagContract, MessageProvider
{
/**
* All of the registered messages.
*
* @var array
*/
protected $messages = [];
/**
* Default format for message output.
*
* @var string
*/
protected $format = ':message';
/**
* Create a new message bag instance.
*/
public function __construct(array $messages = [])
{
foreach ($messages as $key => $value) {
$value = $value instanceof Arrayable ? $value->toArray() : (array) $value;
$this->messages[$key] = array_unique($value);
}
}
/**
* Convert the message bag to its string representation.
*/
public function __toString(): string
{
return $this->toJson();
}
/**
* Get the keys present in the message bag.
*/
public function keys(): array
{
return array_keys($this->messages);
}
/**
* Add a message to the message bag.
*/
public function add(string $key, string $message): MessageBagContract
{
if ($this->isUnique($key, $message)) {
$this->messages[$key][] = $message;
}
return $this;
}
/**
* Merge a new array of messages into the message bag.
*
* @param array|MessageProvider $messages
* @return $this
*/
public function merge($messages)
{
if ($messages instanceof MessageProvider) {
$messages = $messages->getMessageBag()->getMessages();
}
$this->messages = array_merge_recursive($this->messages, $messages);
return $this;
}
/**
* Determine if messages exist for all of the given keys.
*
* @param null|array|string $key
*/
public function has($key): bool
{
if ($this->isEmpty()) {
return false;
}
if (is_null($key)) {
return $this->any();
}
$keys = is_array($key) ? $key : func_get_args();
foreach ($keys as $key) {
if ($this->first($key) === '') {
return false;
}
}
return true;
}
/**
* Determine if messages exist for any of the given keys.
*
* @param array|string $keys
*/
public function hasAny($keys = []): bool
{
if ($this->isEmpty()) {
return false;
}
$keys = is_array($keys) ? $keys : func_get_args();
foreach ($keys as $key) {
if ($this->has($key)) {
return true;
}
}
return false;
}
/**
* Get the first message from the message bag for a given key.
*
* @param string $key
* @param string $format
*/
public function first($key = null, $format = null): string
{
$messages = is_null($key) ? $this->all($format) : $this->get($key, $format);
$firstMessage = Arr::first($messages, null, '');
return is_array($firstMessage) ? Arr::first($firstMessage) : $firstMessage;
}
/**
* Get all of the messages from the message bag for a given key.
*/
public function get(string $key, ?string $format = null): array
{
// If the message exists in the message bag, we will transform it and return
// the message. Otherwise, we will check if the key is implicit & collect
// all the messages that match the given key and output it as an array.
if (array_key_exists($key, $this->messages)) {
return $this->transform(
$this->messages[$key],
$this->checkFormat($format),
$key
);
}
if (Str::contains($key, '*')) {
return $this->getMessagesForWildcardKey($key, $format);
}
return [];
}
/**
* Get all of the messages for every key in the message bag.
*/
public function all(?string $format = null): array
{
$format = $this->checkFormat($format);
$all = [];
foreach ($this->messages as $key => $messages) {
$all = array_merge($all, $this->transform($messages, $format, $key));
}
return $all;
}
/**
* Get all of the unique messages for every key in the message bag.
*/
public function unique(?string $format = null): array
{
return array_unique($this->all($format));
}
/**
* Get the raw messages in the message bag.
*/
public function messages(): array
{
return $this->messages;
}
/**
* Get the raw messages in the message bag.
*/
public function getMessages(): array
{
return $this->messages();
}
/**
* Get the messages for the instance.
*/
public function getMessageBag(): MessageBagContract
{
return $this;
}
/**
* Get the default message format.
*/
public function getFormat(): string
{
return $this->format;
}
/**
* Set the default message format.
*/
public function setFormat(string $format = ':message'): self
{
$this->format = $format;
return $this;
}
/**
* Determine if the message bag has any messages.
*/
public function isEmpty(): bool
{
return ! $this->any();
}
/**
* Determine if the message bag has any messages.
*/
public function isNotEmpty(): bool
{
return $this->any();
}
/**
* Determine if the message bag has any messages.
*/
public function any(): bool
{
return $this->count() > 0;
}
/**
* Get the number of messages in the message bag.
*/
public function count(): int
{
return count($this->messages, COUNT_RECURSIVE) - count($this->messages);
}
/**
* Get the instance as an array.
*/
public function toArray(): array
{
return $this->getMessages();
}
/**
* Convert the object into something JSON serializable.
*/
public function jsonSerialize(): array
{
return $this->toArray();
}
/**
* Convert the object to its JSON representation.
*/
public function toJson(int $options = 0): string
{
return json_encode($this->jsonSerialize(), $options);
}
/**
* Determine if a key and message combination already exists.
*/
protected function isUnique(string $key, string $message): bool
{
$messages = (array) $this->messages;
return ! isset($messages[$key]) || ! in_array($message, $messages[$key]);
}
/**
* Get the messages for a wildcard key.
*/
protected function getMessagesForWildcardKey(string $key, ?string $format): array
{
return collect($this->messages)
->filter(function ($messages, $messageKey) use ($key) {
return Str::is($key, $messageKey);
})
->map(function ($messages, $messageKey) use ($format) {
return $this->transform(
$messages,
$this->checkFormat($format),
$messageKey
);
})->all();
}
/**
* Format an array of messages.
*/
protected function transform(array $messages, string $format, string $messageKey): array
{
return collect($messages)
->map(function ($message) use ($format, $messageKey) {
// We will simply spin through the given messages and transform each one
// replacing the :message place holder with the real message allowing
// the messages to be easily formatted to each developer's desires.
return str_replace([':message', ':key'], [$message, $messageKey], $format);
})->all();
}
/**
* Get the appropriate format based on the given format.
*/
protected function checkFormat(?string $format): string
{
return $format ?: $this->format;
}
}

View File

@@ -0,0 +1,814 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils;
class MimeTypeExtensionGuesser
{
/**
* A map of mime types and their default extensions.
*
* @see http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
*/
protected $defaultExtensions = [
'application/andrew-inset' => 'ez',
'application/applixware' => 'aw',
'application/atom+xml' => 'atom',
'application/atomcat+xml' => 'atomcat',
'application/atomsvc+xml' => 'atomsvc',
'application/ccxml+xml' => 'ccxml',
'application/cdmi-capability' => 'cdmia',
'application/cdmi-container' => 'cdmic',
'application/cdmi-domain' => 'cdmid',
'application/cdmi-object' => 'cdmio',
'application/cdmi-queue' => 'cdmiq',
'application/cu-seeme' => 'cu',
'application/davmount+xml' => 'davmount',
'application/docbook+xml' => 'dbk',
'application/dssc+der' => 'dssc',
'application/dssc+xml' => 'xdssc',
'application/ecmascript' => 'ecma',
'application/emma+xml' => 'emma',
'application/epub+zip' => 'epub',
'application/exi' => 'exi',
'application/font-tdpfr' => 'pfr',
'application/gml+xml' => 'gml',
'application/gpx+xml' => 'gpx',
'application/gxf' => 'gxf',
'application/hyperstudio' => 'stk',
'application/inkml+xml' => 'ink',
'application/ipfix' => 'ipfix',
'application/java-archive' => 'jar',
'application/java-serialized-object' => 'ser',
'application/java-vm' => 'class',
'application/javascript' => 'js',
'application/json' => 'json',
'application/jsonml+json' => 'jsonml',
'application/lost+xml' => 'lostxml',
'application/mac-binhex40' => 'hqx',
'application/mac-compactpro' => 'cpt',
'application/mads+xml' => 'mads',
'application/marc' => 'mrc',
'application/marcxml+xml' => 'mrcx',
'application/mathematica' => 'ma',
'application/mathml+xml' => 'mathml',
'application/mbox' => 'mbox',
'application/mediaservercontrol+xml' => 'mscml',
'application/metalink+xml' => 'metalink',
'application/metalink4+xml' => 'meta4',
'application/mets+xml' => 'mets',
'application/mods+xml' => 'mods',
'application/mp21' => 'm21',
'application/mp4' => 'mp4s',
'application/msword' => 'doc',
'application/mxf' => 'mxf',
'application/octet-stream' => 'bin',
'application/oda' => 'oda',
'application/oebps-package+xml' => 'opf',
'application/ogg' => 'ogx',
'application/omdoc+xml' => 'omdoc',
'application/onenote' => 'onetoc',
'application/oxps' => 'oxps',
'application/patch-ops-error+xml' => 'xer',
'application/pdf' => 'pdf',
'application/pgp-encrypted' => 'pgp',
'application/pgp-signature' => 'asc',
'application/pics-rules' => 'prf',
'application/pkcs10' => 'p10',
'application/pkcs7-mime' => 'p7m',
'application/pkcs7-signature' => 'p7s',
'application/pkcs8' => 'p8',
'application/pkix-attr-cert' => 'ac',
'application/pkix-cert' => 'cer',
'application/pkix-crl' => 'crl',
'application/pkix-pkipath' => 'pkipath',
'application/pkixcmp' => 'pki',
'application/pls+xml' => 'pls',
'application/postscript' => 'ai',
'application/prs.cww' => 'cww',
'application/pskc+xml' => 'pskcxml',
'application/rdf+xml' => 'rdf',
'application/reginfo+xml' => 'rif',
'application/relax-ng-compact-syntax' => 'rnc',
'application/resource-lists+xml' => 'rl',
'application/resource-lists-diff+xml' => 'rld',
'application/rls-services+xml' => 'rs',
'application/rpki-ghostbusters' => 'gbr',
'application/rpki-manifest' => 'mft',
'application/rpki-roa' => 'roa',
'application/rsd+xml' => 'rsd',
'application/rss+xml' => 'rss',
'application/rtf' => 'rtf',
'application/sbml+xml' => 'sbml',
'application/scvp-cv-request' => 'scq',
'application/scvp-cv-response' => 'scs',
'application/scvp-vp-request' => 'spq',
'application/scvp-vp-response' => 'spp',
'application/sdp' => 'sdp',
'application/set-payment-initiation' => 'setpay',
'application/set-registration-initiation' => 'setreg',
'application/shf+xml' => 'shf',
'application/smil+xml' => 'smi',
'application/sparql-query' => 'rq',
'application/sparql-results+xml' => 'srx',
'application/srgs' => 'gram',
'application/srgs+xml' => 'grxml',
'application/sru+xml' => 'sru',
'application/ssdl+xml' => 'ssdl',
'application/ssml+xml' => 'ssml',
'application/tei+xml' => 'tei',
'application/thraud+xml' => 'tfi',
'application/timestamped-data' => 'tsd',
'application/vnd.3gpp.pic-bw-large' => 'plb',
'application/vnd.3gpp.pic-bw-small' => 'psb',
'application/vnd.3gpp.pic-bw-var' => 'pvb',
'application/vnd.3gpp2.tcap' => 'tcap',
'application/vnd.3m.post-it-notes' => 'pwn',
'application/vnd.accpac.simply.aso' => 'aso',
'application/vnd.accpac.simply.imp' => 'imp',
'application/vnd.acucobol' => 'acu',
'application/vnd.acucorp' => 'atc',
'application/vnd.adobe.air-application-installer-package+zip' => 'air',
'application/vnd.adobe.formscentral.fcdt' => 'fcdt',
'application/vnd.adobe.fxp' => 'fxp',
'application/vnd.adobe.xdp+xml' => 'xdp',
'application/vnd.adobe.xfdf' => 'xfdf',
'application/vnd.ahead.space' => 'ahead',
'application/vnd.airzip.filesecure.azf' => 'azf',
'application/vnd.airzip.filesecure.azs' => 'azs',
'application/vnd.amazon.ebook' => 'azw',
'application/vnd.americandynamics.acc' => 'acc',
'application/vnd.amiga.ami' => 'ami',
'application/vnd.android.package-archive' => 'apk',
'application/vnd.anser-web-certificate-issue-initiation' => 'cii',
'application/vnd.anser-web-funds-transfer-initiation' => 'fti',
'application/vnd.antix.game-component' => 'atx',
'application/vnd.apple.installer+xml' => 'mpkg',
'application/vnd.apple.mpegurl' => 'm3u8',
'application/vnd.aristanetworks.swi' => 'swi',
'application/vnd.astraea-software.iota' => 'iota',
'application/vnd.audiograph' => 'aep',
'application/vnd.blueice.multipass' => 'mpm',
'application/vnd.bmi' => 'bmi',
'application/vnd.businessobjects' => 'rep',
'application/vnd.chemdraw+xml' => 'cdxml',
'application/vnd.chipnuts.karaoke-mmd' => 'mmd',
'application/vnd.cinderella' => 'cdy',
'application/vnd.claymore' => 'cla',
'application/vnd.cloanto.rp9' => 'rp9',
'application/vnd.clonk.c4group' => 'c4g',
'application/vnd.cluetrust.cartomobile-config' => 'c11amc',
'application/vnd.cluetrust.cartomobile-config-pkg' => 'c11amz',
'application/vnd.commonspace' => 'csp',
'application/vnd.contact.cmsg' => 'cdbcmsg',
'application/vnd.cosmocaller' => 'cmc',
'application/vnd.crick.clicker' => 'clkx',
'application/vnd.crick.clicker.keyboard' => 'clkk',
'application/vnd.crick.clicker.palette' => 'clkp',
'application/vnd.crick.clicker.template' => 'clkt',
'application/vnd.crick.clicker.wordbank' => 'clkw',
'application/vnd.criticaltools.wbs+xml' => 'wbs',
'application/vnd.ctc-posml' => 'pml',
'application/vnd.cups-ppd' => 'ppd',
'application/vnd.curl.car' => 'car',
'application/vnd.curl.pcurl' => 'pcurl',
'application/vnd.dart' => 'dart',
'application/vnd.data-vision.rdz' => 'rdz',
'application/vnd.dece.data' => 'uvf',
'application/vnd.dece.ttml+xml' => 'uvt',
'application/vnd.dece.unspecified' => 'uvx',
'application/vnd.dece.zip' => 'uvz',
'application/vnd.denovo.fcselayout-link' => 'fe_launch',
'application/vnd.dna' => 'dna',
'application/vnd.dolby.mlp' => 'mlp',
'application/vnd.dpgraph' => 'dpg',
'application/vnd.dreamfactory' => 'dfac',
'application/vnd.ds-keypoint' => 'kpxx',
'application/vnd.dvb.ait' => 'ait',
'application/vnd.dvb.service' => 'svc',
'application/vnd.dynageo' => 'geo',
'application/vnd.ecowin.chart' => 'mag',
'application/vnd.enliven' => 'nml',
'application/vnd.epson.esf' => 'esf',
'application/vnd.epson.msf' => 'msf',
'application/vnd.epson.quickanime' => 'qam',
'application/vnd.epson.salt' => 'slt',
'application/vnd.epson.ssf' => 'ssf',
'application/vnd.eszigno3+xml' => 'es3',
'application/vnd.ezpix-album' => 'ez2',
'application/vnd.ezpix-package' => 'ez3',
'application/vnd.fdf' => 'fdf',
'application/vnd.fdsn.mseed' => 'mseed',
'application/vnd.fdsn.seed' => 'seed',
'application/vnd.flographit' => 'gph',
'application/vnd.fluxtime.clip' => 'ftc',
'application/vnd.framemaker' => 'fm',
'application/vnd.frogans.fnc' => 'fnc',
'application/vnd.frogans.ltf' => 'ltf',
'application/vnd.fsc.weblaunch' => 'fsc',
'application/vnd.fujitsu.oasys' => 'oas',
'application/vnd.fujitsu.oasys2' => 'oa2',
'application/vnd.fujitsu.oasys3' => 'oa3',
'application/vnd.fujitsu.oasysgp' => 'fg5',
'application/vnd.fujitsu.oasysprs' => 'bh2',
'application/vnd.fujixerox.ddd' => 'ddd',
'application/vnd.fujixerox.docuworks' => 'xdw',
'application/vnd.fujixerox.docuworks.binder' => 'xbd',
'application/vnd.fuzzysheet' => 'fzs',
'application/vnd.genomatix.tuxedo' => 'txd',
'application/vnd.geogebra.file' => 'ggb',
'application/vnd.geogebra.tool' => 'ggt',
'application/vnd.geometry-explorer' => 'gex',
'application/vnd.geonext' => 'gxt',
'application/vnd.geoplan' => 'g2w',
'application/vnd.geospace' => 'g3w',
'application/vnd.gmx' => 'gmx',
'application/vnd.google-earth.kml+xml' => 'kml',
'application/vnd.google-earth.kmz' => 'kmz',
'application/vnd.grafeq' => 'gqf',
'application/vnd.groove-account' => 'gac',
'application/vnd.groove-help' => 'ghf',
'application/vnd.groove-identity-message' => 'gim',
'application/vnd.groove-injector' => 'grv',
'application/vnd.groove-tool-message' => 'gtm',
'application/vnd.groove-tool-template' => 'tpl',
'application/vnd.groove-vcard' => 'vcg',
'application/vnd.hal+xml' => 'hal',
'application/vnd.handheld-entertainment+xml' => 'zmm',
'application/vnd.hbci' => 'hbci',
'application/vnd.hhe.lesson-player' => 'les',
'application/vnd.hp-hpgl' => 'hpgl',
'application/vnd.hp-hpid' => 'hpid',
'application/vnd.hp-hps' => 'hps',
'application/vnd.hp-jlyt' => 'jlt',
'application/vnd.hp-pcl' => 'pcl',
'application/vnd.hp-pclxl' => 'pclxl',
'application/vnd.hydrostatix.sof-data' => 'sfd-hdstx',
'application/vnd.ibm.minipay' => 'mpy',
'application/vnd.ibm.modcap' => 'afp',
'application/vnd.ibm.rights-management' => 'irm',
'application/vnd.ibm.secure-container' => 'sc',
'application/vnd.iccprofile' => 'icc',
'application/vnd.igloader' => 'igl',
'application/vnd.immervision-ivp' => 'ivp',
'application/vnd.immervision-ivu' => 'ivu',
'application/vnd.insors.igm' => 'igm',
'application/vnd.intercon.formnet' => 'xpw',
'application/vnd.intergeo' => 'i2g',
'application/vnd.intu.qbo' => 'qbo',
'application/vnd.intu.qfx' => 'qfx',
'application/vnd.ipunplugged.rcprofile' => 'rcprofile',
'application/vnd.irepository.package+xml' => 'irp',
'application/vnd.is-xpr' => 'xpr',
'application/vnd.isac.fcs' => 'fcs',
'application/vnd.jam' => 'jam',
'application/vnd.jcp.javame.midlet-rms' => 'rms',
'application/vnd.jisp' => 'jisp',
'application/vnd.joost.joda-archive' => 'joda',
'application/vnd.kahootz' => 'ktz',
'application/vnd.kde.karbon' => 'karbon',
'application/vnd.kde.kchart' => 'chrt',
'application/vnd.kde.kformula' => 'kfo',
'application/vnd.kde.kivio' => 'flw',
'application/vnd.kde.kontour' => 'kon',
'application/vnd.kde.kpresenter' => 'kpr',
'application/vnd.kde.kspread' => 'ksp',
'application/vnd.kde.kword' => 'kwd',
'application/vnd.kenameaapp' => 'htke',
'application/vnd.kidspiration' => 'kia',
'application/vnd.kinar' => 'kne',
'application/vnd.koan' => 'skp',
'application/vnd.kodak-descriptor' => 'sse',
'application/vnd.las.las+xml' => 'lasxml',
'application/vnd.llamagraphics.life-balance.desktop' => 'lbd',
'application/vnd.llamagraphics.life-balance.exchange+xml' => 'lbe',
'application/vnd.lotus-1-2-3' => '123',
'application/vnd.lotus-approach' => 'apr',
'application/vnd.lotus-freelance' => 'pre',
'application/vnd.lotus-notes' => 'nsf',
'application/vnd.lotus-organizer' => 'org',
'application/vnd.lotus-screencam' => 'scm',
'application/vnd.lotus-wordpro' => 'lwp',
'application/vnd.macports.portpkg' => 'portpkg',
'application/vnd.mcd' => 'mcd',
'application/vnd.medcalcdata' => 'mc1',
'application/vnd.mediastation.cdkey' => 'cdkey',
'application/vnd.mfer' => 'mwf',
'application/vnd.mfmp' => 'mfm',
'application/vnd.micrografx.flo' => 'flo',
'application/vnd.micrografx.igx' => 'igx',
'application/vnd.mif' => 'mif',
'application/vnd.mobius.daf' => 'daf',
'application/vnd.mobius.dis' => 'dis',
'application/vnd.mobius.mbk' => 'mbk',
'application/vnd.mobius.mqy' => 'mqy',
'application/vnd.mobius.msl' => 'msl',
'application/vnd.mobius.plc' => 'plc',
'application/vnd.mobius.txf' => 'txf',
'application/vnd.mophun.application' => 'mpn',
'application/vnd.mophun.certificate' => 'mpc',
'application/vnd.mozilla.xul+xml' => 'xul',
'application/vnd.ms-artgalry' => 'cil',
'application/vnd.ms-cab-compressed' => 'cab',
'application/vnd.ms-excel' => 'xls',
'application/vnd.ms-excel.addin.macroenabled.12' => 'xlam',
'application/vnd.ms-excel.sheet.binary.macroenabled.12' => 'xlsb',
'application/vnd.ms-excel.sheet.macroenabled.12' => 'xlsm',
'application/vnd.ms-excel.template.macroenabled.12' => 'xltm',
'application/vnd.ms-fontobject' => 'eot',
'application/vnd.ms-htmlhelp' => 'chm',
'application/vnd.ms-ims' => 'ims',
'application/vnd.ms-lrm' => 'lrm',
'application/vnd.ms-officetheme' => 'thmx',
'application/vnd.ms-pki.seccat' => 'cat',
'application/vnd.ms-pki.stl' => 'stl',
'application/vnd.ms-powerpoint' => 'ppt',
'application/vnd.ms-powerpoint.addin.macroenabled.12' => 'ppam',
'application/vnd.ms-powerpoint.presentation.macroenabled.12' => 'pptm',
'application/vnd.ms-powerpoint.slide.macroenabled.12' => 'sldm',
'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => 'ppsm',
'application/vnd.ms-powerpoint.template.macroenabled.12' => 'potm',
'application/vnd.ms-project' => 'mpp',
'application/vnd.ms-word.document.macroenabled.12' => 'docm',
'application/vnd.ms-word.template.macroenabled.12' => 'dotm',
'application/vnd.ms-works' => 'wps',
'application/vnd.ms-wpl' => 'wpl',
'application/vnd.ms-xpsdocument' => 'xps',
'application/vnd.mseq' => 'mseq',
'application/vnd.musician' => 'mus',
'application/vnd.muvee.style' => 'msty',
'application/vnd.mynfc' => 'taglet',
'application/vnd.neurolanguage.nlu' => 'nlu',
'application/vnd.nitf' => 'ntf',
'application/vnd.noblenet-directory' => 'nnd',
'application/vnd.noblenet-sealer' => 'nns',
'application/vnd.noblenet-web' => 'nnw',
'application/vnd.nokia.n-gage.data' => 'ngdat',
'application/vnd.nokia.n-gage.symbian.install' => 'n-gage',
'application/vnd.nokia.radio-preset' => 'rpst',
'application/vnd.nokia.radio-presets' => 'rpss',
'application/vnd.novadigm.edm' => 'edm',
'application/vnd.novadigm.edx' => 'edx',
'application/vnd.novadigm.ext' => 'ext',
'application/vnd.oasis.opendocument.chart' => 'odc',
'application/vnd.oasis.opendocument.chart-template' => 'otc',
'application/vnd.oasis.opendocument.database' => 'odb',
'application/vnd.oasis.opendocument.formula' => 'odf',
'application/vnd.oasis.opendocument.formula-template' => 'odft',
'application/vnd.oasis.opendocument.graphics' => 'odg',
'application/vnd.oasis.opendocument.graphics-template' => 'otg',
'application/vnd.oasis.opendocument.image' => 'odi',
'application/vnd.oasis.opendocument.image-template' => 'oti',
'application/vnd.oasis.opendocument.presentation' => 'odp',
'application/vnd.oasis.opendocument.presentation-template' => 'otp',
'application/vnd.oasis.opendocument.spreadsheet' => 'ods',
'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots',
'application/vnd.oasis.opendocument.text' => 'odt',
'application/vnd.oasis.opendocument.text-master' => 'odm',
'application/vnd.oasis.opendocument.text-template' => 'ott',
'application/vnd.oasis.opendocument.text-web' => 'oth',
'application/vnd.olpc-sugar' => 'xo',
'application/vnd.oma.dd2+xml' => 'dd2',
'application/vnd.openofficeorg.extension' => 'oxt',
'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',
'application/vnd.openxmlformats-officedocument.presentationml.slide' => 'sldx',
'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx',
'application/vnd.openxmlformats-officedocument.presentationml.template' => 'potx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'xltx',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx',
'application/vnd.osgeo.mapguide.package' => 'mgp',
'application/vnd.osgi.dp' => 'dp',
'application/vnd.osgi.subsystem' => 'esa',
'application/vnd.palm' => 'pdb',
'application/vnd.pawaafile' => 'paw',
'application/vnd.pg.format' => 'str',
'application/vnd.pg.osasli' => 'ei6',
'application/vnd.picsel' => 'efif',
'application/vnd.pmi.widget' => 'wg',
'application/vnd.pocketlearn' => 'plf',
'application/vnd.powerbuilder6' => 'pbd',
'application/vnd.previewsystems.box' => 'box',
'application/vnd.proteus.magazine' => 'mgz',
'application/vnd.publishare-delta-tree' => 'qps',
'application/vnd.pvi.ptid1' => 'ptid',
'application/vnd.quark.quarkxpress' => 'qxd',
'application/vnd.realvnc.bed' => 'bed',
'application/vnd.recordare.musicxml' => 'mxl',
'application/vnd.recordare.musicxml+xml' => 'musicxml',
'application/vnd.rig.cryptonote' => 'cryptonote',
'application/vnd.rim.cod' => 'cod',
'application/vnd.rn-realmedia' => 'rm',
'application/vnd.rn-realmedia-vbr' => 'rmvb',
'application/vnd.route66.link66+xml' => 'link66',
'application/vnd.sailingtracker.track' => 'st',
'application/vnd.seemail' => 'see',
'application/vnd.sema' => 'sema',
'application/vnd.semd' => 'semd',
'application/vnd.semf' => 'semf',
'application/vnd.shana.informed.formdata' => 'ifm',
'application/vnd.shana.informed.formtemplate' => 'itp',
'application/vnd.shana.informed.interchange' => 'iif',
'application/vnd.shana.informed.package' => 'ipk',
'application/vnd.simtech-mindmapper' => 'twd',
'application/vnd.smaf' => 'mmf',
'application/vnd.smart.teacher' => 'teacher',
'application/vnd.solent.sdkm+xml' => 'sdkm',
'application/vnd.spotfire.dxp' => 'dxp',
'application/vnd.spotfire.sfs' => 'sfs',
'application/vnd.stardivision.calc' => 'sdc',
'application/vnd.stardivision.draw' => 'sda',
'application/vnd.stardivision.impress' => 'sdd',
'application/vnd.stardivision.math' => 'smf',
'application/vnd.stardivision.writer' => 'sdw',
'application/vnd.stardivision.writer-global' => 'sgl',
'application/vnd.stepmania.package' => 'smzip',
'application/vnd.stepmania.stepchart' => 'sm',
'application/vnd.sun.xml.calc' => 'sxc',
'application/vnd.sun.xml.calc.template' => 'stc',
'application/vnd.sun.xml.draw' => 'sxd',
'application/vnd.sun.xml.draw.template' => 'std',
'application/vnd.sun.xml.impress' => 'sxi',
'application/vnd.sun.xml.impress.template' => 'sti',
'application/vnd.sun.xml.math' => 'sxm',
'application/vnd.sun.xml.writer' => 'sxw',
'application/vnd.sun.xml.writer.global' => 'sxg',
'application/vnd.sun.xml.writer.template' => 'stw',
'application/vnd.sus-calendar' => 'sus',
'application/vnd.svd' => 'svd',
'application/vnd.symbian.install' => 'sis',
'application/vnd.syncml+xml' => 'xsm',
'application/vnd.syncml.dm+wbxml' => 'bdm',
'application/vnd.syncml.dm+xml' => 'xdm',
'application/vnd.tao.intent-module-archive' => 'tao',
'application/vnd.tcpdump.pcap' => 'pcap',
'application/vnd.tmobile-livetv' => 'tmo',
'application/vnd.trid.tpt' => 'tpt',
'application/vnd.triscape.mxs' => 'mxs',
'application/vnd.trueapp' => 'tra',
'application/vnd.ufdl' => 'ufd',
'application/vnd.uiq.theme' => 'utz',
'application/vnd.umajin' => 'umj',
'application/vnd.unity' => 'unityweb',
'application/vnd.uoml+xml' => 'uoml',
'application/vnd.vcx' => 'vcx',
'application/vnd.visio' => 'vsd',
'application/vnd.visionary' => 'vis',
'application/vnd.vsf' => 'vsf',
'application/vnd.wap.wbxml' => 'wbxml',
'application/vnd.wap.wmlc' => 'wmlc',
'application/vnd.wap.wmlscriptc' => 'wmlsc',
'application/vnd.webturbo' => 'wtb',
'application/vnd.wolfram.player' => 'nbp',
'application/vnd.wordperfect' => 'wpd',
'application/vnd.wqd' => 'wqd',
'application/vnd.wt.stf' => 'stf',
'application/vnd.xara' => 'xar',
'application/vnd.xfdl' => 'xfdl',
'application/vnd.yamaha.hv-dic' => 'hvd',
'application/vnd.yamaha.hv-script' => 'hvs',
'application/vnd.yamaha.hv-voice' => 'hvp',
'application/vnd.yamaha.openscoreformat' => 'osf',
'application/vnd.yamaha.openscoreformat.osfpvg+xml' => 'osfpvg',
'application/vnd.yamaha.smaf-audio' => 'saf',
'application/vnd.yamaha.smaf-phrase' => 'spf',
'application/vnd.yellowriver-custom-menu' => 'cmp',
'application/vnd.zul' => 'zir',
'application/vnd.zzazz.deck+xml' => 'zaz',
'application/voicexml+xml' => 'vxml',
'application/widget' => 'wgt',
'application/winhlp' => 'hlp',
'application/wsdl+xml' => 'wsdl',
'application/wspolicy+xml' => 'wspolicy',
'application/x-7z-compressed' => '7z',
'application/x-abiword' => 'abw',
'application/x-ace-compressed' => 'ace',
'application/x-apple-diskimage' => 'dmg',
'application/x-authorware-bin' => 'aab',
'application/x-authorware-map' => 'aam',
'application/x-authorware-seg' => 'aas',
'application/x-bcpio' => 'bcpio',
'application/x-bittorrent' => 'torrent',
'application/x-blorb' => 'blb',
'application/x-bzip' => 'bz',
'application/x-bzip2' => 'bz2',
'application/x-cbr' => 'cbr',
'application/x-cdlink' => 'vcd',
'application/x-cfs-compressed' => 'cfs',
'application/x-chat' => 'chat',
'application/x-chess-pgn' => 'pgn',
'application/x-conference' => 'nsc',
'application/x-cpio' => 'cpio',
'application/x-csh' => 'csh',
'application/x-debian-package' => 'deb',
'application/x-dgc-compressed' => 'dgc',
'application/x-director' => 'dir',
'application/x-doom' => 'wad',
'application/x-dtbncx+xml' => 'ncx',
'application/x-dtbook+xml' => 'dtb',
'application/x-dtbresource+xml' => 'res',
'application/x-dvi' => 'dvi',
'application/x-envoy' => 'evy',
'application/x-eva' => 'eva',
'application/x-font-bdf' => 'bdf',
'application/x-font-ghostscript' => 'gsf',
'application/x-font-linux-psf' => 'psf',
'application/x-font-otf' => 'otf',
'application/x-font-pcf' => 'pcf',
'application/x-font-snf' => 'snf',
'application/x-font-ttf' => 'ttf',
'application/x-font-type1' => 'pfa',
'application/x-font-woff' => 'woff',
'application/x-freearc' => 'arc',
'application/x-futuresplash' => 'spl',
'application/x-gca-compressed' => 'gca',
'application/x-glulx' => 'ulx',
'application/x-gnumeric' => 'gnumeric',
'application/x-gramps-xml' => 'gramps',
'application/x-gtar' => 'gtar',
'application/x-hdf' => 'hdf',
'application/x-install-instructions' => 'install',
'application/x-iso9660-image' => 'iso',
'application/x-java-jnlp-file' => 'jnlp',
'application/x-latex' => 'latex',
'application/x-lzh-compressed' => 'lzh',
'application/x-mie' => 'mie',
'application/x-mobipocket-ebook' => 'prc',
'application/x-ms-application' => 'application',
'application/x-ms-shortcut' => 'lnk',
'application/x-ms-wmd' => 'wmd',
'application/x-ms-wmz' => 'wmz',
'application/x-ms-xbap' => 'xbap',
'application/x-msaccess' => 'mdb',
'application/x-msbinder' => 'obd',
'application/x-mscardfile' => 'crd',
'application/x-msclip' => 'clp',
'application/x-msdownload' => 'exe',
'application/x-msmediaview' => 'mvb',
'application/x-msmetafile' => 'wmf',
'application/x-msmoney' => 'mny',
'application/x-mspublisher' => 'pub',
'application/x-msschedule' => 'scd',
'application/x-msterminal' => 'trm',
'application/x-mswrite' => 'wri',
'application/x-netcdf' => 'nc',
'application/x-nzb' => 'nzb',
'application/x-pkcs12' => 'p12',
'application/x-pkcs7-certificates' => 'p7b',
'application/x-pkcs7-certreqresp' => 'p7r',
'application/x-rar-compressed' => 'rar',
'application/x-rar' => 'rar',
'application/x-research-info-systems' => 'ris',
'application/x-sh' => 'sh',
'application/x-shar' => 'shar',
'application/x-shockwave-flash' => 'swf',
'application/x-silverlight-app' => 'xap',
'application/x-sql' => 'sql',
'application/x-stuffit' => 'sit',
'application/x-stuffitx' => 'sitx',
'application/x-subrip' => 'srt',
'application/x-sv4cpio' => 'sv4cpio',
'application/x-sv4crc' => 'sv4crc',
'application/x-t3vm-image' => 't3',
'application/x-tads' => 'gam',
'application/x-tar' => 'tar',
'application/x-tcl' => 'tcl',
'application/x-tex' => 'tex',
'application/x-tex-tfm' => 'tfm',
'application/x-texinfo' => 'texinfo',
'application/x-tgif' => 'obj',
'application/x-ustar' => 'ustar',
'application/x-wais-source' => 'src',
'application/x-x509-ca-cert' => 'der',
'application/x-xfig' => 'fig',
'application/x-xliff+xml' => 'xlf',
'application/x-xpinstall' => 'xpi',
'application/x-xz' => 'xz',
'application/x-zip-compressed' => 'zip',
'application/x-zmachine' => 'z1',
'application/xaml+xml' => 'xaml',
'application/xcap-diff+xml' => 'xdf',
'application/xenc+xml' => 'xenc',
'application/xhtml+xml' => 'xhtml',
'application/xml' => 'xml',
'application/xml-dtd' => 'dtd',
'application/xop+xml' => 'xop',
'application/xproc+xml' => 'xpl',
'application/xslt+xml' => 'xslt',
'application/xspf+xml' => 'xspf',
'application/xv+xml' => 'mxml',
'application/yang' => 'yang',
'application/yin+xml' => 'yin',
'application/zip' => 'zip',
'audio/adpcm' => 'adp',
'audio/basic' => 'au',
'audio/midi' => 'mid',
'audio/mp4' => 'mp4a',
'audio/mpeg' => 'mpga',
'audio/ogg' => 'oga',
'audio/s3m' => 's3m',
'audio/silk' => 'sil',
'audio/vnd.dece.audio' => 'uva',
'audio/vnd.digital-winds' => 'eol',
'audio/vnd.dra' => 'dra',
'audio/vnd.dts' => 'dts',
'audio/vnd.dts.hd' => 'dtshd',
'audio/vnd.lucent.voice' => 'lvp',
'audio/vnd.ms-playready.media.pya' => 'pya',
'audio/vnd.nuera.ecelp4800' => 'ecelp4800',
'audio/vnd.nuera.ecelp7470' => 'ecelp7470',
'audio/vnd.nuera.ecelp9600' => 'ecelp9600',
'audio/vnd.rip' => 'rip',
'audio/webm' => 'weba',
'audio/x-aac' => 'aac',
'audio/x-aiff' => 'aif',
'audio/x-caf' => 'caf',
'audio/x-flac' => 'flac',
'audio/x-matroska' => 'mka',
'audio/x-mpegurl' => 'm3u',
'audio/x-ms-wax' => 'wax',
'audio/x-ms-wma' => 'wma',
'audio/x-pn-realaudio' => 'ram',
'audio/x-pn-realaudio-plugin' => 'rmp',
'audio/x-wav' => 'wav',
'audio/xm' => 'xm',
'chemical/x-cdx' => 'cdx',
'chemical/x-cif' => 'cif',
'chemical/x-cmdf' => 'cmdf',
'chemical/x-cml' => 'cml',
'chemical/x-csml' => 'csml',
'chemical/x-xyz' => 'xyz',
'image/bmp' => 'bmp',
'image/x-ms-bmp' => 'bmp',
'image/cgm' => 'cgm',
'image/g3fax' => 'g3',
'image/gif' => 'gif',
'image/ief' => 'ief',
'image/jpeg' => 'jpg',
'image/pjpeg' => 'jpeg',
'image/ktx' => 'ktx',
'image/png' => 'png',
'image/prs.btif' => 'btif',
'image/sgi' => 'sgi',
'image/svg+xml' => 'svg',
'image/tiff' => 'tiff',
'image/vnd.adobe.photoshop' => 'psd',
'image/vnd.dece.graphic' => 'uvi',
'image/vnd.dvb.subtitle' => 'sub',
'image/vnd.djvu' => 'djvu',
'image/vnd.dwg' => 'dwg',
'image/vnd.dxf' => 'dxf',
'image/vnd.fastbidsheet' => 'fbs',
'image/vnd.fpx' => 'fpx',
'image/vnd.fst' => 'fst',
'image/vnd.fujixerox.edmics-mmr' => 'mmr',
'image/vnd.fujixerox.edmics-rlc' => 'rlc',
'image/vnd.ms-modi' => 'mdi',
'image/vnd.ms-photo' => 'wdp',
'image/vnd.net-fpx' => 'npx',
'image/vnd.wap.wbmp' => 'wbmp',
'image/vnd.xiff' => 'xif',
'image/webp' => 'webp',
'image/x-3ds' => '3ds',
'image/x-cmu-raster' => 'ras',
'image/x-cmx' => 'cmx',
'image/x-freehand' => 'fh',
'image/x-icon' => 'ico',
'image/x-mrsid-image' => 'sid',
'image/x-pcx' => 'pcx',
'image/x-pict' => 'pic',
'image/x-portable-anymap' => 'pnm',
'image/x-portable-bitmap' => 'pbm',
'image/x-portable-graymap' => 'pgm',
'image/x-portable-pixmap' => 'ppm',
'image/x-rgb' => 'rgb',
'image/x-tga' => 'tga',
'image/x-xbitmap' => 'xbm',
'image/x-xpixmap' => 'xpm',
'image/x-xwindowdump' => 'xwd',
'message/rfc822' => 'eml',
'model/iges' => 'igs',
'model/mesh' => 'msh',
'model/vnd.collada+xml' => 'dae',
'model/vnd.dwf' => 'dwf',
'model/vnd.gdl' => 'gdl',
'model/vnd.gtw' => 'gtw',
'model/vnd.mts' => 'mts',
'model/vnd.vtu' => 'vtu',
'model/vrml' => 'wrl',
'model/x3d+binary' => 'x3db',
'model/x3d+vrml' => 'x3dv',
'model/x3d+xml' => 'x3d',
'text/cache-manifest' => 'appcache',
'text/calendar' => 'ics',
'text/css' => 'css',
'text/csv' => 'csv',
'text/html' => 'html',
'text/n3' => 'n3',
'text/plain' => 'txt',
'text/prs.lines.tag' => 'dsc',
'text/richtext' => 'rtx',
'text/rtf' => 'rtf',
'text/sgml' => 'sgml',
'text/tab-separated-values' => 'tsv',
'text/troff' => 't',
'text/turtle' => 'ttl',
'text/uri-list' => 'uri',
'text/vcard' => 'vcard',
'text/vnd.curl' => 'curl',
'text/vnd.curl.dcurl' => 'dcurl',
'text/vnd.curl.scurl' => 'scurl',
'text/vnd.curl.mcurl' => 'mcurl',
'text/vnd.dvb.subtitle' => 'sub',
'text/vnd.fly' => 'fly',
'text/vnd.fmi.flexstor' => 'flx',
'text/vnd.graphviz' => 'gv',
'text/vnd.in3d.3dml' => '3dml',
'text/vnd.in3d.spot' => 'spot',
'text/vnd.sun.j2me.app-descriptor' => 'jad',
'text/vnd.wap.wml' => 'wml',
'text/vnd.wap.wmlscript' => 'wmls',
'text/vtt' => 'vtt',
'text/x-asm' => 's',
'text/x-c' => 'c',
'text/x-fortran' => 'f',
'text/x-pascal' => 'p',
'text/x-java-source' => 'java',
'text/x-opml' => 'opml',
'text/x-nfo' => 'nfo',
'text/x-setext' => 'etx',
'text/x-sfv' => 'sfv',
'text/x-uuencode' => 'uu',
'text/x-vcalendar' => 'vcs',
'text/x-vcard' => 'vcf',
'video/3gpp' => '3gp',
'video/3gpp2' => '3g2',
'video/h261' => 'h261',
'video/h263' => 'h263',
'video/h264' => 'h264',
'video/jpeg' => 'jpgv',
'video/jpm' => 'jpm',
'video/mj2' => 'mj2',
'video/mp4' => 'mp4',
'video/mpeg' => 'mpeg',
'video/ogg' => 'ogv',
'video/quicktime' => 'qt',
'video/vnd.dece.hd' => 'uvh',
'video/vnd.dece.mobile' => 'uvm',
'video/vnd.dece.pd' => 'uvp',
'video/vnd.dece.sd' => 'uvs',
'video/vnd.dece.video' => 'uvv',
'video/vnd.dvb.file' => 'dvb',
'video/vnd.fvt' => 'fvt',
'video/vnd.mpegurl' => 'mxu',
'video/vnd.ms-playready.media.pyv' => 'pyv',
'video/vnd.uvvu.mp4' => 'uvu',
'video/vnd.vivo' => 'viv',
'video/webm' => 'webm',
'video/x-f4v' => 'f4v',
'video/x-fli' => 'fli',
'video/x-flv' => 'flv',
'video/x-m4v' => 'm4v',
'video/x-matroska' => 'mkv',
'video/x-mng' => 'mng',
'video/x-ms-asf' => 'asf',
'video/x-ms-vob' => 'vob',
'video/x-ms-wm' => 'wm',
'video/x-ms-wmv' => 'wmv',
'video/x-ms-wmx' => 'wmx',
'video/x-ms-wvx' => 'wvx',
'video/x-msvideo' => 'avi',
'video/x-sgi-movie' => 'movie',
'video/x-smv' => 'smv',
'x-conference/x-cooltalk' => 'ice',
];
/**
* Reversed from $defaultExtensions property in constructor.
*/
private $defaultMineTypes = [];
public function __construct()
{
$this->defaultMineTypes = array_flip($this->defaultExtensions);
}
public function guessExtension(string $mimeType): ?string
{
return isset($this->defaultExtensions[$mimeType]) ? $this->defaultExtensions[$mimeType] : null;
}
public function guessMimeType(string $extension): ?string
{
return isset($this->defaultMineTypes[$extension]) ? $this->defaultMineTypes[$extension] : null;
}
}

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils;
use RuntimeException;
class Network
{
public static function ip(): string
{
$ips = [];
if (function_exists('swoole_get_local_ip')) {
$ips = swoole_get_local_ip();
}
if (empty($ips) && function_exists('net_get_interfaces')) {
foreach (net_get_interfaces() ?: [] as $name => $value) {
foreach ($value['unicast'] as $item) {
if (! isset($item['address'])) {
continue;
}
if (! Str::contains($item['address'], '::') && $item['address'] !== '127.0.0.1') {
$ips[$name] = $item['address'];
}
}
}
}
if (is_array($ips) && ! empty($ips)) {
return current($ips);
}
/** @var mixed|string $ip */
$ip = gethostbyname(gethostname());
if (is_string($ip)) {
return $ip;
}
throw new RuntimeException('Can not get the internal IP.');
}
}

View File

@@ -0,0 +1,134 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils;
use ArrayAccess;
use Hyperf\Macroable\Macroable;
class Optional implements ArrayAccess
{
use Macroable {
__call as macroCall;
}
/**
* The underlying object.
*
* @var mixed
*/
protected $value;
/**
* Create a new optional instance.
*
* @param mixed $value
*/
public function __construct($value)
{
$this->value = $value;
}
/**
* Dynamically access a property on the underlying object.
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
if (is_object($this->value)) {
return $this->value->{$key} ?? null;
}
return null;
}
/**
* Dynamically check a property exists on the underlying object.
*
* @param mixed $name
* @return bool
*/
public function __isset($name)
{
if (is_object($this->value)) {
return isset($this->value->{$name});
}
return false;
}
/**
* Dynamically pass a method to the underlying object.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
if (static::hasMacro($method)) {
return $this->macroCall($method, $parameters);
}
if (is_object($this->value)) {
return $this->value->{$method}(...$parameters);
}
}
/**
* Determine if an item exists at an offset.
*
* @param mixed $key
* @return bool
*/
public function offsetExists($key)
{
return Arr::accessible($this->value) && Arr::exists($this->value, $key);
}
/**
* Get an item at a given offset.
*
* @param mixed $key
* @return mixed
*/
public function offsetGet($key)
{
return Arr::get($this->value, $key);
}
/**
* Set the item at a given offset.
*
* @param mixed $key
* @param mixed $value
*/
public function offsetSet($key, $value)
{
if (Arr::accessible($this->value)) {
$this->value[$key] = $value;
}
}
/**
* Unset the item at a given offset.
*
* @param string $key
*/
public function offsetUnset($key)
{
if (Arr::accessible($this->value)) {
unset($this->value[$key]);
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Packer;
use Hyperf\Contract\PackerInterface;
class JsonPacker implements PackerInterface
{
public function pack($data): string
{
return json_encode($data, JSON_UNESCAPED_UNICODE);
}
public function unpack(string $data)
{
return json_decode($data, true);
}
}

View File

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

View File

@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils;
use Hyperf\Utils\Exception\ParallelExecutionException;
use Swoole\Coroutine\Channel;
class Parallel
{
/**
* @var callable[]
*/
private $callbacks = [];
/**
* @var null|Channel
*/
private $concurrentChannel;
private $results = [];
/**
* @var \Throwable[]
*/
private $throwables = [];
/**
* @param int $concurrent if $concurrent is equal to 0, that means unlimit
*/
public function __construct(int $concurrent = 0)
{
if ($concurrent > 0) {
$this->concurrentChannel = new Channel($concurrent);
}
}
public function add(callable $callable, $key = null)
{
if (is_null($key)) {
$this->callbacks[] = $callable;
} else {
$this->callbacks[$key] = $callable;
}
}
public function wait(bool $throw = true): array
{
$wg = new WaitGroup();
$wg->add(count($this->callbacks));
foreach ($this->callbacks as $key => $callback) {
$this->concurrentChannel && $this->concurrentChannel->push(true);
$this->results[$key] = null;
Coroutine::create(function () use ($callback, $key, $wg) {
try {
$this->results[$key] = $callback();
} catch (\Throwable $throwable) {
$this->throwables[$key] = $throwable;
unset($this->results[$key]);
} finally {
$this->concurrentChannel && $this->concurrentChannel->pop();
$wg->done();
}
});
}
$wg->wait();
if ($throw && ($throwableCount = count($this->throwables)) > 0) {
$message = 'Detecting ' . $throwableCount . ' throwable occurred during parallel execution:' . PHP_EOL . $this->formatThrowables($this->throwables);
$executionException = new ParallelExecutionException($message);
$executionException->setResults($this->results);
$executionException->setThrowables($this->throwables);
unset($this->results, $this->throwables);
throw $executionException;
}
return $this->results;
}
public function count(): int
{
return count($this->callbacks);
}
public function clear(): void
{
$this->callbacks = [];
$this->results = [];
$this->throwables = [];
}
/**
* Format throwables into a nice list.
*
* @param \Throwable[] $throwables
*/
private function formatThrowables(array $throwables): string
{
$output = '';
foreach ($throwables as $key => $value) {
$output .= \sprintf('(%s) %s: %s' . PHP_EOL . '%s' . PHP_EOL, $key, get_class($value), $value->getMessage(), $value->getTraceAsString());
}
return $output;
}
}

View File

@@ -0,0 +1,171 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils;
use Closure;
use Psr\Container\ContainerInterface;
/**
* This file mostly code come from illuminate/pipe,
* thanks Laravel Team provide such a useful class.
*/
class Pipeline
{
/**
* The container implementation.
*
* @var 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.
*
* @param string $pipe
* @return array
*/
protected function parsePipeString($pipe)
{
[$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;
}
}

View File

@@ -0,0 +1,161 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils;
use Doctrine\Inflector\CachedWordInflector;
use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\Rules\English;
use Doctrine\Inflector\RulesetInflector;
class Pluralizer
{
/**
* Uncountable word forms.
*
* @var array
*/
public static $uncountable
= [
'audio',
'bison',
'cattle',
'chassis',
'compensation',
'coreopsis',
'data',
'deer',
'education',
'emoji',
'equipment',
'evidence',
'feedback',
'firmware',
'fish',
'furniture',
'gold',
'hardware',
'information',
'jedi',
'kin',
'knowledge',
'love',
'metadata',
'money',
'moose',
'news',
'nutrition',
'offspring',
'plankton',
'pokemon',
'police',
'rain',
'rice',
'series',
'sheep',
'software',
'species',
'swine',
'traffic',
'wheat',
];
/**
* @var null|Inflector
*/
protected static $inflector;
/**
* Get the plural form of an English word.
*
* @param string $value
* @param int $count
* @return string
*/
public static function plural($value, $count = 2)
{
if ((int) abs($count) === 1 || static::uncountable($value)) {
return $value;
}
$plural = static::getInflector()->pluralize($value);
return static::matchCase($plural, $value);
}
/**
* Get the singular form of an English word.
*
* @param string $value
* @return string
*/
public static function singular($value)
{
$singular = static::getInflector()->singularize($value);
return static::matchCase($singular, $value);
}
public static function setInflector(?Inflector $inflector): void
{
static::$inflector = $inflector;
}
/**
* Get the inflector instance.
*/
public static function getInflector(): Inflector
{
if (is_null(static::$inflector)) {
static::$inflector = new Inflector(
new CachedWordInflector(new RulesetInflector(
English\Rules::getSingularRuleset()
)),
new CachedWordInflector(new RulesetInflector(
English\Rules::getPluralRuleset()
))
);
}
return static::$inflector;
}
/**
* Determine if the given value is uncountable.
*
* @param string $value
* @return bool
*/
protected static function uncountable($value)
{
return in_array(strtolower($value), static::$uncountable);
}
/**
* Attempt to match the case on two strings.
*
* @param string $value
* @param string $comparison
* @return string
*/
protected static function matchCase($value, $comparison)
{
$functions = ['mb_strtolower', 'mb_strtoupper', 'ucfirst', 'ucwords'];
foreach ($functions as $function) {
if (call_user_func($function, $comparison) === $comparison) {
return call_user_func($function, $value);
}
}
return $value;
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Reflection;
use ReflectionClass;
class ClassInvoker
{
/**
* @var object
*/
protected $instance;
/**
* @var ReflectionClass
*/
protected $reflection;
public function __construct(object $instance)
{
$this->instance = $instance;
$this->reflection = new ReflectionClass($instance);
}
public function __get($name)
{
$property = $this->reflection->getProperty($name);
$property->setAccessible(true);
return $property->getValue($this->instance);
}
public function __call($name, $arguments)
{
$method = $this->reflection->getMethod($name);
$method->setAccessible(true);
return $method->invokeArgs($this->instance, $arguments);
}
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils;
/**
* @deprecated v2.3, please use ResourceGenerator instead.
*/
class Resource
{
/**
* TODO: Swoole file hook does not support `php://temp` and `php://memory`.
*/
public static function from(string $body, string $filename = 'php://temp')
{
$resource = fopen($filename, 'r+');
if ($body !== '') {
fwrite($resource, $body);
fseek($resource, 0);
}
return $resource;
}
public static function fromMemory(string $body)
{
return static::from($body, 'php://memory');
}
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils;
class ResourceGenerator
{
/**
* TODO: Swoole file hook does not support `php://temp` and `php://memory`.
*/
public static function from(string $body, string $filename = 'php://temp')
{
$resource = fopen($filename, 'r+');
if ($body !== '') {
fwrite($resource, $body);
fseek($resource, 0);
}
return $resource;
}
public static function fromMemory(string $body)
{
return static::from($body, 'php://memory');
}
}

View File

@@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Serializer;
use Doctrine\Instantiator\Instantiator;
use Hyperf\Di\ReflectionManager;
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class ExceptionNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface
{
/**
* @var null|Instantiator
*/
protected $instantiator;
public function denormalize($data, string $class, string $format = null, array $context = [])
{
if (is_string($data)) {
$ex = unserialize($data);
if ($ex instanceof \Throwable) {
return $ex;
}
// Retry handle it if the exception not instanceof \Throwable.
$data = $ex;
}
if (is_array($data) && isset($data['message'], $data['code'])) {
try {
$exception = $this->getInstantiator()->instantiate($class);
foreach (['code', 'message', 'file', 'line'] as $attribute) {
if (isset($data[$attribute])) {
$property = ReflectionManager::reflectProperty($class, $attribute);
$property->setAccessible(true);
$property->setValue($exception, $data[$attribute]);
}
}
return $exception;
} catch (\ReflectionException $e) {
return new \RuntimeException(sprintf(
'Bad data %s: %s',
$data['class'],
$data['message']
), $data['code']);
} catch (\TypeError $e) {
return new \RuntimeException(sprintf(
'Uncaught data %s: %s',
$data['class'],
$data['message']
), $data['code']);
}
}
return new \RuntimeException('Bad data data: ' . json_encode($data));
}
public function supportsDenormalization($data, $type, $format = null)
{
return class_exists($type) && is_a($type, \Throwable::class, true);
}
public function normalize($object, string $format = null, array $context = [])
{
if ($object instanceof \Serializable) {
return serialize($object);
}
/* @var \Throwable $object */
return [
'message' => $object->getMessage(),
'code' => $object->getCode(),
'file' => $object->getFile(),
'line' => $object->getLine(),
];
}
public function supportsNormalization($data, string $format = null)
{
return $data instanceof \Throwable;
}
public function hasCacheableSupportsMethod(): bool
{
return \get_class($this) === __CLASS__;
}
protected function getInstantiator(): Instantiator
{
if ($this->instantiator instanceof Instantiator) {
return $this->instantiator;
}
return $this->instantiator = new Instantiator();
}
}

View File

@@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Serializer;
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use function get_class;
use function is_scalar;
class ScalarNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface
{
public function hasCacheableSupportsMethod(): bool
{
return get_class($this) === __CLASS__;
}
public function denormalize($data, string $class, string $format = null, array $context = [])
{
switch ($class) {
case 'int':
return (int) $data;
case 'string':
return (string) $data;
case 'float':
return (float) $data;
case 'bool':
return (bool) $data;
default:
return $data;
}
}
public function supportsDenormalization($data, $type, string $format = null)
{
return in_array($type, [
'int',
'string',
'float',
'bool',
'mixed',
'array', // TODO: Symfony\Component\Serializer\Normalizer\ArrayDenormalizer not support array, so it denormalized in ScalarNormalizer.
]);
}
public function normalize($object, string $format = null, array $context = [])
{
return $object;
}
public function supportsNormalization($data, string $format = null)
{
return is_scalar($data);
}
}

View File

@@ -0,0 +1,312 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Serializer;
use Hyperf\Contract\NormalizerInterface as Normalizer;
use Symfony\Component\Serializer\Encoder;
use Symfony\Component\Serializer\Encoder\ChainDecoder;
use Symfony\Component\Serializer\Encoder\ChainEncoder;
use Symfony\Component\Serializer\Encoder\ContextAwareDecoderInterface;
use Symfony\Component\Serializer\Encoder\ContextAwareEncoderInterface;
use Symfony\Component\Serializer\Encoder\DecoderInterface;
use Symfony\Component\Serializer\Encoder\EncoderInterface;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Exception\LogicException;
use Symfony\Component\Serializer\Exception\NotEncodableValueException;
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\SerializerAwareInterface;
use Symfony\Component\Serializer\SerializerInterface;
/**
* Serializer serializes and deserializes data.
*
* objects are turned into arrays by normalizers.
* arrays are turned into various output formats by encoders.
*
* $serializer->serialize($obj, 'xml')
* $serializer->decode($data, 'xml')
* $serializer->denormalize($data, 'Class', 'xml')
*/
class Serializer implements Normalizer, SerializerInterface, ContextAwareNormalizerInterface, ContextAwareDenormalizerInterface, ContextAwareEncoderInterface, ContextAwareDecoderInterface
{
private const SCALAR_TYPES = [
'int' => true,
'bool' => true,
'float' => true,
'string' => true,
];
/**
* @var Encoder\ChainEncoder
*/
protected $encoder;
/**
* @var Encoder\ChainDecoder
*/
protected $decoder;
private $normalizers = [];
private $denormalizerCache = [];
private $normalizerCache = [];
/**
* @param (NormalizerInterface|DenormalizerInterface|mixed)[] $normalizers
* @param (EncoderInterface|DecoderInterface|mixed)[] $encoders
*/
public function __construct(array $normalizers = [], array $encoders = [])
{
foreach ($normalizers as $normalizer) {
if ($normalizer instanceof SerializerAwareInterface) {
$normalizer->setSerializer($this);
}
if ($normalizer instanceof DenormalizerAwareInterface) {
$normalizer->setDenormalizer($this);
}
if ($normalizer instanceof NormalizerAwareInterface) {
$normalizer->setNormalizer($this);
}
if (! ($normalizer instanceof NormalizerInterface || $normalizer instanceof DenormalizerInterface)) {
throw new InvalidArgumentException(sprintf('The class "%s" neither implements "%s" nor "%s".', get_debug_type($normalizer), NormalizerInterface::class, DenormalizerInterface::class));
}
}
$this->normalizers = $normalizers;
$decoders = [];
$realEncoders = [];
foreach ($encoders as $encoder) {
if ($encoder instanceof SerializerAwareInterface) {
$encoder->setSerializer($this);
}
if ($encoder instanceof DecoderInterface) {
$decoders[] = $encoder;
}
if ($encoder instanceof EncoderInterface) {
$realEncoders[] = $encoder;
}
if (! ($encoder instanceof EncoderInterface || $encoder instanceof DecoderInterface)) {
throw new InvalidArgumentException(sprintf('The class "%s" neither implements "%s" nor "%s".', get_debug_type($encoder), EncoderInterface::class, DecoderInterface::class));
}
}
$this->encoder = new ChainEncoder($realEncoders);
$this->decoder = new ChainDecoder($decoders);
}
final public function serialize($data, string $format, array $context = []): string
{
if (! $this->supportsEncoding($format, $context)) {
throw new NotEncodableValueException(sprintf('Serialization for the format "%s" is not supported.', $format));
}
if ($this->encoder->needsNormalization($format, $context)) {
$data = $this->normalize($data, $format, $context);
}
return $this->encode($data, $format, $context);
}
final public function deserialize($data, string $type, string $format, array $context = [])
{
if (! $this->supportsDecoding($format, $context)) {
throw new NotEncodableValueException(sprintf('Deserialization for the format "%s" is not supported.', $format));
}
$data = $this->decode($data, $format, $context);
return $this->denormalize($data, $type, $format, $context);
}
public function normalize($data, string $format = null, array $context = [])
{
// If a normalizer supports the given data, use it
if ($normalizer = $this->getNormalizer($data, $format, $context)) {
return $normalizer->normalize($data, $format, $context);
}
if ($data === null || is_scalar($data)) {
return $data;
}
if (\is_array($data) || $data instanceof \Traversable) {
if ($data instanceof \Countable && $data->count() === 0) {
return $data;
}
$normalized = [];
foreach ($data as $key => $val) {
$normalized[$key] = $this->normalize($val, $format, $context);
}
return $normalized;
}
if (\is_object($data)) {
if (! $this->normalizers) {
throw new LogicException('You must register at least one normalizer to be able to normalize objects.');
}
throw new NotNormalizableValueException(sprintf('Could not normalize object of type "%s", no supporting normalizer found.', get_debug_type($data)));
}
throw new NotNormalizableValueException('An unexpected value could not be normalized: ' . (! \is_resource($data) ? var_export($data, true) : sprintf('%s resource', get_resource_type($data))));
}
/**
* @param mixed $data
* @throws NotNormalizableValueException
*/
public function denormalize($data, string $type, string $format = null, array $context = [])
{
if (isset(self::SCALAR_TYPES[$type])) {
if (is_scalar($data)) {
switch ($type) {
case 'int':
return (int) $data;
case 'bool':
return (bool) $data;
case 'float':
return (float) $data;
case 'string':
return (string) $data;
}
}
}
if (! $this->normalizers) {
throw new LogicException('You must register at least one normalizer to be able to denormalize objects.');
}
if ($normalizer = $this->getDenormalizer($data, $type, $format, $context)) {
return $normalizer->denormalize($data, $type, $format, $context);
}
throw new NotNormalizableValueException(sprintf('Could not denormalize object of type "%s", no supporting normalizer found.', $type));
}
public function supportsNormalization($data, string $format = null, array $context = [])
{
return $this->getNormalizer($data, $format, $context) !== null;
}
public function supportsDenormalization($data, string $type, string $format = null, array $context = [])
{
return isset(self::SCALAR_TYPES[$type]) || $this->getDenormalizer($data, $type, $format, $context) !== null;
}
final public function encode($data, string $format, array $context = [])
{
return $this->encoder->encode($data, $format, $context);
}
final public function decode(string $data, string $format, array $context = [])
{
return $this->decoder->decode($data, $format, $context);
}
public function supportsEncoding(string $format, array $context = [])
{
return $this->encoder->supportsEncoding($format, $context);
}
public function supportsDecoding(string $format, array $context = [])
{
return $this->decoder->supportsDecoding($format, $context);
}
/**
* Returns a matching normalizer.
*
* @param mixed $data Data to get the serializer for
* @param string $format Format name, present to give the option to normalizers to act differently based on formats
* @param array $context Options available to the normalizer
*/
private function getNormalizer($data, ?string $format, array $context): ?NormalizerInterface
{
$type = \is_object($data) ? \get_class($data) : 'native-' . \gettype($data);
if (! isset($this->normalizerCache[$format][$type])) {
$this->normalizerCache[$format][$type] = [];
foreach ($this->normalizers as $k => $normalizer) {
if (! $normalizer instanceof NormalizerInterface) {
continue;
}
if (! $normalizer instanceof CacheableSupportsMethodInterface || ! $normalizer->hasCacheableSupportsMethod()) {
$this->normalizerCache[$format][$type][$k] = false;
} elseif ($normalizer->supportsNormalization($data, $format)) {
$this->normalizerCache[$format][$type][$k] = true;
break;
}
}
}
foreach ($this->normalizerCache[$format][$type] as $k => $cached) {
$normalizer = $this->normalizers[$k];
if ($cached || $normalizer->supportsNormalization($data, $format, $context)) {
return $normalizer;
}
}
return null;
}
/**
* Returns a matching denormalizer.
*
* @param mixed $data Data to restore
* @param string $class The expected class to instantiate
* @param string $format Format name, present to give the option to normalizers to act differently based on formats
* @param array $context Options available to the denormalizer
*/
private function getDenormalizer($data, string $class, ?string $format, array $context): ?DenormalizerInterface
{
if (! isset($this->denormalizerCache[$format][$class])) {
$this->denormalizerCache[$format][$class] = [];
foreach ($this->normalizers as $k => $normalizer) {
if (! $normalizer instanceof DenormalizerInterface) {
continue;
}
if (! $normalizer instanceof CacheableSupportsMethodInterface || ! $normalizer->hasCacheableSupportsMethod()) {
$this->denormalizerCache[$format][$class][$k] = false;
} elseif ($normalizer->supportsDenormalization(null, $class, $format)) {
$this->denormalizerCache[$format][$class][$k] = true;
break;
}
}
}
foreach ($this->denormalizerCache[$format][$class] as $k => $cached) {
$normalizer = $this->normalizers[$k];
if ($cached || $normalizer->supportsDenormalization($data, $class, $format, $context)) {
return $normalizer;
}
}
return null;
}
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Serializer;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
class SerializerFactory
{
/**
* @var string
*/
protected $serializer;
public function __construct(string $serializer = Serializer::class)
{
$this->serializer = $serializer;
}
public function __invoke()
{
return new $this->serializer([
new ExceptionNormalizer(),
new ObjectNormalizer(),
new ArrayDenormalizer(),
new ScalarNormalizer(),
]);
}
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Serializer;
use Hyperf\Contract\NormalizerInterface;
class SimpleNormalizer implements NormalizerInterface
{
public function normalize($object)
{
return $object;
}
public function denormalize($data, string $class)
{
switch ($class) {
case 'int':
return (int) $data;
case 'string':
return (string) $data;
case 'float':
return (float) $data;
case 'array':
return (array) $data;
case 'bool':
return (bool) $data;
default:
return $data;
}
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Serializer;
use Hyperf\Contract\NormalizerInterface;
use Symfony\Component\Serializer\Serializer;
class SymfonyNormalizer implements NormalizerInterface
{
/**
* @var Serializer
*/
protected $serializer;
public function __construct(Serializer $serializer)
{
$this->serializer = $serializer;
}
public function normalize($object)
{
return $this->serializer->normalize($object);
}
public function denormalize($data, string $class)
{
return $this->serializer->denormalize($data, $class);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,803 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils;
use Closure;
use Hyperf\Macroable\Macroable;
use JsonSerializable;
class Stringable implements JsonSerializable
{
use Traits\Conditionable;
use Macroable;
use Traits\Tappable;
/**
* The underlying string value.
*
* @var string
*/
protected $value;
/**
* Create a new instance of the class.
*
* @param string $value
*/
public function __construct($value = '')
{
$this->value = (string) $value;
}
/**
* Proxy dynamic properties onto methods.
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
return $this->{$key}();
}
/**
* Get the raw string value.
*
* @return string
*/
public function __toString()
{
return (string) $this->value;
}
/**
* Return the remainder of a string after the first occurrence of a given value.
*
* @param string $search
* @return static
*/
public function after($search)
{
return new static(Str::after($this->value, $search));
}
/**
* Return the remainder of a string after the last occurrence of a given value.
*
* @param string $search
* @return static
*/
public function afterLast($search)
{
return new static(Str::afterLast($this->value, $search));
}
/**
* Append the given values to the string.
*
* @param array $values
* @return static
*/
public function append(...$values)
{
return new static($this->value . implode('', $values));
}
/**
* Transliterate a UTF-8 value to ASCII.
*
* @param string $language
* @return static
*/
public function ascii($language = 'en')
{
return new static(Str::ascii($this->value, $language));
}
/**
* Get the trailing name component of the path.
*
* @param string $suffix
* @return static
*/
public function basename($suffix = '')
{
return new static(basename($this->value, $suffix));
}
/**
* Get the basename of the class path.
*
* @return static
*/
public function classBasename()
{
return new static(class_basename($this->value));
}
/**
* Get the portion of a string before the first occurrence of a given value.
*
* @param string $search
* @return static
*/
public function before($search)
{
return new static(Str::before($this->value, $search));
}
/**
* Get the portion of a string before the last occurrence of a given value.
*
* @param string $search
* @return static
*/
public function beforeLast($search)
{
return new static(Str::beforeLast($this->value, $search));
}
/**
* Get the portion of a string between two given values.
*
* @param string $from
* @param string $to
* @return static
*/
public function between($from, $to)
{
return new static(Str::between($this->value, $from, $to));
}
/**
* Convert a value to camel case.
*
* @return static
*/
public function camel()
{
return new static(Str::camel($this->value));
}
/**
* Determine if a given string contains a given substring.
*
* @param array|string $needles
* @return bool
*/
public function contains($needles)
{
return Str::contains($this->value, $needles);
}
/**
* Determine if a given string contains all array values.
*
* @return bool
*/
public function containsAll(array $needles)
{
return Str::containsAll($this->value, $needles);
}
/**
* Get the parent directory's path.
*
* @param int $levels
* @return static
*/
public function dirname($levels = 1)
{
return new static(dirname($this->value, $levels));
}
/**
* Determine if a given string ends with a given substring.
*
* @param array|string $needles
* @return bool
*/
public function endsWith($needles)
{
return Str::endsWith($this->value, $needles);
}
/**
* Determine if the string is an exact match with the given value.
*
* @param string $value
* @return bool
*/
public function exactly($value)
{
return $this->value === $value;
}
/**
* Explode the string into an array.
*
* @param string $delimiter
* @param int $limit
* @return \Hyperf\Utils\Collection
*/
public function explode($delimiter, $limit = PHP_INT_MAX)
{
return collect(explode($delimiter, $this->value, $limit));
}
/**
* Split a string using a regular expression or by length.
*
* @param int|string $pattern
* @param int $limit
* @param int $flags
* @return \Hyperf\Utils\Collection
*/
public function split($pattern, $limit = -1, $flags = 0)
{
if (filter_var($pattern, FILTER_VALIDATE_INT) !== false) {
return collect(mb_str_split($this->value, $pattern));
}
$segments = preg_split($pattern, $this->value, $limit, $flags);
return ! empty($segments) ? collect($segments) : collect();
}
/**
* Cap a string with a single instance of a given value.
*
* @param string $cap
* @return static
*/
public function finish($cap)
{
return new static(Str::finish($this->value, $cap));
}
/**
* Determine if a given string matches a given pattern.
*
* @param array|string $pattern
* @return bool
*/
public function is($pattern)
{
return Str::is($pattern, $this->value);
}
/**
* Determine if the given string is empty.
*
* @return bool
*/
public function isEmpty()
{
return $this->value === '';
}
/**
* Determine if the given string is not empty.
*
* @return bool
*/
public function isNotEmpty()
{
return ! $this->isEmpty();
}
/**
* Convert a string to kebab case.
*
* @return static
*/
public function kebab()
{
return new static(Str::kebab($this->value));
}
/**
* Return the length of the given string.
*
* @param string $encoding
* @return int
*/
public function length($encoding = null)
{
return Str::length($this->value, $encoding);
}
/**
* Limit the number of characters in a string.
*
* @param int $limit
* @param string $end
* @return static
*/
public function limit($limit = 100, $end = '...')
{
return new static(Str::limit($this->value, $limit, $end));
}
/**
* Convert the given string to lower-case.
*
* @return static
*/
public function lower()
{
return new static(Str::lower($this->value));
}
/**
* Get the string matching the given pattern.
*
* @param string $pattern
* @return static
*/
public function match($pattern)
{
preg_match($pattern, $this->value, $matches);
if (! $matches) {
return new static();
}
return new static($matches[1] ?? $matches[0]);
}
/**
* Get the string matching the given pattern.
*
* @param string $pattern
* @return \Hyperf\Utils\Collection
*/
public function matchAll($pattern)
{
preg_match_all($pattern, $this->value, $matches);
if (empty($matches[0])) {
return collect();
}
return collect($matches[1] ?? $matches[0]);
}
/**
* Determine if the string matches the given pattern.
*
* @param string $pattern
* @return bool
*/
public function test($pattern)
{
return $this->match($pattern)->isNotEmpty();
}
/**
* Pad both sides of the string with another.
*
* @param int $length
* @param string $pad
* @return static
*/
public function padBoth($length, $pad = ' ')
{
return new static(Str::padBoth($this->value, $length, $pad));
}
/**
* Pad the left side of the string with another.
*
* @param int $length
* @param string $pad
* @return static
*/
public function padLeft($length, $pad = ' ')
{
return new static(Str::padLeft($this->value, $length, $pad));
}
/**
* Pad the right side of the string with another.
*
* @param int $length
* @param string $pad
* @return static
*/
public function padRight($length, $pad = ' ')
{
return new static(Str::padRight($this->value, $length, $pad));
}
/**
* Parse a Class@method style callback into class and method.
*
* @param null|string $default
* @return array
*/
public function parseCallback($default = null)
{
return Str::parseCallback($this->value, $default);
}
/**
* Call the given callback and return a new string.
*
* @return static
*/
public function pipe(callable $callback)
{
return new static(call_user_func($callback, $this));
}
/**
* Get the plural form of an English word.
*
* @param int $count
* @return static
*/
public function plural($count = 2)
{
return new static(Str::plural($this->value, $count));
}
/**
* Pluralize the last word of an English, studly caps case string.
*
* @param int $count
* @return static
*/
public function pluralStudly($count = 2)
{
return new static(Str::pluralStudly($this->value, $count));
}
/**
* Prepend the given values to the string.
*
* @param array $values
* @return static
*/
public function prepend(...$values)
{
return new static(implode('', $values) . $this->value);
}
/**
* Remove any occurrence of the given string in the subject.
*
* @param array<string>|string $search
* @param bool $caseSensitive
* @return static
*/
public function remove($search, $caseSensitive = true)
{
return new static(Str::remove($search, $this->value, $caseSensitive));
}
/**
* Repeat the string.
*
* @return static
*/
public function repeat(int $times)
{
return new static(Str::repeat($this->value, $times));
}
/**
* Replace the given value in the given string.
*
* @param string|string[] $search
* @param string|string[] $replace
* @return static
*/
public function replace($search, $replace)
{
return new static(Str::replace($search, $replace, $this->value));
}
/**
* Replace a given value in the string sequentially with an array.
*
* @param string $search
* @return static
*/
public function replaceArray($search, array $replace)
{
return new static(Str::replaceArray($search, $replace, $this->value));
}
/**
* Replace the first occurrence of a given value in the string.
*
* @param string $search
* @param string $replace
* @return static
*/
public function replaceFirst($search, $replace)
{
return new static(Str::replaceFirst($search, $replace, $this->value));
}
/**
* Replace the last occurrence of a given value in the string.
*
* @param string $search
* @param string $replace
* @return static
*/
public function replaceLast($search, $replace)
{
return new static(Str::replaceLast($search, $replace, $this->value));
}
/**
* Replace the patterns matching the given regular expression.
*
* @param string $pattern
* @param \Closure|string $replace
* @param int $limit
* @return static
*/
public function replaceMatches($pattern, $replace, $limit = -1)
{
if ($replace instanceof Closure) {
return new static(preg_replace_callback($pattern, $replace, $this->value, $limit));
}
return new static(preg_replace($pattern, $replace, $this->value, $limit));
}
/**
* Begin a string with a single instance of a given value.
*
* @param string $prefix
* @return static
*/
public function start($prefix)
{
return new static(Str::start($this->value, $prefix));
}
/**
* Strip HTML and PHP tags from the given string.
*
* @param null|string|string[] $allowedTags
* @return static
*/
public function stripTags($allowedTags = null)
{
return new static(strip_tags($this->value, $allowedTags));
}
/**
* Convert the given string to upper-case.
*
* @return static
*/
public function upper()
{
return new static(Str::upper($this->value));
}
/**
* Convert the given string to title case.
*
* @return static
*/
public function title()
{
return new static(Str::title($this->value));
}
/**
* Get the singular form of an English word.
*
* @return static
*/
public function singular()
{
return new static(Str::singular($this->value));
}
/**
* Generate a URL friendly "slug" from a given string.
*
* @param string $separator
* @param null|string $language
* @return static
*/
public function slug($separator = '-', $language = 'en')
{
return new static(Str::slug($this->value, $separator, $language));
}
/**
* Convert a string to snake case.
*
* @param string $delimiter
* @return static
*/
public function snake($delimiter = '_')
{
return new static(Str::snake($this->value, $delimiter));
}
/**
* Determine if a given string starts with a given substring.
*
* @param array|string $needles
* @return bool
*/
public function startsWith($needles)
{
return Str::startsWith($this->value, $needles);
}
/**
* Convert a value to studly caps case.
*
* @return static
*/
public function studly()
{
return new static(Str::studly($this->value));
}
/**
* Returns the portion of the string specified by the start and length parameters.
*
* @param int $start
* @param null|int $length
* @return static
*/
public function substr($start, $length = null)
{
return new static(Str::substr($this->value, $start, $length));
}
/**
* Returns the number of substring occurrences.
*
* @param string $needle
* @param null|int $offset
* @param null|int $length
* @return int
*/
public function substrCount($needle, $offset = null, $length = null)
{
return Str::substrCount($this->value, $needle, $offset ?? 0, $length);
}
/**
* Trim the string of the given characters.
*
* @param string $characters
* @return static
*/
public function trim($characters = null)
{
return new static(trim(...array_merge([$this->value], func_get_args())));
}
/**
* Left trim the string of the given characters.
*
* @param string $characters
* @return static
*/
public function ltrim($characters = null)
{
return new static(ltrim(...array_merge([$this->value], func_get_args())));
}
/**
* Right trim the string of the given characters.
*
* @param string $characters
* @return static
*/
public function rtrim($characters = null)
{
return new static(rtrim(...array_merge([$this->value], func_get_args())));
}
/**
* Make a string's first character uppercase.
*
* @return static
*/
public function ucfirst()
{
return new static(Str::ucfirst($this->value));
}
/**
* Replaces the first or the last ones chars from a string by a given char.
*
* @param int $offset if is negative it starts from the end
* @param string $replacement default is *
* @return static
*/
public function mask(int $offset = 0, int $length = 0, string $replacement = '*')
{
return new static(Str::mask($this->value, $offset, $length, $replacement));
}
/**
* Execute the given callback if the string is empty.
*
* @param callable $callback
* @return static
*/
public function whenEmpty($callback)
{
if ($this->isEmpty()) {
$result = $callback($this);
return is_null($result) ? $this : $result;
}
return $this;
}
/**
* Execute the given callback if the string is not empty.
*
* @param callable $callback
* @return static
*/
public function whenNotEmpty($callback)
{
if ($this->isNotEmpty()) {
$result = $callback($this);
return is_null($result) ? $this : $result;
}
return $this;
}
/**
* Limit the number of words in a string.
*
* @param int $words
* @param string $end
* @return static
*/
public function words($words = 100, $end = '...')
{
return new static(Str::words($this->value, $words, $end));
}
/**
* Get the number of words a string contains.
*
* @return int
*/
public function wordCount()
{
return str_word_count($this->value);
}
/**
* Convert the object to a string when JSON encoded.
*
* @return string
*/
public function jsonSerialize()
{
return $this->__toString();
}
}

View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Traits;
trait Conditionable
{
/**
* Apply the callback if the given "value" is truthy.
*
* @param mixed $value
* @param callable $callback
* @param null|callable $default
*
* @return mixed
*/
public function when($value, $callback, $default = null)
{
if ($value) {
return $callback($this, $value) ?: $this;
}
if ($default) {
return $default($this, $value) ?: $this;
}
return $this;
}
/**
* Apply the callback if the given "value" is falsy.
*
* @param mixed $value
* @param callable $callback
* @param null|callable $default
*
* @return mixed
*/
public function unless($value, $callback, $default = null)
{
if (! $value) {
return $callback($this, $value) ?: $this;
}
if ($default) {
return $default($this, $value) ?: $this;
}
return $this;
}
}

View File

@@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Traits;
trait Container
{
/**
* @var array
*/
protected static $container = [];
/**
* Add a value to container by identifier.
* @param mixed $value
*/
public static function set(string $id, $value)
{
static::$container[$id] = $value;
}
/**
* Finds an entry of the container by its identifier and returns it,
* Retunrs $default when does not exists in the container.
* @param null|mixed $default
*/
public static function get(string $id, $default = null)
{
return static::$container[$id] ?? $default;
}
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*/
public static function has(string $id): bool
{
return isset(static::$container[$id]);
}
/**
* Returns the container.
*/
public static function list(): array
{
return static::$container;
}
/**
* Clear the container.
*/
public static function clear(): void
{
static::$container = [];
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Traits;
use Hyperf\Utils\Context;
trait CoroutineProxy
{
public function __call($name, $arguments)
{
$target = $this->getTargetObject();
return $target->{$name}(...$arguments);
}
public function __get($name)
{
$target = $this->getTargetObject();
return $target->{$name};
}
public function __set($name, $value)
{
$target = $this->getTargetObject();
return $target->{$name} = $value;
}
protected function getTargetObject()
{
if (! isset($this->proxyKey)) {
throw new \RuntimeException('$proxyKey property of class missing.');
}
return Context::get($this->proxyKey);
}
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Traits;
use BadMethodCallException;
use Error;
use function get_class;
trait ForwardsCalls
{
/**
* Forward a method call to the given object.
*
* @param mixed $object
* @throws Error
*/
protected function forwardCallTo($object, string $method, array $parameters)
{
try {
return $object->{$method}(...$parameters);
} catch (Error|BadMethodCallException $e) {
$pattern = '~^Call to undefined method (?P<class>[^:]+)::(?P<method>[^\(]+)\(\)$~';
if (! preg_match($pattern, $e->getMessage(), $matches)) {
throw $e;
}
if ($matches['class'] !== get_class($object) || $matches['method'] !== $method) {
throw $e;
}
static::throwBadMethodCallException($method);
}
}
/**
* Throw a bad method call exception for the given method.
* @throws BadMethodCallException
*/
protected static function throwBadMethodCallException(string $method): void
{
throw new BadMethodCallException(sprintf('Call to undefined method %s::%s()', static::class, $method));
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Traits;
/**
* @deprecated please use `Hyperf\Macroable\Macroable` instead
*/
trait Macroable
{
use \Hyperf\Macroable\Macroable;
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Traits;
use Hyperf\Utils\Context;
trait StaticInstance
{
protected $instanceKey;
/**
* @param array $params
* @param bool $refresh
* @return static
*/
public static function instance($params = [], $refresh = false)
{
$key = get_called_class();
$instance = null;
if (Context::has($key)) {
$instance = Context::get($key);
}
if ($refresh || is_null($instance) || ! $instance instanceof static) {
$instance = new static(...$params);
Context::set($key, $instance);
}
return $instance;
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\Traits;
trait Tappable
{
/**
* Call the given Closure with this instance then return the instance.
*
* @param null|callable $callback
* @return mixed
*/
public function tap($callback = null)
{
return tap($this, $callback);
}
}

View File

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

View File

@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils;
use Closure;
use Hyperf\Engine\Channel;
use Hyperf\Utils\Exception\ExceptionThrower;
use Hyperf\Utils\Exception\WaitTimeoutException;
use Throwable;
class Waiter
{
/**
* @var float
*/
protected $pushTimeout = 10.0;
/**
* @var float
*/
protected $popTimeout = 10.0;
public function __construct(float $timeout = 10.0)
{
$this->popTimeout = $timeout;
}
/**
* @param null|float $timeout seconds
*/
public function wait(Closure $closure, ?float $timeout = null)
{
if ($timeout === null) {
$timeout = $this->popTimeout;
}
$channel = new Channel(1);
Coroutine::create(function () use ($channel, $closure) {
try {
$result = $closure();
} catch (Throwable $exception) {
$result = new ExceptionThrower($exception);
} finally {
$channel->push($result ?? null, $this->pushTimeout);
}
});
$result = $channel->pop($timeout);
if ($result === false && $channel->isTimeout()) {
throw new WaitTimeoutException(sprintf('Channel wait failed, reason: Timed out for %s s', $timeout));
}
if ($result instanceof ExceptionThrower) {
throw $result->getThrowable();
}
return $result;
}
}