.gitattributes000064400000000054151360552310007437 0ustar00/tests export-ignore /.github export-ignore LICENSE000064400000002110151360552310005544 0ustar00The MIT License (MIT) Copyright (c) Taylor Otwell Copyright (c) Hyperf Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. composer.json000064400000003235151360552310007272 0ustar00{ "name": "hyperf/utils", "description": "A tools package that could help developer solved the problem quickly.", "license": "MIT", "keywords": [ "php", "swoole", "hyperf", "utils" ], "homepage": "https://hyperf.io", "support": { "docs": "https://hyperf.wiki", "issues": "https://github.com/hyperf/hyperf/issues", "pull-request": "https://github.com/hyperf/hyperf/pulls", "source": "https://github.com/hyperf/hyperf" }, "require": { "php": ">=7.2", "doctrine/inflector": "^2.0", "hyperf/contract": "~3.0.0", "hyperf/coordinator": "~3.0.0", "hyperf/engine": "^1.2|^2.0", "hyperf/macroable": "~3.0.0" }, "suggest": { "ext-swoole": "Required to use methods related to swoole (>=4.5).", "symfony/var-dumper": "Required to use the dd function (^5.0).", "symfony/serializer": "Required to use SymfonyNormalizer (^5.0)", "symfony/property-access": "Required to use SymfonyNormalizer (^5.0)", "hyperf/di": "Required to use ExceptionNormalizer", "nikic/php-parser": "Required to use PhpParser. (^4.0)" }, "autoload": { "files": [ "src/Functions.php" ], "psr-4": { "Hyperf\\Utils\\": "src/" } }, "autoload-dev": { "psr-4": { "HyperfTest\\Utils\\": "tests/" } }, "config": { "sort-packages": true }, "extra": { "hyperf": { "config": "Hyperf\\Utils\\ConfigProvider" }, "branch-alias": { "dev-master": "3.0-dev" } } } src/ApplicationContext.php000064400000001452151360552310011657 0ustar00all(); } 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 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) || ! str_contains($key, '.')) { 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 $array * @param null|TKey $key * @param TValue $value * @return array */ 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 ($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 $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]; } } src/Backoff.php000064400000003344151360552310007404 0ustar00 Backoff::CAP) { throw new \InvalidArgumentException( sprintf( 'first backoff interval must be less or equal than %d milliseconds', self::CAP ) ); } $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; } } src/Channel/Caller.php000064400000003140151360552310010615 0ustar00initInstance(); } 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 { $this->channel?->close(); $this->channel = new Channel(1); $this->channel->push($this->closure->__invoke()); } } src/Channel/ChannelManager.php000064400000002450151360552310012261 0ustar00channels[$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); } } } src/ChannelPool.php000064400000001354151360552310010252 0ustar00isEmpty() ? new Channel(1) : $this->pop(); } public function release(Channel $channel) { $channel->errCode = 0; $this->push($channel); } } src/ClearStatCache.php000064400000002600151360552310010651 0ustar00 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; } } src/CodeGen/Package.php000064400000001060151360552310010701 0ustar00 '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', ]; protected static ?PhpDocReader $instance = null; private UseStatementParser $parser; /** * @param bool $ignorePhpDocErrors enable or disable throwing errors when PhpDoc errors occur (when parsing annotations) */ public function __construct(private bool $ignorePhpDocErrors = false) { $this->parser = new UseStatementParser(); } 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); } } src/CodeGen/PhpDocReaderManager.php000064400000001116151360552310013143 0ustar00parser = $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 getNodeFromReflectionType(ReflectionType $reflection): Node\ComplexType|Node\Identifier|Node\Name { 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); } } return new Node\UnionType($unionType); } return $this->getTypeWithNullableOrNot($reflection); } 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()) { $result->type = $this->getNodeFromReflectionType($parameter->getType()); } if ($parameter->isPassedByReference()) { $result->byRef = true; } if ($parameter->isVariadic()) { $result->variadic = true; } return $result; } public function getExprFromValue($value): Node\Expr { return match (gettype($value)) { 'array' => value(function ($value) { $result = []; foreach ($value as $item) { $result[] = new Node\Expr\ArrayItem($this->getExprFromValue($item)); } return new Node\Expr\Array_($result, [ 'kind' => Node\Expr\Array_::KIND_SHORT, ]); }, $value), 'string' => new Node\Scalar\String_($value), 'integer' => new Node\Scalar\LNumber($value), 'double' => new Node\Scalar\DNumber($value), 'NULL' => new Node\Expr\ConstFetch(new Node\Name('null')), 'boolean' => 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; } private function getTypeWithNullableOrNot(ReflectionType $reflection): Node\ComplexType|Node\Identifier|Node\Name { if (! $reflection instanceof \ReflectionNamedType) { throw new \ReflectionException('ReflectionType must be ReflectionNamedType.'); } $name = $reflection->getName(); if ($reflection->allowsNull() && $name !== 'mixed') { return new Node\NullableType($name); } if (! in_array($name, static::TYPES)) { return new Node\Name('\\' . $name); } return new Node\Identifier($name); } } src/CodeGen/Project.php000064400000003604151360552310010762 0ustar00getAutoloadRules() as $prefix => $prefixPath) { if ($this->isRootNamespace($prefix) || str_starts_with($path, $prefixPath)) { 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) || str_starts_with($name, $prefix)) { 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', []); } } src/Codec/Base62.php000064400000002012151360552310010077 0ustar00 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; }); } } src/Codec/Json.php000064400000002710151360552310007773 0ustar00toArray(); } 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): mixed { try { $decode = json_decode($json, $assoc, $depth, $flags | JSON_THROW_ON_ERROR); } catch (\Throwable $exception) { throw new InvalidArgumentException($exception->getMessage(), $exception->getCode()); } return $decode; } } src/Codec/Xml.php000064400000003317151360552310007626 0ustar00toArray(); } else { $data = (array) $data; } if ($parentNode === null) { $xml = new SimpleXMLElement('' . "<{$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): array { $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); } } src/Collection.php000064400000146057151360552310010155 0ustar00 * @implements Arrayable * @implements IteratorAggregate * * @property HigherOrderCollectionProxy $average * @property HigherOrderCollectionProxy $avg * @property HigherOrderCollectionProxy $contains * @property HigherOrderCollectionProxy $each * @property HigherOrderCollectionProxy $every * @property HigherOrderCollectionProxy $filter * @property HigherOrderCollectionProxy $first * @property HigherOrderCollectionProxy $flatMap * @property HigherOrderCollectionProxy $groupBy * @property HigherOrderCollectionProxy $keyBy * @property HigherOrderCollectionProxy $map * @property HigherOrderCollectionProxy $max * @property HigherOrderCollectionProxy $min * @property HigherOrderCollectionProxy $partition * @property HigherOrderCollectionProxy $reject * @property HigherOrderCollectionProxy $sortBy * @property HigherOrderCollectionProxy $sortByDesc * @property HigherOrderCollectionProxy $sum * @property HigherOrderCollectionProxy $unique */ class Collection implements ArrayAccess, Arrayable, Countable, IteratorAggregate, Jsonable, JsonSerializable { use Macroable; /** * The items contained in the collection. * * @var array */ protected array $items = []; /** * The methods that can be proxied. * * @var string[] */ protected static array $proxies = [ 'average', 'avg', 'contains', 'each', 'every', 'filter', 'first', 'flatMap', 'groupBy', 'keyBy', 'map', 'max', 'min', 'partition', 'reject', 'sortBy', 'sortByDesc', 'sum', 'unique', ]; /** * Create a new collection. * @param null|iterable|Jsonable|JsonSerializable $items */ public function __construct($items = []) { $this->items = $this->getArrayableItems($items); } /** * Convert the collection to its string representation. */ public function __toString(): string { return $this->toJson(); } /** * Dynamically access collection proxies. * * @throws \Exception */ public function __get(string $key) { if (! in_array($key, static::$proxies)) { throw new Exception("Property [{$key}] does not exist on this collection instance."); } return new HigherOrderCollectionProxy($this, $key); } /** * @param null|iterable|Jsonable|JsonSerializable $items * @return static */ public function fill($items = []) { $this->items = $this->getArrayableItems($items); return $this; } /** * Create a new collection instance if the value isn't one already. * * @template TMakeKey of array-key * @template TMakeValue * * @param null|Arrayable|iterable|Jsonable|JsonSerializable $items * @return static */ public static function make($items = []): self { return new static($items); } /** * Wrap the given value in a collection if applicable. * * @template TWrapKey of array-key * @template TWrapValue * * @param iterable $value * @return static */ public static function wrap($value): self { return $value instanceof self ? new static($value) : new static(Arr::wrap($value)); } /** * Get the underlying items from the given collection if applicable. * * @template TUnwrapKey of array-key * @template TUnwrapValue * * @param array|static $value * @return array */ public static function unwrap($value): array { return $value instanceof self ? $value->all() : $value; } /** * Create a new collection by invoking the callback a given amount of times. * * @template TTimesValue * * @param (callable(int): TTimesValue)|null $callback * @return static */ public static function times(int $number, callable $callback = null): self { if ($number < 1) { return new static(); } if (is_null($callback)) { return new static(range(1, $number)); } return (new static(range(1, $number)))->map($callback); } /** * Get all of the items in the collection. * * @return array */ public function all(): array { return $this->items; } /** * Get the average value of a given key. * * @param (callable(TValue): float|int)|string|null $callback */ public function avg($callback = null) { $callback = $this->valueRetriever($callback); $items = $this->map(function ($value) use ($callback) { return $callback($value); })->filter(function ($value) { return ! is_null($value); }); if ($count = $items->count()) { return $items->sum() / $count; } return null; } /** * Alias for the "avg" method. * * @param (callable(TValue): float|int)|string|null $callback * @return null|float|int */ public function average($callback = null) { return $this->avg($callback); } /** * Get the median of a given key. * * @param null|array|string $key */ public function median($key = null) { $values = (isset($key) ? $this->pluck($key) : $this)->filter(function ($item) { return ! is_null($item); })->sort()->values(); $count = $values->count(); if ($count == 0) { return; } $middle = (int) ($count / 2); if ($count % 2) { return $values->get($middle); } return (new static([ $values->get($middle - 1), $values->get($middle), ]))->average(); } /** * Get the mode of a given key. * * @param null|array|string $key * @return null|array */ public function mode($key = null) { if ($this->count() == 0) { return null; } $collection = isset($key) ? $this->pluck($key) : $this; /** * @template TValue of array-key * @var static $counts */ $counts = new self(); $collection->each(function ($value) use ($counts) { $counts[$value] = isset($counts[$value]) ? $counts[$value] + 1 : 1; }); $sorted = $counts->sort(); $highestValue = $sorted->last(); return $sorted->filter(function ($value) use ($highestValue) { return $value == $highestValue; })->sort()->keys()->all(); } /** * Collapse the collection of items into a single array. * * @return static */ public function collapse(): self { return new static(Arr::collapse($this->items)); } /** * Determine if an item exists in the collection. * * @param null|mixed $operator * @param null|mixed $value * @param (callable(TValue): bool)|TValue|string $key */ public function contains($key, $operator = null, $value = null): bool { if (func_num_args() === 1) { if ($this->useAsCallable($key)) { $placeholder = new stdClass(); return $this->first($key, $placeholder) !== $placeholder; } return in_array($key, $this->items); } return $this->contains($this->operatorForWhere(...func_get_args())); } /** * Determine if an item exists in the collection using strict comparison. * * @param null|TValue $value * @param callable|TKey|TValue $key */ public function containsStrict($key, $value = null): bool { if (func_num_args() === 2) { return $this->contains(function ($item) use ($key, $value) { return data_get($item, $key) === $value; }); } if ($this->useAsCallable($key)) { return ! is_null($this->first($key)); } return in_array($key, $this->items, true); } /** * Cross join with the given lists, returning all possible permutations. */ public function crossJoin(...$lists): self { return new static(Arr::crossJoin($this->items, ...array_map([$this, 'getArrayableItems'], $lists))); } /** * Dump the collection and end the script. */ public function dd(...$args): void { call_user_func_array([$this, 'dump'], $args); exit(1); } /** * Dump the collection. */ public function dump(): self { $params = (new static(func_get_args())); $params->push($this)->each(function ($item) { if (! class_exists(VarDumper::class)) { throw new \RuntimeException('symfony/var-dumper package required, please require the package via "composer require symfony/var-dumper"'); } VarDumper::dump($item); }); return $this; } /** * Get the items in the collection that are not present in the given items. * * @param Arrayable|iterable $items * @return static */ public function diff($items): self { return new static(array_diff($this->items, $this->getArrayableItems($items))); } /** * Get the items in the collection that are not present in the given items. * * @param Arrayable|iterable $items * @param callable(TValue): int $callback * @return static */ public function diffUsing($items, callable $callback): self { return new static(array_udiff($this->items, $this->getArrayableItems($items), $callback)); } /** * Get the items in the collection whose keys and values are not present in the given items. * * @param Arrayable|iterable $items * @return static */ public function diffAssoc($items): self { return new static(array_diff_assoc($this->items, $this->getArrayableItems($items))); } /** * Get the items in the collection whose keys and values are not present in the given items. * * @param Arrayable|iterable $items * @param callable(TKey): int $callback * @return static */ public function diffAssocUsing($items, callable $callback): self { return new static(array_diff_uassoc($this->items, $this->getArrayableItems($items), $callback)); } /** * Get the items in the collection whose keys are not present in the given items. * * @param Arrayable|iterable $items * @return static */ public function diffKeys($items): self { return new static(array_diff_key($this->items, $this->getArrayableItems($items))); } /** * Get the items in the collection whose keys are not present in the given items. * * @param Arrayable|iterable $items * @param callable(TKey): int $callback * @return static */ public function diffKeysUsing($items, callable $callback): self { return new static(array_diff_ukey($this->items, $this->getArrayableItems($items), $callback)); } /** * Execute a callback over each item. * @param callable(TValue,TKey): mixed $callback */ public function each(callable $callback): self { foreach ($this->items as $key => $item) { if ($callback($item, $key) === false) { break; } } return $this; } /** * Execute a callback over each nested chunk of items. * @param callable(...mixed): mixed $callback * @return static */ public function eachSpread(callable $callback): self { return $this->each(function ($chunk, $key) use ($callback) { $chunk[] = $key; return $callback(...$chunk); }); } /** * Determine if all items in the collection pass the given test. * * @param (callable(TValue, TKey): bool)|TValue|string $key * @param mixed $operator * @param mixed $value */ public function every($key, $operator = null, $value = null): bool { if (func_num_args() === 1) { $callback = $this->valueRetriever($key); foreach ($this->items as $k => $v) { if (! $callback($v, $k)) { return false; } } return true; } return $this->every($this->operatorForWhere(...func_get_args())); } /** * Get all items except for those with the specified keys. * * @param array|static $keys * @return static */ public function except($keys): self { if ($keys instanceof self) { $keys = $keys->all(); } elseif (! is_array($keys)) { $keys = func_get_args(); } return new static(Arr::except($this->items, $keys)); } /** * Run a filter over each of the items. * * @param callable(TValue, TKey): bool|null $callback * @return static */ public function filter(callable $callback = null): self { if ($callback) { return new static(Arr::where($this->items, $callback)); } return new static(array_filter($this->items)); } /** * Apply the callback if the value is truthy. * * @param callable($this): $this $callback * @param callable($this): $this $default * @return $this */ public function when(bool $value, callable $callback, callable $default = null): self { if ($value) { return $callback($this, $value); } if ($default) { return $default($this, $value); } return $this; } /** * Apply the callback if the value is falsy. * * @param callable($this): $this $callback * @param callable($this): $this|null $default * @return $this */ public function unless(bool $value, callable $callback, callable $default = null): self { return $this->when(! $value, $callback, $default); } /** * Filter items by the given key value pair. * * @param mixed $operator * @param mixed $value * @return static */ public function where(string $key, $operator = null, $value = null): self { return $this->filter($this->operatorForWhere(...func_get_args())); } /** * Filter items by the given key value pair using strict comparison. * * @param mixed $value * @return static */ public function whereStrict(string $key, $value): self { return $this->where($key, '===', $value); } /** * Filter items by the given key value pair. * * @param Arrayable|iterable $values * @return static */ public function whereIn(string $key, $values, bool $strict = false): self { $values = $this->getArrayableItems($values); return $this->filter(function ($item) use ($key, $values, $strict) { return in_array(data_get($item, $key), $values, $strict); }); } /** * Filter items by the given key value pair using strict comparison. * * @param Arrayable|iterable $values * @return static */ public function whereInStrict(string $key, $values): self { return $this->whereIn($key, $values, true); } /** * Filter items by the given key value pair. * * @param Arrayable|iterable $values * @return static */ public function whereNotIn(string $key, $values, bool $strict = false): self { $values = $this->getArrayableItems($values); return $this->reject(function ($item) use ($key, $values, $strict) { return in_array(data_get($item, $key), $values, $strict); }); } /** * Filter items by the given key value pair using strict comparison. * * @param Arrayable|iterable $values * @return static */ public function whereNotInStrict(string $key, $values): self { return $this->whereNotIn($key, $values, true); } /** * Filter the items, removing any items that don't match the given type. * * @param class-string $type * @return static */ public function whereInstanceOf(string $type): self { return $this->filter(function ($value) use ($type) { return $value instanceof $type; }); } /** * Get the first item from the collection. * * @template TFirstDefault * * @param callable(TValue, TKey): bool|null $callback * @param TFirstDefault|callable(): TFirstDefault $default * @return TFirstDefault|TValue */ public function first(callable $callback = null, $default = null) { return Arr::first($this->items, $callback, $default); } /** * Get the first item by the given key value pair. * * @param mixed $operator * @param mixed $value * @return null|TValue */ public function firstWhere(string $key, $operator, $value = null) { return $this->first($this->operatorForWhere(...func_get_args())); } /** * Get a flattened array of the items in the collection. * * @param float|int $depth * @return static */ public function flatten($depth = INF): self { return new static(Arr::flatten($this->items, $depth)); } /** * Flip the items in the collection. * * @return static */ public function flip(): self { return new static(array_flip($this->items)); } /** * Remove an item from the collection by key. * * @param array|TKey $keys * @return $this */ public function forget($keys): self { foreach ((array) $keys as $key) { $this->offsetUnset($key); } return $this; } /** * Get an item from the collection by key. * * @template TGetDefault * * @param TKey $key * @param TGetDefault|(\Closure(): TGetDefault) $default * @return TGetDefault|TValue */ public function get($key, $default = null) { if ($this->offsetExists($key)) { return $this->items[$key]; } return value($default); } /** * Group an associative array by a field or using a callback. * @param mixed $groupBy */ public function groupBy($groupBy, bool $preserveKeys = false): self { if (is_array($groupBy)) { $nextGroups = $groupBy; $groupBy = array_shift($nextGroups); } $groupBy = $this->valueRetriever($groupBy); $results = []; foreach ($this->items as $key => $value) { $groupKeys = $groupBy($value, $key); if (! is_array($groupKeys)) { $groupKeys = [$groupKeys]; } foreach ($groupKeys as $groupKey) { $groupKey = is_bool($groupKey) ? (int) $groupKey : $groupKey; if (! array_key_exists($groupKey, $results)) { $results[$groupKey] = new static(); } $results[$groupKey]->offsetSet($preserveKeys ? $key : null, $value); } } $result = new static($results); if (! empty($nextGroups)) { return $result->map->groupBy($nextGroups, $preserveKeys); } return $result; } /** * Key an associative array by a field or using a callback. * * @param (callable(TValue, TKey): array-key)|array|string $keyBy * @return static> */ public function keyBy($keyBy): self { $keyBy = $this->valueRetriever($keyBy); $results = []; foreach ($this->items as $key => $item) { $resolvedKey = $keyBy($item, $key); if (is_object($resolvedKey)) { $resolvedKey = (string) $resolvedKey; } $results[$resolvedKey] = $item; } return new static($results); } /** * Determine if an item exists in the collection by key. * @param array|TKey $key */ public function has($key): bool { $keys = is_array($key) ? $key : func_get_args(); foreach ($keys as $value) { if (! $this->offsetExists($value)) { return false; } } return true; } /** * Concatenate values of a given key as a string. */ public function implode(string $value, string $glue = null): string { $first = $this->first(); if (is_array($first) || is_object($first)) { return implode($glue, $this->pluck($value)->all()); } return implode($value, $this->items); } /** * Intersect the collection with the given items. * * @param Arrayable|iterable $items * @return static */ public function intersect($items): self { return new static(array_intersect($this->items, $this->getArrayableItems($items))); } /** * Intersect the collection with the given items by key. * @param Arrayable|iterable $items * @return static */ public function intersectByKeys($items): self { return new static(array_intersect_key($this->items, $this->getArrayableItems($items))); } /** * Determine if the collection is empty or not. */ public function isEmpty(): bool { return empty($this->items); } /** * Determine if the collection is not empty. */ public function isNotEmpty(): bool { return ! $this->isEmpty(); } /** * Get the keys of the collection items. * @return static */ public function keys(): self { return new static(array_keys($this->items)); } /** * Get the last item from the collection. * * @template TLastDefault * * @param (callable(TValue, TKey): bool)|null $callback * @param TLastDefault|(\Closure(): TLastDefault) $default * @return TLastDefault|TValue */ public function last(callable $callback = null, $default = null) { return Arr::last($this->items, $callback, $default); } /** * Get the values of a given key. * * @param array|string $value * @return static */ public function pluck($value, ?string $key = null): self { return new static(Arr::pluck($this->items, $value, $key)); } /** * Run a map over each of the items. * * @template TMapValue * * @param callable(TValue, TKey): TMapValue $callback * @return static */ public function map(callable $callback): self { $keys = array_keys($this->items); $items = array_map($callback, $this->items, $keys); return new static(array_combine($keys, $items)); } /** * Run a map over each nested chunk of items. * * @template TMapSpreadValue * * @param callable(mixed): TMapSpreadValue $callback * @return static */ public function mapSpread(callable $callback): self { return $this->map(function ($chunk, $key) use ($callback) { $chunk[] = $key; return $callback(...$chunk); }); } /** * Run a dictionary map over the items. * The callback should return an associative array with a single key/value pair. * * @template TMapToDictionaryKey of array-key * @template TMapToDictionaryValue * * @param callable(TValue, TKey): array $callback * @return static> */ public function mapToDictionary(callable $callback): self { $dictionary = []; foreach ($this->items as $key => $item) { $pair = $callback($item, $key); $key = key($pair); $value = reset($pair); if (! isset($dictionary[$key])) { $dictionary[$key] = []; } $dictionary[$key][] = $value; } return new static($dictionary); } /** * Run a grouping map over the items. * The callback should return an associative array with a single key/value pair. */ public function mapToGroups(callable $callback): self { $groups = $this->mapToDictionary($callback); return $groups->map([$this, 'make']); } /** * Run an associative map over each of the items. * The callback should return an associative array with a single key/value pair. * * @template TMapWithKeysKey of array-key * @template TMapWithKeysValue * * @param callable(TValue, TKey): array $callback * @return static */ public function mapWithKeys(callable $callback): self { $result = []; foreach ($this->items as $key => $value) { $assoc = $callback($value, $key); foreach ($assoc as $mapKey => $mapValue) { $result[$mapKey] = $mapValue; } } return new static($result); } /** * Map a collection and flatten the result by a single level. * * @param callable(TValue, TKey): mixed $callback * @return static */ public function flatMap(callable $callback): self { return $this->map($callback)->collapse(); } /** * Map the values into a new class. * * @param class-string $class * @return static */ public function mapInto(string $class): self { return $this->map(function ($value, $key) use ($class) { return new $class($value, $key); }); } /** * Get the max value of a given key. * * @param (callable(TValue):mixed)|string|null $callback * @return TValue */ public function max($callback = null) { $callback = $this->valueRetriever($callback); return $this->filter(function ($value) { return ! is_null($value); })->reduce(function ($result, $item) use ($callback) { $value = $callback($item); return is_null($result) || $value > $result ? $value : $result; }); } /** * Merge the collection with the given items. * @param Arrayable|iterable $items * @return static */ public function merge($items): self { return new static(array_merge($this->items, $this->getArrayableItems($items))); } /** * Create a collection by using this collection for keys and another for its values. * * @template TCombineValue * * @param Arrayable|iterable $values * @return static */ public function combine($values): self { return new static(array_combine($this->all(), $this->getArrayableItems($values))); } /** * Union the collection with the given items. * * @param Arrayable|iterable $items * @return static */ public function union($items): self { return new static($this->items + $this->getArrayableItems($items)); } /** * Get the min value of a given key. * * @param (callable(TValue):mixed)|string|null $callback * @return TValue */ public function min($callback = null) { $callback = $this->valueRetriever($callback); return $this->map(function ($value) use ($callback) { return $callback($value); })->filter(function ($value) { return ! is_null($value); })->reduce(function ($result, $value) { return is_null($result) || $value < $result ? $value : $result; }); } /** * Create a new collection consisting of every n-th element. * * @return static */ public function nth(int $step, int $offset = 0): self { $new = []; $position = 0; foreach ($this->items as $item) { if ($position % $step === $offset) { $new[] = $item; } ++$position; } return new static($new); } /** * Get the items with the specified keys. * * @param null|array|static|string $keys * @return static */ public function only($keys): self { if (is_null($keys)) { return new static($this->items); } if ($keys instanceof self) { $keys = $keys->all(); } $keys = is_array($keys) ? $keys : func_get_args(); return new static(Arr::only($this->items, $keys)); } /** * "Paginate" the collection by slicing it into a smaller collection. */ public function forPage(int $page, int $perPage): self { $offset = max(0, ($page - 1) * $perPage); return $this->slice($offset, $perPage); } /** * Partition the collection into two arrays using the given callback or key. * * @param callable(TValue, TKey) bool)|TValue|string $key * @param null|string|TValue $operator * @param null|TValue $value * @return static> */ public function partition($key, $operator = null, $value = null): self { $partitions = [new static(), new static()]; $callback = func_num_args() === 1 ? $this->valueRetriever($key) : $this->operatorForWhere(...func_get_args()); foreach ($this->items as $key => $item) { $partitions[(int) ! $callback($item, $key)][$key] = $item; } return new static($partitions); } /** * Pass the collection to the given callback and return the result. * * @template TPipeReturnType * * @param callable($this): TPipeReturnType $callback * @return TPipeReturnType */ public function pipe(callable $callback) { return $callback($this); } /** * Get and remove the last item from the collection. */ public function pop() { return array_pop($this->items); } /** * Push an item onto the beginning of the collection. * * @param TValue $value * @param null|TKey $key * @return $this */ public function prepend($value, $key = null): self { $this->items = Arr::prepend($this->items, $value, $key); return $this; } /** * Push an item onto the end of the collection. * * @param TValue $value * @return $this */ public function push($value): self { $this->offsetSet(null, $value); return $this; } /** * Push all of the given items onto the collection. * * @param iterable $source * @return static */ public function concat($source): self { $result = new static($this); foreach ($source as $item) { $result->push($item); } return $result; } /** * Get and remove an item from the collection. * * @template TPullDefault * * @param TKey $key * @param TPullDefault|(\Closure(): TPullDefault) $default * @return TPullDefault|TValue */ public function pull($key, $default = null) { return Arr::pull($this->items, $key, $default); } /** * Put an item in the collection by key. * * @param TKey $key * @param TValue $value * @return $this */ public function put($key, $value): self { $this->offsetSet($key, $value); return $this; } /** * Get one or a specified number of items randomly from the collection. * * @throws \InvalidArgumentException * @return static|TValue */ public function random(int $number = null) { if (is_null($number)) { return Arr::random($this->items); } return new static(Arr::random($this->items, $number)); } /** * Reduce the collection to a single value. * * @template TReduceInitial * @template TReduceReturnType * * @param callable(TReduceInitial|TReduceReturnType, TValue): TReduceReturnType $callback * @param TReduceInitial $initial * @return TReduceInitial|TReduceReturnType */ public function reduce(callable $callback, $initial = null) { return array_reduce($this->items, $callback, $initial); } /** * Create a collection of all elements that do not pass a given truth test. * * @param callable(TValue, TKey): bool|bool $callback * @return static */ public function reject($callback): self { if ($this->useAsCallable($callback)) { return $this->filter(function ($value, $key) use ($callback) { return ! $callback($value, $key); }); } return $this->filter(function ($item) use ($callback) { return $item != $callback; }); } /** * Reverse items order. * * @return static */ public function reverse(): self { return new static(array_reverse($this->items, true)); } /** * Search the collection for a given value and return the corresponding key if successful. * * @param TValue|(callable(TValue,TKey): bool) $value * @return bool|TKey */ public function search($value, bool $strict = false) { if (! $this->useAsCallable($value)) { return array_search($value, $this->items, $strict); } foreach ($this->items as $key => $item) { if (call_user_func($value, $item, $key)) { return $key; } } return false; } /** * Get and remove the first item from the collection. * * @return null|TValue */ public function shift() { return array_shift($this->items); } /** * Shuffle the items in the collection. * * @return static */ public function shuffle(int $seed = null): self { return new static(Arr::shuffle($this->items, $seed)); } /** * Slice the underlying collection array. * * @return static */ public function slice(int $offset, int $length = null): self { return new static(array_slice($this->items, $offset, $length, true)); } /** * Split a collection into a certain number of groups. * * @return static> */ public function split(int $numberOfGroups): self { if ($this->isEmpty()) { return new static(); } $groups = new static(); $groupSize = (int) floor($this->count() / $numberOfGroups); $remain = $this->count() % $numberOfGroups; $start = 0; for ($i = 0; $i < $numberOfGroups; ++$i) { $size = $groupSize; if ($i < $remain) { ++$size; } if ($size) { $groups->push(new static(array_slice($this->items, $start, $size))); $start += $size; } } return $groups; } /** * Chunk the underlying collection array. * * @return static> */ public function chunk(int $size): self { if ($size <= 0) { return new static(); } $chunks = []; foreach (array_chunk($this->items, $size, true) as $chunk) { $chunks[] = new static($chunk); } return new static($chunks); } /** * Sort through each item with a callback. * * @param callable(TValue, TValue): int $callback * @return static */ public function sort(callable $callback = null): self { $items = $this->items; $callback ? uasort($items, $callback) : asort($items); return new static($items); } /** * Sort the collection using the given callback. * * @param (callable(TValue, TKey): mixed)|string|array $callback * @return static */ public function sortBy($callback, int $options = SORT_REGULAR, bool $descending = false): self { if (is_array($callback) && ! is_callable($callback)) { return $this->sortByMany($callback); } $results = []; $callback = $this->valueRetriever($callback); // First we will loop through the items and get the comparator from a callback // function which we were given. Then, we will sort the returned values and // and grab the corresponding values for the sorted keys from this array. foreach ($this->items as $key => $value) { $results[$key] = $callback($value, $key); } $descending ? arsort($results, $options) : asort($results, $options); // Once we have sorted all of the keys in the array, we will loop through them // and grab the corresponding model so we can set the underlying items list // to the sorted version. Then we'll just return the collection instance. foreach (array_keys($results) as $key) { $results[$key] = $this->items[$key]; } return new static($results); } /** * Sort the collection in descending order using the given callback. * * @param (callable(TValue, TKey): mixed)|string $callback * @return static */ public function sortByDesc($callback, int $options = SORT_REGULAR): self { return $this->sortBy($callback, $options, true); } /** * Sort the collection keys. * * @return static */ public function sortKeys(int $options = SORT_REGULAR, bool $descending = false): self { $items = $this->items; $descending ? krsort($items, $options) : ksort($items, $options); return new static($items); } /** * Sort the collection keys in descending order. * * @return static */ public function sortKeysDesc(int $options = SORT_REGULAR): self { return $this->sortKeys($options, true); } /** * Splice a portion of the underlying collection array. * * @param array $replacement * @return static */ public function splice(int $offset, int $length = null, $replacement = []): self { if (func_num_args() === 1) { return new static(array_splice($this->items, $offset)); } return new static(array_splice($this->items, $offset, $length, $replacement)); } /** * Get the sum of the given values. * * @param (callable(TValue): mixed)|string|null $callback * @return mixed */ public function sum($callback = null) { if (is_null($callback)) { return array_sum($this->items); } $callback = $this->valueRetriever($callback); return $this->reduce(function ($result, $item) use ($callback) { return $result + $callback($item); }, 0); } /** * Take the first or last {$limit} items. * * @return static */ public function take(int $limit): self { if ($limit < 0) { return $this->slice($limit, abs($limit)); } return $this->slice(0, $limit); } /** * Pass the collection to the given callback and then return it. * * @param callable(static): mixed $callback * @return $this */ public function tap(callable $callback): self { $callback(new static($this->items)); return $this; } /** * Transform each item in the collection using a callback. * * @param callable(TValue, TKey): TValue $callback * @return $this */ public function transform(callable $callback): self { $this->items = $this->map($callback)->all(); return $this; } /** * Return only unique items from the collection array. * * @param (callable(TValue, TKey): bool)|string|null $key * @return static */ public function unique($key = null, bool $strict = false): self { $callback = $this->valueRetriever($key); $exists = []; return $this->reject(function ($item, $key) use ($callback, $strict, &$exists) { if (in_array($id = $callback($item, $key), $exists, $strict)) { return true; } $exists[] = $id; }); } /** * Return only unique items from the collection array using strict comparison. * * @param (callable(TValue, TKey): bool)|string|null $key * @return static */ public function uniqueStrict($key = null): self { return $this->unique($key, true); } /** * Reset the keys on the underlying array. * * @return static */ public function values(): self { return new static(array_values($this->items)); } /** * Zip the collection together with one or more arrays. * e.g. new Collection([1, 2, 3])->zip([4, 5, 6]); * => [[1, 4], [2, 5], [3, 6]]. * * @template TZipValue * * @param Arrayable|iterable ...$items * @return static> */ public function zip($items): self { $arrayableItems = array_map(function ($items) { return $this->getArrayableItems($items); }, func_get_args()); $params = array_merge([ function () { return new static(func_get_args()); }, $this->items, ], $arrayableItems); return new static(call_user_func_array('array_map', $params)); } /** * Pad collection to the specified length with a value. * * @template TPadValue * * @param TPadValue $value * @return static */ public function pad(int $size, $value): self { return new static(array_pad($this->items, $size, $value)); } /** * Get the collection of items as a plain array. * * @return array */ public function toArray(): array { return array_map(function ($value) { return $value instanceof Arrayable ? $value->toArray() : $value; }, $this->items); } /** * Convert the object into something JSON serializable. * * @return array */ public function jsonSerialize(): mixed { return array_map(function ($value) { if ($value instanceof JsonSerializable) { return $value->jsonSerialize(); } if ($value instanceof Jsonable) { return json_decode($value->__toString(), true); } if ($value instanceof Arrayable) { return $value->toArray(); } return $value; }, $this->items); } /** * Get the collection of items as JSON. */ public function toJson(int $options = 0): string { return json_encode($this->jsonSerialize(), $options); } /** * Get an iterator for the items. * * @return ArrayIterator */ public function getIterator(): ArrayIterator { return new ArrayIterator($this->items); } /** * Get a CachingIterator instance. */ public function getCachingIterator(int $flags = CachingIterator::CALL_TOSTRING): CachingIterator { return new CachingIterator($this->getIterator(), $flags); } /** * Count the number of items in the collection. */ public function count(): int { return count($this->items); } /** * Get a base Support collection instance from this collection. * * @return Collection */ public function toBase() { return new self($this); } /** * Determine if an item exists at an offset. * * @param TKey $offset */ public function offsetExists(mixed $offset): bool { return array_key_exists($offset, $this->items); } /** * Get an item at a given offset. * * @param TKey $offset * @return TValue */ public function offsetGet(mixed $offset): mixed { return $this->items[$offset]; } /** * Set the item at a given offset. * * @param null|TKey $offset * @param TValue $value */ public function offsetSet(mixed $offset, mixed $value): void { if (is_null($offset)) { $this->items[] = $value; } else { $this->items[$offset] = $value; } } /** * Unset the item at a given offset. * * @param TKey $offset */ public function offsetUnset(mixed $offset): void { unset($this->items[$offset]); } /** * Add a method to the list of proxied methods. */ public static function proxy(string $method): void { static::$proxies[] = $method; } /** * Sort the collection using multiple comparisons. * * @return static */ protected function sortByMany(array $comparisons = []) { $items = $this->items; usort($items, function ($a, $b) use ($comparisons) { foreach ($comparisons as $comparison) { $comparison = Arr::wrap($comparison); $prop = $comparison[0]; $ascending = Arr::get($comparison, 1, true) === true || Arr::get($comparison, 1, true) === 'asc'; $result = 0; if (! is_string($prop) && is_callable($prop)) { $result = $prop($a, $b); } else { $values = [data_get($a, $prop), data_get($b, $prop)]; if (! $ascending) { $values = array_reverse($values); } $result = $values[0] <=> $values[1]; } if ($result === 0) { continue; } return $result; } }); return new static($items); } /** * Get an operator checker callback. * @param mixed|string $operator * @param null|TValue $value */ protected function operatorForWhere(string $key, $operator = null, $value = null): \Closure { if (func_num_args() === 1) { $value = true; $operator = '='; } if (func_num_args() === 2) { $value = $operator; $operator = '='; } return function ($item) use ($key, $operator, $value) { $retrieved = data_get($item, $key); $strings = array_filter([$retrieved, $value], function ($value) { return is_string($value) || (is_object($value) && method_exists($value, '__toString')); }); if (count($strings) < 2 && count(array_filter([$retrieved, $value], 'is_object')) == 1) { return in_array($operator, ['!=', '<>', '!==']); } switch ($operator) { default: case '=': case '==': return $retrieved == $value; case '!=': case '<>': return $retrieved != $value; case '<': return $retrieved < $value; case '>': return $retrieved > $value; case '<=': return $retrieved <= $value; case '>=': return $retrieved >= $value; case '===': return $retrieved === $value; case '!==': return $retrieved !== $value; } }; } /** * Determine if the given value is callable, but not a string. * @param mixed $value */ protected function useAsCallable($value): bool { return ! is_string($value) && is_callable($value); } /** * Get a value retrieving callback. * @param mixed $value */ protected function valueRetriever($value): callable { if ($this->useAsCallable($value)) { return $value; } return function ($item) use ($value) { return data_get($item, $value); }; } /** * Results array of items from Collection or Arrayable. * @param null|Arrayable|iterable|Jsonable|JsonSerializable|static $items * @return array */ protected function getArrayableItems($items): array { if (is_array($items)) { return $items; } if ($items instanceof self) { return $items->all(); } if ($items instanceof Arrayable) { return $items->toArray(); } if ($items instanceof Jsonable) { return json_decode($items->__toString(), true); } if ($items instanceof JsonSerializable) { return $items->jsonSerialize(); } if ($items instanceof Traversable) { return iterator_to_array($items); } return (array) $items; } } src/Composer.php000064400000010716151360552310007641 0ustar00offsetGet('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; } public static function getScripts(): array { return self::$scripts; } public static function getVersions(): array { return self::$versions; } private static function findLoader(): ClassLoader { $composerClass = ''; foreach (get_declared_classes() as $declaredClass) { if (str_starts_with($declaredClass, 'ComposerAutoloaderInit') && method_exists($declaredClass, 'getLoader')) { $composerClass = $declaredClass; break; } } if (! $composerClass) { throw new \RuntimeException('Composer loader not found.'); } return $composerClass::getLoader(); } } src/ConfigProvider.php000064400000001606151360552310010770 0ustar00 [ Serializer::class => SerializerFactory::class, NormalizerInterface::class => SimpleNormalizer::class, ], 'annotations' => [ 'scan' => [ 'paths' => [ __DIR__, ], ], ], ]; } } src/Context.php000064400000004775151360552310007506 0ustar00exchangeArray($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; } } src/Contracts/Arrayable.php000064400000000566151360552310011716 0ustar00has(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) { return -1; } } public static function inCoroutine(): bool { return Co::id() > 0; } } src/Coroutine/Concurrent.php000064400000004307151360552310012142 0ustar00channel = new Channel($limit); } public function __call($name, $arguments) { if (in_array($name, ['isFull', 'isEmpty'])) { return $this->channel->{$name}(...$arguments); } throw new InvalidArgumentException(sprintf('The method %s is not supported.', $name)); } 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 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(); } }); } } src/Coroutine/Locker.php000064400000002241151360552310011232 0ustar00 0) { SwooleCoroutine::resume($id); } } self::clear($key); } } } src/Exception/ChannelClosedException.php000064400000000514151360552310014364 0ustar00throwable; } } src/Exception/IPReadFailedException.php000064400000000513151360552310014072 0ustar00results; } public function setResults(array $results) { $this->results = $results; } public function getThrowables(): array { return $this->throwables; } public function setThrowables(array $throwables) { return $this->throwables = $throwables; } } src/Exception/TimeoutException.php000064400000000506151360552310013311 0ustar00isFile($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. */ public function requireOnce(string $file): void { require_once $file; } /** * Get the MD5 hash of the file at the given path. */ public function hash(string $path): string { return md5_file($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) { $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): bool { if (! $this->windowsOs()) { return symlink($target, $link); } $mode = $this->isDirectory($target) ? 'J' : 'H'; exec("mklink /{$mode} \"{$link}\" \"{$target}\""); return true; } /** * 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); } } } src/Fluent.php000075500000007526151360552310007317 0ustar00 $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. */ public function jsonSerialize(): mixed { 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. */ public function offsetExists(mixed $offset): bool { return isset($this->attributes[$offset]); } /** * Get the value for a given offset. */ public function offsetGet(mixed $offset): mixed { return $this->get($offset); } /** * Set the value at the given offset. */ public function offsetSet(mixed $offset, mixed $value): void { $this->attributes[$offset] = $value; } /** * Unset the value at the given offset. */ public function offsetUnset(mixed $offset): void { unset($this->attributes[$offset]); } } src/Functions.php000064400000031447151360552310010026 0ustar00 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 unlimited */ 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); } return null; } } 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); } } src/HigherOrderCollectionProxy.php000064400000002460151360552310013327 0ustar00collection->{$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); }); } } src/HigherOrderTapProxy.php000064400000001377151360552310011766 0ustar00target->{$method}(...$parameters); return $this->target; } } src/IPReader.php000064400000001153151360552310007500 0ustar00getMessage()); } } } src/InteractsWithTime.php000064400000003355151360552310011462 0ustar00parseDateInterval($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(); } } src/MessageBag.php000064400000020537151360552310010052 0ustar00 $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 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 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 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 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(): mixed { 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 = $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 placeholder 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; } } src/MimeTypeExtensionGuesser.php000064400000107363151360552310013043 0ustar00 '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 $this->defaultExtensions[$mimeType] ?? null; } public function guessMimeType(string $extension): ?string { return $this->defaultMineTypes[$extension] ?? null; } } src/Network.php000064400000002400151360552310007472 0ustar00 $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); } $name = gethostname(); if ($name === false) { throw new RuntimeException('Can not get the internal IP.'); } return gethostbyname($name); } } src/Optional.php000064400000004674151360552310007645 0ustar00value)) { 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); } return null; } /** * Determine if an item exists at an offset. */ public function offsetExists(mixed $offset): bool { return Arr::accessible($this->value) && Arr::exists($this->value, $offset); } /** * Get an item at a given offset. */ public function offsetGet(mixed $offset): mixed { return Arr::get($this->value, $offset); } /** * Set the item at a given offset. */ public function offsetSet(mixed $offset, mixed $value): void { if (Arr::accessible($this->value)) { $this->value[$offset] = $value; } } /** * Unset the item at a given offset. */ public function offsetUnset(mixed $offset): void { if (Arr::accessible($this->value)) { unset($this->value[$offset]); } } } src/Packer/JsonPacker.php000064400000001062151360552310011310 0ustar00 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 { $result = $throwables = []; $wg = new WaitGroup(); $wg->add(count($this->callbacks)); foreach ($this->callbacks as $key => $callback) { $this->concurrentChannel && $this->concurrentChannel->push(true); $result[$key] = null; Coroutine::create(function () use ($callback, $key, $wg, &$result, &$throwables) { try { $result[$key] = call($callback); } catch (\Throwable $throwable) { $throwables[$key] = $throwable; unset($result[$key]); } finally { $this->concurrentChannel && $this->concurrentChannel->pop(); $wg->done(); } }); } $wg->wait(); if ($throw && ($throwableCount = count($throwables)) > 0) { $message = 'Detecting ' . $throwableCount . ' throwable occurred during parallel execution:' . PHP_EOL . $this->formatThrowables($throwables); $executionException = new ParallelExecutionException($message); $executionException->setResults($result); $executionException->setThrowables($throwables); throw $executionException; } return $result; } public function count(): int { return count($this->callbacks); } public function clear(): void { $this->callbacks = []; } /** * 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; } } src/Pipeline.php000064400000010453151360552310007615 0ustar00passable = $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; } } src/Pluralizer.php000064400000007172151360552310010205 0ustar00pluralize($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; } } src/Reflection/ClassInvoker.php000064400000001622151360552310012543 0ustar00reflection = 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); } } src/ResourceGenerator.php000064400000001407151360552310011505 0ustar00getInstantiator()->instantiate($type); foreach (['code', 'message', 'file', 'line'] as $attribute) { if (isset($data[$attribute])) { $property = ReflectionManager::reflectProperty($type, $attribute); $property->setAccessible(true); $property->setValue($exception, $data[$attribute]); } } return $exception; } catch (\ReflectionException) { return new \RuntimeException(sprintf( 'Bad data %s: %s', $data['class'], $data['message'] ), $data['code']); } catch (\TypeError) { 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(); } } src/Serializer/ScalarNormalizer.php000064400000003267151360552310013436 0ustar00 (int) $data, 'string' => (string) $data, 'float' => (float) $data, 'bool' => (bool) $data, default => $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); } } src/Serializer/Serializer.php000064400000027465151360552310012305 0ustar00serialize($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($object, string $format = null, array $context = []) { // If a normalizer supports the given data, use it if ($normalizer = $this->getNormalizer($object, $format, $context)) { return $normalizer->normalize($object, $format, $context); } if ($object === null || is_scalar($object)) { return $object; } if (\is_array($object) || $object instanceof \Traversable) { if ($object instanceof \Countable && $object->count() === 0) { return $object; } $normalized = []; foreach ($object as $key => $val) { $normalized[$key] = $this->normalize($val, $format, $context); } return $normalized; } if (\is_object($object)) { 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($object))); } throw new NotNormalizableValueException('An unexpected value could not be normalized: ' . (! \is_resource($object) ? var_export($object, true) : sprintf('%s resource', get_resource_type($object)))); } /** * @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; } } src/Serializer/SerializerFactory.php000064400000001452151360552310013621 0ustar00serializer([ new ExceptionNormalizer(), new ObjectNormalizer(), new ArrayDenormalizer(), new ScalarNormalizer(), ]); } } src/Serializer/SimpleNormalizer.php000064400000001407151360552310013454 0ustar00 (int) $data, 'string' => (string) $data, 'float' => (float) $data, 'array' => (array) $data, 'bool' => (bool) $data, default => $data, }; } } src/Serializer/SymfonyNormalizer.php000064400000001335151360552310013667 0ustar00serializer->normalize($object); } public function denormalize($data, string $class) { return $this->serializer->denormalize($data, $class); } } src/Str.php000064400000105034151360552310006620 0ustar00 $val) { $value = str_replace($val, (string) $key, $value); } return preg_replace('/[^\x20-\x7E]/u', '', $value); } /** * Get the portion of a string before a given value. * * @param string $subject * @param string $search * @return string */ public static function before($subject, $search) { return $search === '' ? $subject : explode($search, $subject)[0]; } /** * Get the portion of a string before the last occurrence of a given value. * * @param string $subject * @param string $search * @return string */ public static function beforeLast($subject, $search) { if ($search === '') { return $subject; } $pos = mb_strrpos($subject, $search); if ($pos === false) { return $subject; } return static::substr($subject, 0, $pos); } /** * Get the portion of a string between two given values. * * @param string $subject * @param string $from * @param string $to * @return string */ public static function between($subject, $from, $to) { if ($from === '' || $to === '') { return $subject; } return static::beforeLast(static::after($subject, $from), $to); } /** * Convert a value to camel case. * * @param string $value * @return string */ public static function camel($value) { if (isset(static::$camelCache[$value])) { return static::$camelCache[$value]; } return static::$camelCache[$value] = lcfirst(static::studly($value)); } /** * Determine if a given string contains a given substring. * * @param string $haystack * @param array|string $needles */ public static function contains($haystack, $needles): bool { foreach ((array) $needles as $needle) { if ($needle !== '' && mb_strpos($haystack, $needle) !== false) { return true; } } return false; } /** * Determine if a given string contains all array values. * * @param string $haystack * @param string[] $needles * @return bool */ public static function containsAll($haystack, array $needles) { foreach ($needles as $needle) { if (! static::contains($haystack, $needle)) { return false; } } return true; } /** * Determine if a given string ends with a given substring. * * @param string $haystack * @param array|string $needles * @return bool */ public static function endsWith($haystack, $needles) { foreach ((array) $needles as $needle) { if (substr($haystack, -strlen($needle)) === (string) $needle) { return true; } } return false; } /** * Cap a string with a single instance of a given value. * * @param string $value * @param string $cap * @return string */ public static function finish($value, $cap) { $quoted = preg_quote($cap, '/'); return preg_replace('/(?:' . $quoted . ')+$/u', '', $value) . $cap; } /** * Determine if a given string matches a given pattern. * * @param array|string $pattern * @param string $value * @return bool */ public static function is($pattern, $value) { $patterns = Arr::wrap($pattern); if (empty($patterns)) { return false; } foreach ($patterns as $pattern) { // If the given value is an exact match we can of course return true right // from the beginning. Otherwise, we will translate asterisks and do an // actual pattern match against the two strings to see if they match. if ($pattern == $value) { return true; } $pattern = preg_quote($pattern, '#'); // Asterisks are translated into zero-or-more regular expression wildcards // to make it convenient to check if the strings starts with the given // pattern such as "library/*", making any string check convenient. $pattern = str_replace('\*', '.*', $pattern); if (preg_match('#^' . $pattern . '\z#u', $value) === 1) { return true; } } return false; } /** * Convert a string to kebab case. * * @param string $value * @return string */ public static function kebab($value) { return static::snake($value, '-'); } /** * Return the length of the given string. * * @param string $value * @param string $encoding * @return int */ public static function length($value, $encoding = null) { if ($encoding) { return mb_strlen($value, $encoding); } return mb_strlen($value); } /** * Limit the number of characters in a string. * * @param string $value * @param int $limit * @param string $end * @return string */ public static function limit($value, $limit = 100, $end = '...') { if (mb_strwidth($value, 'UTF-8') <= $limit) { return $value; } return rtrim(mb_strimwidth($value, 0, $limit, '', 'UTF-8')) . $end; } /** * Convert the given string to lower-case. * * @param string $value * @return string */ public static function lower($value) { return mb_strtolower($value, 'UTF-8'); } /** * Limit the number of words in a string. */ public static function words(string $value, int $words = 100, string $end = '...'): string { preg_match('/^\s*+(?:\S++\s*+){1,' . $words . '}/u', $value, $matches); if (! isset($matches[0]) || static::length($value) === static::length($matches[0])) { return $value; } return rtrim($matches[0]) . $end; } /** * Get the string matching the given pattern. * * @param string $pattern * @param string $subject * @return string */ public static function match($pattern, $subject) { preg_match($pattern, $subject, $matches); if (! $matches) { return ''; } return $matches[1] ?? $matches[0]; } /** * Get the string matching the given pattern. * * @param string $pattern * @param string $subject * @return Collection */ public static function matchAll($pattern, $subject) { preg_match_all($pattern, $subject, $matches); if (empty($matches[0])) { return collect(); } return collect($matches[1] ?? $matches[0]); } /** * Pad both sides of a string with another. * * @param string $value * @param int $length * @param string $pad * @return string */ public static function padBoth($value, $length, $pad = ' ') { return str_pad($value, $length, $pad, STR_PAD_BOTH); } /** * Pad the left side of a string with another. * * @param string $value * @param int $length * @param string $pad * @return string */ public static function padLeft($value, $length, $pad = ' ') { return str_pad($value, $length, $pad, STR_PAD_LEFT); } /** * Pad the right side of a string with another. * * @param string $value * @param int $length * @param string $pad * @return string */ public static function padRight($value, $length, $pad = ' ') { return str_pad($value, $length, $pad, STR_PAD_RIGHT); } /** * Parse a Class@method style callback into class and method. * * @param null|string $default */ public static function parseCallback(string $callback, $default = null): array { return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default]; } /** * Pluralize the last word of an English, studly caps case string. */ public static function pluralStudly(string $value, int $count = 2): string { $parts = preg_split('/(.)(?=[A-Z])/u', $value, -1, PREG_SPLIT_DELIM_CAPTURE); $lastWord = array_pop($parts); return implode('', $parts) . self::plural($lastWord, $count); } /** * Get the plural form of an English word. */ public static function plural(string $value, int $count = 2): string { return Pluralizer::plural($value, $count); } /** * Generate a more truly "random" alpha-numeric string. */ public static function random(int $length = 16): string { $string = ''; while (($len = strlen($string)) < $length) { $size = $length - $len; $bytes = random_bytes($size); $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size); } return $string; } /** * Repeat the given string. * * @return string */ public static function repeat(string $string, int $times) { return str_repeat($string, $times); } /** * Replace a given value in the string sequentially with an array. */ public static function replaceArray(string $search, array $replace, string $subject): string { foreach ($replace as $value) { $subject = static::replaceFirst($search, (string) $value, $subject); } return $subject; } /** * Replace the given value in the given string. * * @param string|string[] $search * @param string|string[] $replace * @param string|string[] $subject * @return string */ public static function replace($search, $replace, $subject) { return str_replace($search, $replace, $subject); } /** * Replace the first occurrence of a given value in the string. */ public static function replaceFirst(string $search, string $replace, string $subject): string { if ($search == '') { return $subject; } $position = strpos($subject, $search); if ($position !== false) { return substr_replace($subject, $replace, $position, strlen($search)); } return $subject; } /** * Replace the last occurrence of a given value in the string. */ public static function replaceLast(string $search, string $replace, string $subject): string { $position = strrpos($subject, $search); if ($position !== false) { return substr_replace($subject, $replace, $position, strlen($search)); } return $subject; } /** * Remove any occurrence of the given string in the subject. * * @param array|string $search * @param string $subject * @param bool $caseSensitive * @return string */ public static function remove($search, $subject, $caseSensitive = true) { return $caseSensitive ? str_replace($search, '', $subject) : str_ireplace($search, '', $subject); } /** * Begin a string with a single instance of a given value. */ public static function start(string $value, string $prefix): string { $quoted = preg_quote($prefix, '/'); return $prefix . preg_replace('/^(?:' . $quoted . ')+/u', '', $value); } /** * Strip HTML and PHP tags from the given string. * * @param null|string|string[] $allowedTags */ public static function stripTags(string $value, $allowedTags = null): string { return strip_tags($value, $allowedTags); } /** * Convert the given string to upper-case. */ public static function upper(string $value): string { return mb_strtoupper($value, 'UTF-8'); } /** * Convert the given string to title case. */ public static function title(string $value): string { return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8'); } /** * Get the singular form of an English word. */ public static function singular(string $value): string { return Pluralizer::singular($value); } /** * Generate a URL friendly "slug" from a given string. */ public static function slug(string $title, string $separator = '-', string $language = 'en'): string { $title = $language ? static::ascii($title, $language) : $title; // Convert all dashes/underscores into separator $flip = $separator === '-' ? '_' : '-'; $title = preg_replace('![' . preg_quote($flip) . ']+!u', $separator, $title); // Replace @ with the word 'at' $title = str_replace('@', $separator . 'at' . $separator, $title); // Remove all characters that are not the separator, letters, numbers, or whitespace. $title = preg_replace('![^' . preg_quote($separator) . '\pL\pN\s]+!u', '', mb_strtolower($title)); // Replace all separator characters and whitespace by a single separator $title = preg_replace('![' . preg_quote($separator) . '\s]+!u', $separator, $title); return trim($title, $separator); } /** * Convert a string to snake case. */ public static function snake(string $value, string $delimiter = '_'): string { $key = $value; if (isset(static::$snakeCache[$key][$delimiter])) { return static::$snakeCache[$key][$delimiter]; } if (! ctype_lower($value)) { $value = preg_replace('/\s+/u', '', ucwords($value)); $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $value)); } return static::$snakeCache[$key][$delimiter] = $value; } /** * Determine if a given string starts with a given substring. * * @param array|string $needles */ public static function startsWith(string $haystack, $needles): bool { foreach ((array) $needles as $needle) { if ($needle !== '' && substr($haystack, 0, strlen($needle)) === (string) $needle) { return true; } } return false; } /** * Convert a value to studly caps case. */ public static function studly(string $value, string $gap = ''): string { $key = $value; if (isset(static::$studlyCache[$key])) { return static::$studlyCache[$key]; } $value = ucwords(str_replace(['-', '_'], ' ', $value)); return static::$studlyCache[$key] = str_replace(' ', $gap, $value); } /** * Returns the portion of string specified by the start and length parameters. * * @param string $string * @param int $start * @param null|int $length * @return string */ public static function substr($string, $start, $length = null) { return mb_substr($string, $start, $length, 'UTF-8'); } /** * Returns the number of substring occurrences. * * @param string $haystack * @param string $needle * @param int $offset * @param null|int $length * @return int */ public static function substrCount($haystack, $needle, $offset = 0, $length = null) { if (! is_null($length)) { return substr_count($haystack, $needle, $offset, $length); } return substr_count($haystack, $needle, $offset); } /** * Make a string's first character uppercase. */ public static function ucfirst(string $string): string { return static::upper(static::substr($string, 0, 1)) . static::substr($string, 1); } /** * 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 * */ public static function mask(string $string, int $offset = 0, int $length = 0, string $replacement = '*') { if ($length < 0) { throw new InvalidArgumentException('The length must equal or greater than zero.'); } $stringLength = mb_strlen($string); $absOffset = abs($offset); if ($absOffset >= $stringLength) { return $string; } $hiddenLength = $length ?: $stringLength - $absOffset; if ($offset >= 0) { return mb_substr($string, 0, $offset) . str_repeat($replacement, $hiddenLength) . mb_substr($string, $offset + $hiddenLength); } return mb_substr($string, 0, max($stringLength - $hiddenLength - $absOffset, 0)) . str_repeat($replacement, $hiddenLength) . mb_substr($string, $offset); } /** * Returns the replacements for the ascii method. * Note: Adapted from Stringy\Stringy. * * @see https://github.com/danielstjules/Stringy/blob/3.1.0/LICENSE.txt */ protected static function charsArray(): array { static $charsArray; if (isset($charsArray)) { return $charsArray; } return $charsArray = [ '0' => ['°', '₀', '۰', '0'], '1' => ['¹', '₁', '۱', '1'], '2' => ['²', '₂', '۲', '2'], '3' => ['³', '₃', '۳', '3'], '4' => ['⁴', '₄', '۴', '٤', '4'], '5' => ['⁵', '₅', '۵', '٥', '5'], '6' => ['⁶', '₆', '۶', '٦', '6'], '7' => ['⁷', '₇', '۷', '7'], '8' => ['⁸', '₈', '۸', '8'], '9' => ['⁹', '₉', '۹', '9'], 'a' => [ 'à', 'á', 'ả', 'ã', 'ạ', 'ă', 'ắ', 'ằ', 'ẳ', 'ẵ', 'ặ', 'â', 'ấ', 'ầ', 'ẩ', 'ẫ', 'ậ', 'ā', 'ą', 'å', 'α', 'ά', 'ἀ', 'ἁ', 'ἂ', 'ἃ', 'ἄ', 'ἅ', 'ἆ', 'ἇ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ὰ', 'ά', 'ᾰ', 'ᾱ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'а', 'أ', 'အ', 'ာ', 'ါ', 'ǻ', 'ǎ', 'ª', 'ა', 'अ', 'ا', 'a', 'ä', ], 'b' => ['б', 'β', 'ب', 'ဗ', 'ბ', 'b'], 'c' => ['ç', 'ć', 'č', 'ĉ', 'ċ', 'c'], 'd' => ['ď', 'ð', 'đ', 'ƌ', 'ȡ', 'ɖ', 'ɗ', 'ᵭ', 'ᶁ', 'ᶑ', 'д', 'δ', 'د', 'ض', 'ဍ', 'ဒ', 'დ', 'd'], 'e' => [ 'é', 'è', 'ẻ', 'ẽ', 'ẹ', 'ê', 'ế', 'ề', 'ể', 'ễ', 'ệ', 'ë', 'ē', 'ę', 'ě', 'ĕ', 'ė', 'ε', 'έ', 'ἐ', 'ἑ', 'ἒ', 'ἓ', 'ἔ', 'ἕ', 'ὲ', 'έ', 'е', 'ё', 'э', 'є', 'ə', 'ဧ', 'ေ', 'ဲ', 'ე', 'ए', 'إ', 'ئ', 'e', ], 'f' => ['ф', 'φ', 'ف', 'ƒ', 'ფ', 'f'], 'g' => ['ĝ', 'ğ', 'ġ', 'ģ', 'г', 'ґ', 'γ', 'ဂ', 'გ', 'گ', 'g'], 'h' => ['ĥ', 'ħ', 'η', 'ή', 'ح', 'ه', 'ဟ', 'ှ', 'ჰ', 'h'], 'i' => [ 'í', 'ì', 'ỉ', 'ĩ', 'ị', 'î', 'ï', 'ī', 'ĭ', 'į', 'ı', 'ι', 'ί', 'ϊ', 'ΐ', 'ἰ', 'ἱ', 'ἲ', 'ἳ', 'ἴ', 'ἵ', 'ἶ', 'ἷ', 'ὶ', 'ί', 'ῐ', 'ῑ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'і', 'ї', 'и', 'ဣ', 'ိ', 'ီ', 'ည်', 'ǐ', 'ი', 'इ', 'ی', 'i', ], 'j' => ['ĵ', 'ј', 'Ј', 'ჯ', 'ج', 'j'], 'k' => ['ķ', 'ĸ', 'к', 'κ', 'Ķ', 'ق', 'ك', 'က', 'კ', 'ქ', 'ک', 'k'], 'l' => ['ł', 'ľ', 'ĺ', 'ļ', 'ŀ', 'л', 'λ', 'ل', 'လ', 'ლ', 'l'], 'm' => ['м', 'μ', 'م', 'မ', 'მ', 'm'], 'n' => ['ñ', 'ń', 'ň', 'ņ', 'ʼn', 'ŋ', 'ν', 'н', 'ن', 'န', 'ნ', 'n'], 'o' => [ 'ó', 'ò', 'ỏ', 'õ', 'ọ', 'ô', 'ố', 'ồ', 'ổ', 'ỗ', 'ộ', 'ơ', 'ớ', 'ờ', 'ở', 'ỡ', 'ợ', 'ø', 'ō', 'ő', 'ŏ', 'ο', 'ὀ', 'ὁ', 'ὂ', 'ὃ', 'ὄ', 'ὅ', 'ὸ', 'ό', 'о', 'و', 'θ', 'ို', 'ǒ', 'ǿ', 'º', 'ო', 'ओ', 'o', 'ö', ], 'p' => ['п', 'π', 'ပ', 'პ', 'پ', 'p'], 'q' => ['ყ', 'q'], 'r' => ['ŕ', 'ř', 'ŗ', 'р', 'ρ', 'ر', 'რ', 'r'], 's' => ['ś', 'š', 'ş', 'с', 'σ', 'ș', 'ς', 'س', 'ص', 'စ', 'ſ', 'ს', 's'], 't' => ['ť', 'ţ', 'т', 'τ', 'ț', 'ت', 'ط', 'ဋ', 'တ', 'ŧ', 'თ', 'ტ', 't'], 'u' => [ 'ú', 'ù', 'ủ', 'ũ', 'ụ', 'ư', 'ứ', 'ừ', 'ử', 'ữ', 'ự', 'û', 'ū', 'ů', 'ű', 'ŭ', 'ų', 'µ', 'у', 'ဉ', 'ု', 'ူ', 'ǔ', 'ǖ', 'ǘ', 'ǚ', 'ǜ', 'უ', 'उ', 'u', 'ў', 'ü', ], 'v' => ['в', 'ვ', 'ϐ', 'v'], 'w' => ['ŵ', 'ω', 'ώ', 'ဝ', 'ွ', 'w'], 'x' => ['χ', 'ξ', 'x'], 'y' => ['ý', 'ỳ', 'ỷ', 'ỹ', 'ỵ', 'ÿ', 'ŷ', 'й', 'ы', 'υ', 'ϋ', 'ύ', 'ΰ', 'ي', 'ယ', 'y'], 'z' => ['ź', 'ž', 'ż', 'з', 'ζ', 'ز', 'ဇ', 'ზ', 'z'], 'aa' => ['ع', 'आ', 'آ'], 'ae' => ['æ', 'ǽ'], 'ai' => ['ऐ'], 'ch' => ['ч', 'ჩ', 'ჭ', 'چ'], 'dj' => ['ђ', 'đ'], 'dz' => ['џ', 'ძ'], 'ei' => ['ऍ'], 'gh' => ['غ', 'ღ'], 'ii' => ['ई'], 'ij' => ['ij'], 'kh' => ['х', 'خ', 'ხ'], 'lj' => ['љ'], 'nj' => ['њ'], 'oe' => ['ö', 'œ', 'ؤ'], 'oi' => ['ऑ'], 'oii' => ['ऒ'], 'ps' => ['ψ'], 'sh' => ['ш', 'შ', 'ش'], 'shch' => ['щ'], 'ss' => ['ß'], 'sx' => ['ŝ'], 'th' => ['þ', 'ϑ', 'ث', 'ذ', 'ظ'], 'ts' => ['ц', 'ც', 'წ'], 'ue' => ['ü'], 'uu' => ['ऊ'], 'ya' => ['я'], 'yu' => ['ю'], 'zh' => ['ж', 'ჟ', 'ژ'], '(c)' => ['©'], 'A' => [ 'Á', 'À', 'Ả', 'Ã', 'Ạ', 'Ă', 'Ắ', 'Ằ', 'Ẳ', 'Ẵ', 'Ặ', 'Â', 'Ấ', 'Ầ', 'Ẩ', 'Ẫ', 'Ậ', 'Å', 'Ā', 'Ą', 'Α', 'Ά', 'Ἀ', 'Ἁ', 'Ἂ', 'Ἃ', 'Ἄ', 'Ἅ', 'Ἆ', 'Ἇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'Ᾰ', 'Ᾱ', 'Ὰ', 'Ά', 'ᾼ', 'А', 'Ǻ', 'Ǎ', 'A', 'Ä', ], 'B' => ['Б', 'Β', 'ब', 'B'], 'C' => ['Ç', 'Ć', 'Č', 'Ĉ', 'Ċ', 'C'], 'D' => ['Ď', 'Ð', 'Đ', 'Ɖ', 'Ɗ', 'Ƌ', 'ᴅ', 'ᴆ', 'Д', 'Δ', 'D'], 'E' => [ 'É', 'È', 'Ẻ', 'Ẽ', 'Ẹ', 'Ê', 'Ế', 'Ề', 'Ể', 'Ễ', 'Ệ', 'Ë', 'Ē', 'Ę', 'Ě', 'Ĕ', 'Ė', 'Ε', 'Έ', 'Ἐ', 'Ἑ', 'Ἒ', 'Ἓ', 'Ἔ', 'Ἕ', 'Έ', 'Ὲ', 'Е', 'Ё', 'Э', 'Є', 'Ə', 'E', ], 'F' => ['Ф', 'Φ', 'F'], 'G' => ['Ğ', 'Ġ', 'Ģ', 'Г', 'Ґ', 'Γ', 'G'], 'H' => ['Η', 'Ή', 'Ħ', 'H'], 'I' => [ 'Í', 'Ì', 'Ỉ', 'Ĩ', 'Ị', 'Î', 'Ï', 'Ī', 'Ĭ', 'Į', 'İ', 'Ι', 'Ί', 'Ϊ', 'Ἰ', 'Ἱ', 'Ἳ', 'Ἴ', 'Ἵ', 'Ἶ', 'Ἷ', 'Ῐ', 'Ῑ', 'Ὶ', 'Ί', 'И', 'І', 'Ї', 'Ǐ', 'ϒ', 'I', ], 'J' => ['J'], 'K' => ['К', 'Κ', 'K'], 'L' => ['Ĺ', 'Ł', 'Л', 'Λ', 'Ļ', 'Ľ', 'Ŀ', 'ल', 'L'], 'M' => ['М', 'Μ', 'M'], 'N' => ['Ń', 'Ñ', 'Ň', 'Ņ', 'Ŋ', 'Н', 'Ν', 'N'], 'O' => [ 'Ó', 'Ò', 'Ỏ', 'Õ', 'Ọ', 'Ô', 'Ố', 'Ồ', 'Ổ', 'Ỗ', 'Ộ', 'Ơ', 'Ớ', 'Ờ', 'Ở', 'Ỡ', 'Ợ', 'Ø', 'Ō', 'Ő', 'Ŏ', 'Ο', 'Ό', 'Ὀ', 'Ὁ', 'Ὂ', 'Ὃ', 'Ὄ', 'Ὅ', 'Ὸ', 'Ό', 'О', 'Θ', 'Ө', 'Ǒ', 'Ǿ', 'O', 'Ö', ], 'P' => ['П', 'Π', 'P'], 'Q' => ['Q'], 'R' => ['Ř', 'Ŕ', 'Р', 'Ρ', 'Ŗ', 'R'], 'S' => ['Ş', 'Ŝ', 'Ș', 'Š', 'Ś', 'С', 'Σ', 'S'], 'T' => ['Ť', 'Ţ', 'Ŧ', 'Ț', 'Т', 'Τ', 'T'], 'U' => [ 'Ú', 'Ù', 'Ủ', 'Ũ', 'Ụ', 'Ư', 'Ứ', 'Ừ', 'Ử', 'Ữ', 'Ự', 'Û', 'Ū', 'Ů', 'Ű', 'Ŭ', 'Ų', 'У', 'Ǔ', 'Ǖ', 'Ǘ', 'Ǚ', 'Ǜ', 'U', 'Ў', 'Ü', ], 'V' => ['В', 'V'], 'W' => ['Ω', 'Ώ', 'Ŵ', 'W'], 'X' => ['Χ', 'Ξ', 'X'], 'Y' => ['Ý', 'Ỳ', 'Ỷ', 'Ỹ', 'Ỵ', 'Ÿ', 'Ῠ', 'Ῡ', 'Ὺ', 'Ύ', 'Ы', 'Й', 'Υ', 'Ϋ', 'Ŷ', 'Y'], 'Z' => ['Ź', 'Ž', 'Ż', 'З', 'Ζ', 'Z'], 'AE' => ['Æ', 'Ǽ'], 'Ch' => ['Ч'], 'Dj' => ['Ђ'], 'Dz' => ['Џ'], 'Gx' => ['Ĝ'], 'Hx' => ['Ĥ'], 'Ij' => ['IJ'], 'Jx' => ['Ĵ'], 'Kh' => ['Х'], 'Lj' => ['Љ'], 'Nj' => ['Њ'], 'Oe' => ['Œ'], 'Ps' => ['Ψ'], 'Sh' => ['Ш'], 'Shch' => ['Щ'], 'Ss' => ['ẞ'], 'Th' => ['Þ'], 'Ts' => ['Ц'], 'Ya' => ['Я'], 'Yu' => ['Ю'], 'Zh' => ['Ж'], ' ' => [ "\xC2\xA0", "\xE2\x80\x80", "\xE2\x80\x81", "\xE2\x80\x82", "\xE2\x80\x83", "\xE2\x80\x84", "\xE2\x80\x85", "\xE2\x80\x86", "\xE2\x80\x87", "\xE2\x80\x88", "\xE2\x80\x89", "\xE2\x80\x8A", "\xE2\x80\xAF", "\xE2\x81\x9F", "\xE3\x80\x80", "\xEF\xBE\xA0", ], ]; } /** * Returns the language specific replacements for the ascii method. * Note: Adapted from Stringy\Stringy. * * @see https://github.com/danielstjules/Stringy/blob/3.1.0/LICENSE.txt * @return null|array */ protected static function languageSpecificCharsArray(string $language) { static $languageSpecific; if (! isset($languageSpecific)) { $languageSpecific = [ 'bg' => [ ['х', 'Х', 'щ', 'Щ', 'ъ', 'Ъ', 'ь', 'Ь'], ['h', 'H', 'sht', 'SHT', 'a', 'А', 'y', 'Y'], ], 'de' => [ ['ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü'], ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], ], ]; } return $languageSpecific[$language] ?? null; } } src/Stringable.php000064400000043145151360552310010146 0ustar00value = (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 $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 string $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 string $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 $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 * @param null|callable $default * @return static */ public function whenEmpty($callback, $default = null) { return $this->when($this->isEmpty(), $callback, $default); } /** * Execute the given callback if the string is not empty. * * @param callable $callback * @param null|callable $default * @return static */ public function whenNotEmpty($callback, $default = null) { return $this->when($this->isNotEmpty(), $callback, $default); } /** * 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. */ public function jsonSerialize(): mixed { return $this->__toString(); } } src/Traits/Conditionable.php000064400000002346151360552310012072 0ustar00getTargetObject(); 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); } } src/Traits/ForwardsCalls.php000064400000002623151360552310012064 0ustar00{$method}(...$parameters); } catch (Error|BadMethodCallException $e) { $pattern = '~^Call to undefined method (?P[^:]+)::(?P[^\(]+)\(\)$~'; 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)); } } src/Traits/StaticInstance.php000064400000001423151360552310012227 0ustar00popTimeout = $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; } }