dep: package update
This commit is contained in:
+1
@@ -27,6 +27,7 @@ class Mapper extends \IteratorIterator
|
||||
}
|
||||
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
return ($this->callback)(parent::current(), parent::key());
|
||||
|
||||
+1
-1
@@ -24,4 +24,4 @@ interface Translator
|
||||
}
|
||||
|
||||
|
||||
interface_exists(Nette\Localization\ITranslator::class);
|
||||
interface_exists(ITranslator::class);
|
||||
|
||||
+6
-2
@@ -14,11 +14,13 @@ use Nette;
|
||||
|
||||
/**
|
||||
* Provides objects to work as array.
|
||||
* @template T
|
||||
*/
|
||||
class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* Transforms array to ArrayHash.
|
||||
* @param array<T> $array
|
||||
* @return static
|
||||
*/
|
||||
public static function from(array $array, bool $recursive = true)
|
||||
@@ -35,6 +37,7 @@ class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \Iterator
|
||||
|
||||
/**
|
||||
* Returns an iterator over all items.
|
||||
* @return \RecursiveArrayIterator<array-key, T>
|
||||
*/
|
||||
public function getIterator(): \RecursiveArrayIterator
|
||||
{
|
||||
@@ -54,7 +57,7 @@ class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \Iterator
|
||||
/**
|
||||
* Replaces or appends a item.
|
||||
* @param string|int $key
|
||||
* @param mixed $value
|
||||
* @param T $value
|
||||
*/
|
||||
public function offsetSet($key, $value): void
|
||||
{
|
||||
@@ -68,8 +71,9 @@ class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \Iterator
|
||||
/**
|
||||
* Returns a item.
|
||||
* @param string|int $key
|
||||
* @return mixed
|
||||
* @return T
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($key)
|
||||
{
|
||||
return $this->$key;
|
||||
|
||||
+22
-3
@@ -14,6 +14,7 @@ use Nette;
|
||||
|
||||
/**
|
||||
* Provides the base class for a generic list (items can be accessed by index).
|
||||
* @template T
|
||||
*/
|
||||
class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
|
||||
{
|
||||
@@ -23,8 +24,25 @@ class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
|
||||
private $list = [];
|
||||
|
||||
|
||||
/**
|
||||
* Transforms array to ArrayList.
|
||||
* @param array<T> $array
|
||||
* @return static
|
||||
*/
|
||||
public static function from(array $array)
|
||||
{
|
||||
if (!Arrays::isList($array)) {
|
||||
throw new Nette\InvalidArgumentException('Array is not valid list.');
|
||||
}
|
||||
$obj = new static;
|
||||
$obj->list = $array;
|
||||
return $obj;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an iterator over all items.
|
||||
* @return \ArrayIterator<int, T>
|
||||
*/
|
||||
public function getIterator(): \ArrayIterator
|
||||
{
|
||||
@@ -44,7 +62,7 @@ class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
|
||||
/**
|
||||
* Replaces or appends a item.
|
||||
* @param int|null $index
|
||||
* @param mixed $value
|
||||
* @param T $value
|
||||
* @throws Nette\OutOfRangeException
|
||||
*/
|
||||
public function offsetSet($index, $value): void
|
||||
@@ -64,9 +82,10 @@ class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
|
||||
/**
|
||||
* Returns a item.
|
||||
* @param int $index
|
||||
* @return mixed
|
||||
* @return T
|
||||
* @throws Nette\OutOfRangeException
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($index)
|
||||
{
|
||||
if (!is_int($index) || $index < 0 || $index >= count($this->list)) {
|
||||
@@ -102,7 +121,7 @@ class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
|
||||
|
||||
/**
|
||||
* Prepends a item.
|
||||
* @param mixed $value
|
||||
* @param T $value
|
||||
*/
|
||||
public function prepend($value): void
|
||||
{
|
||||
|
||||
+37
-19
@@ -22,9 +22,11 @@ class Arrays
|
||||
|
||||
/**
|
||||
* Returns item from array. If it does not exist, it throws an exception, unless a default value is set.
|
||||
* @param string|int|array $key one or more keys
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
* @template T
|
||||
* @param array<T> $array
|
||||
* @param array-key|array-key[] $key
|
||||
* @param ?T $default
|
||||
* @return ?T
|
||||
* @throws Nette\InvalidArgumentException if item does not exist and default value is not provided
|
||||
*/
|
||||
public static function get(array $array, $key, $default = null)
|
||||
@@ -45,8 +47,10 @@ class Arrays
|
||||
|
||||
/**
|
||||
* Returns reference to array item. If the index does not exist, new one is created with value null.
|
||||
* @param string|int|array $key one or more keys
|
||||
* @return mixed
|
||||
* @template T
|
||||
* @param array<T> $array
|
||||
* @param array-key|array-key[] $key
|
||||
* @return ?T
|
||||
* @throws Nette\InvalidArgumentException if traversed item is not an array
|
||||
*/
|
||||
public static function &getRef(array &$array, $key)
|
||||
@@ -66,6 +70,11 @@ class Arrays
|
||||
* Recursively merges two fields. It is useful, for example, for merging tree structures. It behaves as
|
||||
* the + operator for array, ie. it adds a key/value pair from the second array to the first one and retains
|
||||
* the value from the first array in the case of a key collision.
|
||||
* @template T1
|
||||
* @template T2
|
||||
* @param array<T1> $array1
|
||||
* @param array<T2> $array2
|
||||
* @return array<T1|T2>
|
||||
*/
|
||||
public static function mergeTree(array $array1, array $array2): array
|
||||
{
|
||||
@@ -81,7 +90,7 @@ class Arrays
|
||||
|
||||
/**
|
||||
* Returns zero-indexed position of given array key. Returns null if key is not found.
|
||||
* @param string|int $key
|
||||
* @param array-key $key
|
||||
* @return int|null offset if it is found, null otherwise
|
||||
*/
|
||||
public static function getKeyOffset(array $array, $key): ?int
|
||||
@@ -111,7 +120,9 @@ class Arrays
|
||||
|
||||
/**
|
||||
* Returns the first item from the array or null if array is empty.
|
||||
* @return mixed
|
||||
* @template T
|
||||
* @param array<T> $array
|
||||
* @return ?T
|
||||
*/
|
||||
public static function first(array $array)
|
||||
{
|
||||
@@ -121,7 +132,9 @@ class Arrays
|
||||
|
||||
/**
|
||||
* Returns the last item from the array or null if array is empty.
|
||||
* @return mixed
|
||||
* @template T
|
||||
* @param array<T> $array
|
||||
* @return ?T
|
||||
*/
|
||||
public static function last(array $array)
|
||||
{
|
||||
@@ -132,7 +145,7 @@ class Arrays
|
||||
/**
|
||||
* Inserts the contents of the $inserted array into the $array immediately after the $key.
|
||||
* If $key is null (or does not exist), it is inserted at the beginning.
|
||||
* @param string|int|null $key
|
||||
* @param array-key|null $key
|
||||
*/
|
||||
public static function insertBefore(array &$array, $key, array $inserted): void
|
||||
{
|
||||
@@ -146,7 +159,7 @@ class Arrays
|
||||
/**
|
||||
* Inserts the contents of the $inserted array into the $array before the $key.
|
||||
* If $key is null (or does not exist), it is inserted at the end.
|
||||
* @param string|int|null $key
|
||||
* @param array-key|null $key
|
||||
*/
|
||||
public static function insertAfter(array &$array, $key, array $inserted): void
|
||||
{
|
||||
@@ -161,8 +174,8 @@ class Arrays
|
||||
|
||||
/**
|
||||
* Renames key in array.
|
||||
* @param string|int $oldKey
|
||||
* @param string|int $newKey
|
||||
* @param array-key $oldKey
|
||||
* @param array-key $newKey
|
||||
*/
|
||||
public static function renameKey(array &$array, $oldKey, $newKey): bool
|
||||
{
|
||||
@@ -181,7 +194,8 @@ class Arrays
|
||||
|
||||
/**
|
||||
* Returns only those array items, which matches a regular expression $pattern.
|
||||
* @throws Nette\RegexpException on compilation or runtime error
|
||||
* @param string[] $array
|
||||
* @return string[]
|
||||
*/
|
||||
public static function grep(array $array, string $pattern, int $flags = 0): array
|
||||
{
|
||||
@@ -286,9 +300,11 @@ class Arrays
|
||||
/**
|
||||
* Returns and removes the value of an item from an array. If it does not exist, it throws an exception,
|
||||
* or returns $default, if provided.
|
||||
* @param string|int $key
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
* @template T
|
||||
* @param array<T> $array
|
||||
* @param array-key $key
|
||||
* @param ?T $default
|
||||
* @return ?T
|
||||
* @throws Nette\InvalidArgumentException if item does not exist and default value is not provided
|
||||
*/
|
||||
public static function pick(array &$array, $key, $default = null)
|
||||
@@ -381,8 +397,9 @@ class Arrays
|
||||
|
||||
/**
|
||||
* Copies the elements of the $array array to the $object object and then returns it.
|
||||
* @param object $object
|
||||
* @return object
|
||||
* @template T of object
|
||||
* @param T $object
|
||||
* @return T
|
||||
*/
|
||||
public static function toObject(iterable $array, $object)
|
||||
{
|
||||
@@ -396,7 +413,7 @@ class Arrays
|
||||
/**
|
||||
* Converts value to array key.
|
||||
* @param mixed $value
|
||||
* @return int|string
|
||||
* @return array-key
|
||||
*/
|
||||
public static function toKey($value)
|
||||
{
|
||||
@@ -407,6 +424,7 @@ class Arrays
|
||||
/**
|
||||
* Returns copy of the $array where every item is converted to string
|
||||
* and prefixed by $prefix and suffixed by $suffix.
|
||||
* @param string[] $array
|
||||
* @return string[]
|
||||
*/
|
||||
public static function wrap(array $array, string $prefix = '', string $suffix = ''): array
|
||||
|
||||
+1
@@ -97,6 +97,7 @@ class DateTime extends \DateTime implements \JsonSerializable
|
||||
* @param string|\DateTimeZone $timezone (default timezone is used if null is passed)
|
||||
* @return static|false
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public static function createFromFormat($format, $time, $timezone = null)
|
||||
{
|
||||
if ($timezone === null) {
|
||||
|
||||
+23
@@ -148,6 +148,29 @@ final class FileSystem
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fixes permissions to a specific file or directory. Directories can be fixed recursively.
|
||||
* @throws Nette\IOException on error occurred
|
||||
*/
|
||||
public static function makeWritable(string $path, int $dirMode = 0777, int $fileMode = 0666): void
|
||||
{
|
||||
if (is_file($path)) {
|
||||
if (!@chmod($path, $fileMode)) { // @ is escalated to exception
|
||||
throw new Nette\IOException("Unable to chmod file '$path' to mode " . decoct($fileMode) . '. ' . Helpers::getLastError());
|
||||
}
|
||||
} elseif (is_dir($path)) {
|
||||
foreach (new \FilesystemIterator($path) as $item) {
|
||||
static::makeWritable($item->getPathname(), $dirMode, $fileMode);
|
||||
}
|
||||
if (!@chmod($path, $dirMode)) { // @ is escalated to exception
|
||||
throw new Nette\IOException("Unable to chmod directory '$path' to mode " . decoct($dirMode) . '. ' . Helpers::getLastError());
|
||||
}
|
||||
} else {
|
||||
throw new Nette\IOException("File or directory '$path' not found.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines if the path is absolute.
|
||||
*/
|
||||
|
||||
+4
-2
@@ -248,7 +248,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
||||
'isindex' => 1, 'wbr' => 1, 'command' => 1, 'track' => 1,
|
||||
];
|
||||
|
||||
/** @var array<int, Html|string> nodes */
|
||||
/** @var array<int, HtmlStringable|string> nodes */
|
||||
protected $children = [];
|
||||
|
||||
/** @var string element's name */
|
||||
@@ -662,8 +662,9 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
||||
/**
|
||||
* Returns child node (\ArrayAccess implementation).
|
||||
* @param int $index
|
||||
* @return static|string
|
||||
* @return HtmlStringable|string
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
final public function offsetGet($index)
|
||||
{
|
||||
return $this->children[$index];
|
||||
@@ -712,6 +713,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
||||
|
||||
/**
|
||||
* Iterates over elements.
|
||||
* @return \ArrayIterator<int, HtmlStringable|string>
|
||||
*/
|
||||
final public function getIterator(): \ArrayIterator
|
||||
{
|
||||
|
||||
+45
-13
@@ -47,27 +47,59 @@ final class ObjectHelpers
|
||||
/** @throws MemberAccessException */
|
||||
public static function strictCall(string $class, string $method, array $additionalMethods = []): void
|
||||
{
|
||||
$hint = self::getSuggestion(array_merge(
|
||||
get_class_methods($class),
|
||||
self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:\S+[ \t]+)??(\w+)\(~m'),
|
||||
$additionalMethods
|
||||
), $method);
|
||||
$trace = debug_backtrace(0, 3); // suppose this method is called from __call()
|
||||
$context = ($trace[1]['function'] ?? null) === '__call'
|
||||
? ($trace[2]['class'] ?? null)
|
||||
: null;
|
||||
|
||||
if (method_exists($class, $method)) { // called parent::$method()
|
||||
$class = 'parent';
|
||||
if ($context && is_a($class, $context, true) && method_exists($context, $method)) { // called parent::$method()
|
||||
$class = get_parent_class($context);
|
||||
}
|
||||
|
||||
if (method_exists($class, $method)) { // insufficient visibility
|
||||
$rm = new \ReflectionMethod($class, $method);
|
||||
$visibility = $rm->isPrivate()
|
||||
? 'private '
|
||||
: ($rm->isProtected() ? 'protected ' : '');
|
||||
throw new MemberAccessException("Call to {$visibility}method $class::$method() from " . ($context ? "scope $context." : 'global scope.'));
|
||||
|
||||
} else {
|
||||
$hint = self::getSuggestion(array_merge(
|
||||
get_class_methods($class),
|
||||
self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:\S+[ \t]+)??(\w+)\(~m'),
|
||||
$additionalMethods
|
||||
), $method);
|
||||
throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
|
||||
}
|
||||
throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
|
||||
}
|
||||
|
||||
|
||||
/** @throws MemberAccessException */
|
||||
public static function strictStaticCall(string $class, string $method): void
|
||||
{
|
||||
$hint = self::getSuggestion(
|
||||
array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), function ($m) { return $m->isStatic(); }),
|
||||
$method
|
||||
);
|
||||
throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
|
||||
$trace = debug_backtrace(0, 3); // suppose this method is called from __callStatic()
|
||||
$context = ($trace[1]['function'] ?? null) === '__callStatic'
|
||||
? ($trace[2]['class'] ?? null)
|
||||
: null;
|
||||
|
||||
if ($context && is_a($class, $context, true) && method_exists($context, $method)) { // called parent::$method()
|
||||
$class = get_parent_class($context);
|
||||
}
|
||||
|
||||
if (method_exists($class, $method)) { // insufficient visibility
|
||||
$rm = new \ReflectionMethod($class, $method);
|
||||
$visibility = $rm->isPrivate()
|
||||
? 'private '
|
||||
: ($rm->isProtected() ? 'protected ' : '');
|
||||
throw new MemberAccessException("Call to {$visibility}method $class::$method() from " . ($context ? "scope $context." : 'global scope.'));
|
||||
|
||||
} else {
|
||||
$hint = self::getSuggestion(
|
||||
array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), function ($m) { return $m->isStatic(); }),
|
||||
$method
|
||||
);
|
||||
throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+22
@@ -18,6 +18,8 @@ use Nette;
|
||||
* @property int $page
|
||||
* @property-read int $firstPage
|
||||
* @property-read int|null $lastPage
|
||||
* @property-read int $firstItemOnPage
|
||||
* @property-read int $lastItemOnPage
|
||||
* @property int $base
|
||||
* @property-read bool $first
|
||||
* @property-read bool $last
|
||||
@@ -85,6 +87,26 @@ class Paginator
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the sequence number of the first element on the page
|
||||
*/
|
||||
public function getFirstItemOnPage(): int
|
||||
{
|
||||
return $this->itemCount !== 0
|
||||
? $this->offset + 1
|
||||
: 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the sequence number of the last element on the page
|
||||
*/
|
||||
public function getLastItemOnPage(): int
|
||||
{
|
||||
return $this->offset + $this->length;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets first page (base) number.
|
||||
* @return static
|
||||
|
||||
+24
-48
@@ -22,6 +22,7 @@ final class Reflection
|
||||
private const BUILTIN_TYPES = [
|
||||
'string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1,
|
||||
'callable' => 1, 'iterable' => 1, 'void' => 1, 'null' => 1, 'mixed' => 1, 'false' => 1,
|
||||
'never' => 1,
|
||||
];
|
||||
|
||||
|
||||
@@ -37,27 +38,29 @@ final class Reflection
|
||||
/**
|
||||
* Returns the type of return value of given function or method and normalizes `self`, `static`, and `parent` to actual class names.
|
||||
* If the function does not have a return type, it returns null.
|
||||
* If the function has union type, it throws Nette\InvalidStateException.
|
||||
* If the function has union or intersection type, it throws Nette\InvalidStateException.
|
||||
*/
|
||||
public static function getReturnType(\ReflectionFunctionAbstract $func): ?string
|
||||
{
|
||||
return self::getType($func, $func->getReturnType());
|
||||
$type = $func->getReturnType() ?? (PHP_VERSION_ID >= 80100 && $func instanceof \ReflectionMethod ? $func->getTentativeReturnType() : null);
|
||||
return self::getType($func, $type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the types of return value of given function or method and normalizes `self`, `static`, and `parent` to actual class names.
|
||||
* @deprecated
|
||||
*/
|
||||
public static function getReturnTypes(\ReflectionFunctionAbstract $func): array
|
||||
{
|
||||
return self::getType($func, $func->getReturnType(), true);
|
||||
$type = Type::fromReflection($func);
|
||||
return $type ? $type->getNames() : [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the type of given parameter and normalizes `self` and `parent` to the actual class names.
|
||||
* If the parameter does not have a type, it returns null.
|
||||
* If the parameter has union type, it throws Nette\InvalidStateException.
|
||||
* If the parameter has union or intersection type, it throws Nette\InvalidStateException.
|
||||
*/
|
||||
public static function getParameterType(\ReflectionParameter $param): ?string
|
||||
{
|
||||
@@ -66,18 +69,19 @@ final class Reflection
|
||||
|
||||
|
||||
/**
|
||||
* Returns the types of given parameter and normalizes `self` and `parent` to the actual class names.
|
||||
* @deprecated
|
||||
*/
|
||||
public static function getParameterTypes(\ReflectionParameter $param): array
|
||||
{
|
||||
return self::getType($param, $param->getType(), true);
|
||||
$type = Type::fromReflection($param);
|
||||
return $type ? $type->getNames() : [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the type of given property and normalizes `self` and `parent` to the actual class names.
|
||||
* If the property does not have a type, it returns null.
|
||||
* If the property has union type, it throws Nette\InvalidStateException.
|
||||
* If the property has union or intersection type, it throws Nette\InvalidStateException.
|
||||
*/
|
||||
public static function getPropertyType(\ReflectionProperty $prop): ?string
|
||||
{
|
||||
@@ -86,41 +90,28 @@ final class Reflection
|
||||
|
||||
|
||||
/**
|
||||
* Returns the types of given property and normalizes `self` and `parent` to the actual class names.
|
||||
* @deprecated
|
||||
*/
|
||||
public static function getPropertyTypes(\ReflectionProperty $prop): array
|
||||
{
|
||||
return self::getType($prop, PHP_VERSION_ID >= 70400 ? $prop->getType() : null, true);
|
||||
$type = Type::fromReflection($prop);
|
||||
return $type ? $type->getNames() : [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \ReflectionFunction|\ReflectionMethod|\ReflectionParameter|\ReflectionProperty $reflection
|
||||
* @return string|array|null
|
||||
*/
|
||||
private static function getType($reflection, ?\ReflectionType $type, bool $asArray = false)
|
||||
private static function getType($reflection, ?\ReflectionType $type): ?string
|
||||
{
|
||||
if ($type === null) {
|
||||
return $asArray ? [] : null;
|
||||
return null;
|
||||
|
||||
} elseif ($type instanceof \ReflectionNamedType) {
|
||||
$name = self::normalizeType($type->getName(), $reflection);
|
||||
if ($asArray) {
|
||||
return $type->allowsNull() && $type->getName() !== 'mixed'
|
||||
? [$name, 'null']
|
||||
: [$name];
|
||||
}
|
||||
return $name;
|
||||
return Type::resolve($type->getName(), $reflection);
|
||||
|
||||
} elseif ($type instanceof \ReflectionUnionType) {
|
||||
if ($asArray) {
|
||||
$types = [];
|
||||
foreach ($type->getTypes() as $type) {
|
||||
$types[] = self::normalizeType($type->getName(), $reflection);
|
||||
}
|
||||
return $types;
|
||||
}
|
||||
throw new Nette\InvalidStateException('The ' . self::toString($reflection) . ' is not expected to have a union type.');
|
||||
} elseif ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) {
|
||||
throw new Nette\InvalidStateException('The ' . self::toString($reflection) . ' is not expected to have a union or intersection type.');
|
||||
|
||||
} else {
|
||||
throw new Nette\InvalidStateException('Unexpected type of ' . self::toString($reflection));
|
||||
@@ -128,24 +119,6 @@ final class Reflection
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \ReflectionFunction|\ReflectionMethod|\ReflectionParameter|\ReflectionProperty $reflection
|
||||
*/
|
||||
private static function normalizeType(string $type, $reflection): string
|
||||
{
|
||||
$lower = strtolower($type);
|
||||
if ($reflection instanceof \ReflectionFunction) {
|
||||
return $type;
|
||||
} elseif ($lower === 'self' || $lower === 'static') {
|
||||
return $reflection->getDeclaringClass()->name;
|
||||
} elseif ($lower === 'parent' && $reflection->getDeclaringClass()->getParentClass()) {
|
||||
return $reflection->getDeclaringClass()->getParentClass()->name;
|
||||
} else {
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the default value of parameter. If it is a constant, it returns its value.
|
||||
* @return mixed
|
||||
@@ -157,7 +130,7 @@ final class Reflection
|
||||
$const = $orig = $param->getDefaultValueConstantName();
|
||||
$pair = explode('::', $const);
|
||||
if (isset($pair[1])) {
|
||||
$pair[0] = self::normalizeType($pair[0], $param);
|
||||
$pair[0] = Type::resolve($pair[0], $param);
|
||||
try {
|
||||
$rcc = new \ReflectionClassConstant($pair[0], $pair[1]);
|
||||
} catch (\ReflectionException $e) {
|
||||
@@ -343,6 +316,9 @@ final class Reflection
|
||||
case T_CLASS:
|
||||
case T_INTERFACE:
|
||||
case T_TRAIT:
|
||||
case PHP_VERSION_ID < 80100
|
||||
? T_CLASS
|
||||
: T_ENUM:
|
||||
if ($name = self::fetch($tokens, T_STRING)) {
|
||||
$class = $namespace . $name;
|
||||
$classLevel = $level + 1;
|
||||
|
||||
+2
-2
@@ -425,7 +425,7 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Returns position in bytes of $nth occurence of $needle in $haystack or null if the $needle was not found.
|
||||
* Returns position in characters of $nth occurence of $needle in $haystack or null if the $needle was not found.
|
||||
* Negative value of `$nth` means searching from the end.
|
||||
*/
|
||||
public static function indexOf(string $haystack, string $needle, int $nth = 1): ?int
|
||||
@@ -438,7 +438,7 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Returns position in bytes of $nth occurence of $needle in $haystack or null if the needle was not found.
|
||||
* Returns position in characters of $nth occurence of $needle in $haystack or null if the needle was not found.
|
||||
*/
|
||||
private static function pos(string $haystack, string $needle, int $nth = 1): ?int
|
||||
{
|
||||
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* PHP type reflection.
|
||||
*/
|
||||
final class Type
|
||||
{
|
||||
/** @var array */
|
||||
private $types;
|
||||
|
||||
/** @var bool */
|
||||
private $single;
|
||||
|
||||
/** @var string |, & */
|
||||
private $kind;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a Type object based on reflection. Resolves self, static and parent to the actual class name.
|
||||
* If the subject has no type, it returns null.
|
||||
* @param \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $reflection
|
||||
*/
|
||||
public static function fromReflection($reflection): ?self
|
||||
{
|
||||
if ($reflection instanceof \ReflectionProperty && PHP_VERSION_ID < 70400) {
|
||||
return null;
|
||||
} elseif ($reflection instanceof \ReflectionMethod) {
|
||||
$type = $reflection->getReturnType() ?? (PHP_VERSION_ID >= 80100 ? $reflection->getTentativeReturnType() : null);
|
||||
} else {
|
||||
$type = $reflection instanceof \ReflectionFunctionAbstract
|
||||
? $reflection->getReturnType()
|
||||
: $reflection->getType();
|
||||
}
|
||||
|
||||
if ($type === null) {
|
||||
return null;
|
||||
|
||||
} elseif ($type instanceof \ReflectionNamedType) {
|
||||
$name = self::resolve($type->getName(), $reflection);
|
||||
return new self($type->allowsNull() && $type->getName() !== 'mixed' ? [$name, 'null'] : [$name]);
|
||||
|
||||
} elseif ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) {
|
||||
return new self(
|
||||
array_map(
|
||||
function ($t) use ($reflection) { return self::resolve($t->getName(), $reflection); },
|
||||
$type->getTypes()
|
||||
),
|
||||
$type instanceof \ReflectionUnionType ? '|' : '&'
|
||||
);
|
||||
|
||||
} else {
|
||||
throw new Nette\InvalidStateException('Unexpected type of ' . Reflection::toString($reflection));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates the Type object according to the text notation.
|
||||
*/
|
||||
public static function fromString(string $type): self
|
||||
{
|
||||
if (!preg_match('#(?:
|
||||
\?([\w\\\\]+)|
|
||||
[\w\\\\]+ (?: (&[\w\\\\]+)* | (\|[\w\\\\]+)* )
|
||||
)()$#xAD', $type, $m)) {
|
||||
throw new Nette\InvalidArgumentException("Invalid type '$type'.");
|
||||
}
|
||||
[, $nType, $iType] = $m;
|
||||
if ($nType) {
|
||||
return new self([$nType, 'null']);
|
||||
} elseif ($iType) {
|
||||
return new self(explode('&', $type), '&');
|
||||
} else {
|
||||
return new self(explode('|', $type));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolves 'self', 'static' and 'parent' to the actual class name.
|
||||
* @param \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $reflection
|
||||
*/
|
||||
public static function resolve(string $type, $reflection): string
|
||||
{
|
||||
$lower = strtolower($type);
|
||||
if ($reflection instanceof \ReflectionFunction) {
|
||||
return $type;
|
||||
} elseif ($lower === 'self' || $lower === 'static') {
|
||||
return $reflection->getDeclaringClass()->name;
|
||||
} elseif ($lower === 'parent' && $reflection->getDeclaringClass()->getParentClass()) {
|
||||
return $reflection->getDeclaringClass()->getParentClass()->name;
|
||||
} else {
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function __construct(array $types, string $kind = '|')
|
||||
{
|
||||
if ($types[0] === 'null') { // null as last
|
||||
array_push($types, array_shift($types));
|
||||
}
|
||||
$this->types = $types;
|
||||
$this->single = ($types[1] ?? 'null') === 'null';
|
||||
$this->kind = count($types) > 1 ? $kind : '';
|
||||
}
|
||||
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->single
|
||||
? (count($this->types) > 1 ? '?' : '') . $this->types[0]
|
||||
: implode($this->kind, $this->types);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the array of subtypes that make up the compound type as strings.
|
||||
* @return string[]
|
||||
*/
|
||||
public function getNames(): array
|
||||
{
|
||||
return $this->types;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the array of subtypes that make up the compound type as Type objects:
|
||||
* @return self[]
|
||||
*/
|
||||
public function getTypes(): array
|
||||
{
|
||||
return array_map(function ($name) { return self::fromString($name); }, $this->types);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the type name for single types, otherwise null.
|
||||
*/
|
||||
public function getSingleName(): ?string
|
||||
{
|
||||
return $this->single
|
||||
? $this->types[0]
|
||||
: null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true whether it is a union type.
|
||||
*/
|
||||
public function isUnion(): bool
|
||||
{
|
||||
return $this->kind === '|';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true whether it is an intersection type.
|
||||
*/
|
||||
public function isIntersection(): bool
|
||||
{
|
||||
return $this->kind === '&';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true whether it is a single type. Simple nullable types are also considered to be single types.
|
||||
*/
|
||||
public function isSingle(): bool
|
||||
{
|
||||
return $this->single;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true whether the type is both a single and a PHP built-in type.
|
||||
*/
|
||||
public function isBuiltin(): bool
|
||||
{
|
||||
return $this->single && Reflection::isBuiltinType($this->types[0]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true whether the type is both a single and a class name.
|
||||
*/
|
||||
public function isClass(): bool
|
||||
{
|
||||
return $this->single && !Reflection::isBuiltinType($this->types[0]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies type compatibility. For example, it checks if a value of a certain type could be passed as a parameter.
|
||||
*/
|
||||
public function allows(string $type): bool
|
||||
{
|
||||
if ($this->types === ['mixed']) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$type = self::fromString($type);
|
||||
|
||||
if ($this->isIntersection()) {
|
||||
if (!$type->isIntersection()) {
|
||||
return false;
|
||||
}
|
||||
return Arrays::every($this->types, function ($currentType) use ($type) {
|
||||
$builtin = Reflection::isBuiltinType($currentType);
|
||||
return Arrays::some($type->types, function ($testedType) use ($currentType, $builtin) {
|
||||
return $builtin
|
||||
? strcasecmp($currentType, $testedType) === 0
|
||||
: is_a($testedType, $currentType, true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$method = $type->isIntersection() ? 'some' : 'every';
|
||||
return Arrays::$method($type->types, function ($testedType) {
|
||||
$builtin = Reflection::isBuiltinType($testedType);
|
||||
return Arrays::some($this->types, function ($currentType) use ($testedType, $builtin) {
|
||||
return $builtin
|
||||
? strcasecmp($currentType, $testedType) === 0
|
||||
: is_a($testedType, $currentType, true);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user