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

View File

@@ -0,0 +1,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;
}
}