This commit is contained in:
RafficMohammed
2023-02-17 13:25:50 +05:30
parent 2381fd7cf5
commit 67950fc78f
891 changed files with 102300 additions and 138477 deletions

View File

@@ -21,9 +21,8 @@ class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \Iterator
/**
* Transforms array to ArrayHash.
* @param array<T> $array
* @return static
*/
public static function from(array $array, bool $recursive = true)
public static function from(array $array, bool $recursive = true): static
{
$obj = new static;
foreach ($array as $key => $value) {
@@ -38,11 +37,13 @@ class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \Iterator
/**
* Returns an iterator over all items.
* @return \RecursiveArrayIterator<array-key, T>
* @return \Iterator<int|string, T>
*/
public function getIterator(): \RecursiveArrayIterator
public function &getIterator(): \Iterator
{
return new \RecursiveArrayIterator((array) $this);
foreach ((array) $this as $key => $foo) {
yield $key => $this->$key;
}
}

View File

@@ -20,16 +20,14 @@ class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
{
use Nette\SmartObject;
/** @var mixed[] */
private $list = [];
private array $list = [];
/**
* Transforms array to ArrayList.
* @param array<T> $array
* @return static
*/
public static function from(array $array)
public static function from(array $array): static
{
if (!Arrays::isList($array)) {
throw new Nette\InvalidArgumentException('Array is not valid list.');
@@ -43,11 +41,13 @@ class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
/**
* Returns an iterator over all items.
* @return \ArrayIterator<int, T>
* @return \Iterator<int, T>
*/
public function getIterator(): \ArrayIterator
public function &getIterator(): \Iterator
{
return new \ArrayIterator($this->list);
foreach ($this->list as &$item) {
yield $item;
}
}
@@ -86,8 +86,7 @@ class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
* @return T
* @throws Nette\OutOfRangeException
*/
#[\ReturnTypeWillChange]
public function offsetGet($index)
public function offsetGet($index): mixed
{
if (!is_int($index) || $index < 0 || $index >= count($this->list)) {
throw new Nette\OutOfRangeException('Offset invalid or out of range');
@@ -126,7 +125,7 @@ class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
* Prepends a item.
* @param T $value
*/
public function prepend($value): void
public function prepend(mixed $value): void
{
$first = array_slice($this->list, 0, 1);
$this->offsetSet(0, $value);

View File

@@ -30,7 +30,7 @@ class Arrays
* @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)
public static function get(array $array, string|int|array $key, mixed $default = null): mixed
{
foreach (is_array($key) ? $key : [$key] as $k) {
if (is_array($array) && array_key_exists($k, $array)) {
@@ -56,7 +56,7 @@ class Arrays
* @return ?T
* @throws Nette\InvalidArgumentException if traversed item is not an array
*/
public static function &getRef(array &$array, $key)
public static function &getRef(array &$array, string|int|array $key): mixed
{
foreach (is_array($key) ? $key : [$key] as $k) {
if (is_array($array) || $array === null) {
@@ -95,10 +95,8 @@ class Arrays
/**
* Returns zero-indexed position of given array key. Returns null if key is not found.
* @param array-key $key
* @return int|null offset if it is found, null otherwise
*/
public static function getKeyOffset(array $array, $key): ?int
public static function getKeyOffset(array $array, string|int $key): ?int
{
return Helpers::falseToNull(array_search(self::toKey($key), array_keys($array), true));
}
@@ -115,9 +113,8 @@ class Arrays
/**
* Tests an array for the presence of value.
* @param mixed $value
*/
public static function contains(array $array, $value): bool
public static function contains(array $array, mixed $value): bool
{
return in_array($value, $array, true);
}
@@ -129,7 +126,7 @@ class Arrays
* @param array<T> $array
* @return ?T
*/
public static function first(array $array)
public static function first(array $array): mixed
{
return count($array) ? reset($array) : null;
}
@@ -141,7 +138,7 @@ class Arrays
* @param array<T> $array
* @return ?T
*/
public static function last(array $array)
public static function last(array $array): mixed
{
return count($array) ? end($array) : null;
}
@@ -150,9 +147,8 @@ 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 array-key|null $key
*/
public static function insertBefore(array &$array, $key, array $inserted): void
public static function insertBefore(array &$array, string|int|null $key, array $inserted): void
{
$offset = $key === null ? 0 : (int) self::getKeyOffset($array, $key);
$array = array_slice($array, 0, $offset, true)
@@ -164,9 +160,8 @@ 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 array-key|null $key
*/
public static function insertAfter(array &$array, $key, array $inserted): void
public static function insertAfter(array &$array, string|int|null $key, array $inserted): void
{
if ($key === null || ($offset = self::getKeyOffset($array, $key)) === null) {
$offset = count($array) - 1;
@@ -180,10 +175,8 @@ class Arrays
/**
* Renames key in array.
* @param array-key $oldKey
* @param array-key $newKey
*/
public static function renameKey(array &$array, $oldKey, $newKey): bool
public static function renameKey(array &$array, string|int $oldKey, string|int $newKey): bool
{
$offset = self::getKeyOffset($array, $oldKey);
if ($offset === null) {
@@ -208,9 +201,10 @@ class Arrays
array $array,
#[Language('RegExp')]
string $pattern,
int $flags = 0
bool|int $invert = false,
): array
{
$flags = $invert ? PREG_GREP_INVERT : 0;
return Strings::pcre('preg_grep', [$pattern, $array, $flags]);
}
@@ -231,9 +225,8 @@ class Arrays
/**
* Checks if the array is indexed in ascending order of numeric keys from zero, a.k.a list.
* @param mixed $value
*/
public static function isList($value): bool
public static function isList(mixed $value): bool
{
return is_array($value) && (PHP_VERSION_ID < 80100
? !$value || array_keys($value) === range(0, count($value) - 1)
@@ -245,9 +238,8 @@ class Arrays
/**
* Reformats table to associative tree. Path looks like 'field|field[]field->field=field'.
* @param string|string[] $path
* @return array|\stdClass
*/
public static function associate(array $array, $path)
public static function associate(array $array, $path): array|\stdClass
{
$parts = is_array($path)
? $path
@@ -299,9 +291,8 @@ class Arrays
/**
* Normalizes array to associative array. Replace numeric keys with their values, the new value will be $filling.
* @param mixed $filling
*/
public static function normalize(array $array, $filling = null): array
public static function normalize(array $array, mixed $filling = null): array
{
$res = [];
foreach ($array as $k => $v) {
@@ -317,12 +308,11 @@ class Arrays
* or returns $default, if provided.
* @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)
public static function pick(array &$array, string|int $key, mixed $default = null): mixed
{
if (array_key_exists($key, $array)) {
$value = $array[$key];
@@ -421,7 +411,7 @@ class Arrays
* @param T $object
* @return T
*/
public static function toObject(iterable $array, $object)
public static function toObject(iterable $array, object $object): object
{
foreach ($array as $k => $v) {
$object->$k = $v;
@@ -433,10 +423,8 @@ class Arrays
/**
* Converts value to array key.
* @param mixed $value
* @return array-key
*/
public static function toKey($value)
public static function toKey(mixed $value): int|string
{
return key([$value => null]);
}

View File

@@ -20,52 +20,10 @@ final class Callback
{
use Nette\StaticClass;
/**
* @param string|object|callable $callable class, object, callable
* @deprecated use Closure::fromCallable()
*/
public static function closure($callable, ?string $method = null): \Closure
{
trigger_error(__METHOD__ . '() is deprecated, use Closure::fromCallable().', E_USER_DEPRECATED);
try {
return \Closure::fromCallable($method === null ? $callable : [$callable, $method]);
} catch (\TypeError $e) {
throw new Nette\InvalidArgumentException($e->getMessage());
}
}
/**
* Invokes callback.
* @return mixed
* @deprecated
*/
public static function invoke($callable, ...$args)
{
trigger_error(__METHOD__ . '() is deprecated, use native invoking.', E_USER_DEPRECATED);
self::check($callable);
return $callable(...$args);
}
/**
* Invokes callback with an array of parameters.
* @return mixed
* @deprecated
*/
public static function invokeArgs($callable, array $args = [])
{
trigger_error(__METHOD__ . '() is deprecated, use native invoking.', E_USER_DEPRECATED);
self::check($callable);
return $callable(...$args);
}
/**
* Invokes internal PHP function with own error handler.
* @return mixed
*/
public static function invokeSafe(string $function, array $args, callable $onError)
public static function invokeSafe(string $function, array $args, callable $onError): mixed
{
$prev = set_error_handler(function ($severity, $message, $file) use ($onError, &$prev, $function): ?bool {
if ($file === __FILE__) {
@@ -92,17 +50,16 @@ final class Callback
/**
* Checks that $callable is valid PHP callback. Otherwise throws exception. If the $syntax is set to true, only verifies
* that $callable has a valid structure to be used as a callback, but does not verify if the class or method actually exists.
* @param mixed $callable
* @return callable
* @throws Nette\InvalidArgumentException
*/
public static function check($callable, bool $syntax = false)
public static function check(mixed $callable, bool $syntax = false)
{
if (!is_callable($callable, $syntax)) {
throw new Nette\InvalidArgumentException(
$syntax
? 'Given value is not a callable type.'
: sprintf("Callback '%s' is not callable.", self::toString($callable))
: sprintf("Callback '%s' is not callable.", self::toString($callable)),
);
}
@@ -112,15 +69,12 @@ final class Callback
/**
* Converts PHP callback to textual form. Class or method may not exists.
* @param mixed $callable
*/
public static function toString($callable): string
public static function toString(mixed $callable): string
{
if ($callable instanceof \Closure) {
$inner = self::unwrap($callable);
return '{closure' . ($inner instanceof \Closure ? '}' : ' ' . self::toString($inner) . '}');
} elseif (is_string($callable) && $callable[0] === "\0") {
return '{lambda}';
} else {
is_callable(is_object($callable) ? [$callable, '__invoke'] : $callable, true, $textual);
return $textual;
@@ -131,16 +85,15 @@ final class Callback
/**
* Returns reflection for method or function used in PHP callback.
* @param callable $callable type check is escalated to ReflectionException
* @return \ReflectionMethod|\ReflectionFunction
* @throws \ReflectionException if callback is not valid
*/
public static function toReflection($callable): \ReflectionFunctionAbstract
public static function toReflection($callable): \ReflectionMethod|\ReflectionFunction
{
if ($callable instanceof \Closure) {
$callable = self::unwrap($callable);
}
if (is_string($callable) && strpos($callable, '::')) {
if (is_string($callable) && str_contains($callable, '::')) {
return new \ReflectionMethod($callable);
} elseif (is_array($callable)) {
return new \ReflectionMethod($callable[0], $callable[1]);
@@ -163,12 +116,11 @@ final class Callback
/**
* Unwraps closure created by Closure::fromCallable().
* @return callable|array
*/
public static function unwrap(\Closure $closure)
public static function unwrap(\Closure $closure): callable|array
{
$r = new \ReflectionFunction($closure);
if (substr($r->name, -1) === '}') {
if (str_ends_with($r->name, '}')) {
return $closure;
} elseif ($obj = $r->getClosureThis()) {

View File

@@ -32,19 +32,17 @@ class DateTime extends \DateTime implements \JsonSerializable
public const WEEK = 7 * self::DAY;
/** average month in seconds */
public const MONTH = 2629800;
public const MONTH = 2_629_800;
/** average year in seconds */
public const YEAR = 31557600;
public const YEAR = 31_557_600;
/**
* Creates a DateTime object from a string, UNIX timestamp, or other DateTimeInterface object.
* @param string|int|\DateTimeInterface $time
* @return static
* @throws \Exception if the date and time are not valid.
*/
public static function from($time)
public static function from(string|int|\DateTimeInterface|null $time): static
{
if ($time instanceof \DateTimeInterface) {
return new static($time->format('Y-m-d H:i:s.u'), $time->getTimezone());
@@ -64,7 +62,6 @@ class DateTime extends \DateTime implements \JsonSerializable
/**
* Creates DateTime object.
* @return static
* @throws Nette\InvalidArgumentException if the date and time are not valid.
*/
public static function fromParts(
@@ -73,8 +70,9 @@ class DateTime extends \DateTime implements \JsonSerializable
int $day,
int $hour = 0,
int $minute = 0,
float $second = 0.0
) {
float $second = 0.0,
): static
{
$s = sprintf('%04d-%02d-%02d %02d:%02d:%02.5F', $year, $month, $day, $hour, $minute, $second);
if (
!checkdate($month, $day, $year)
@@ -94,22 +92,18 @@ class DateTime extends \DateTime implements \JsonSerializable
/**
* Returns new DateTime object formatted according to the specified format.
* @param string $format The format the $time parameter should be in
* @param string $time
* @param string|\DateTimeZone $timezone (default timezone is used if null is passed)
* @return static|false
*/
#[\ReturnTypeWillChange]
public static function createFromFormat($format, $time, $timezone = null)
public static function createFromFormat(
string $format,
string $time,
string|\DateTimeZone|null $timezone = null,
): static|false
{
if ($timezone === null) {
$timezone = new \DateTimeZone(date_default_timezone_get());
} elseif (is_string($timezone)) {
$timezone = new \DateTimeZone($timezone);
} elseif (!$timezone instanceof \DateTimeZone) {
throw new Nette\InvalidArgumentException('Invalid timezone given');
}
$date = parent::createFromFormat($format, $time, $timezone);
@@ -137,9 +131,8 @@ class DateTime extends \DateTime implements \JsonSerializable
/**
* Creates a copy with a modified time.
* @return static
*/
public function modifyClone(string $modify = '')
public function modifyClone(string $modify = ''): static
{
$dolly = clone $this;
return $modify ? $dolly->modify($modify) : $dolly;

View File

@@ -0,0 +1,69 @@
<?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;
/**
* Represents the file or directory returned by the Finder.
* @internal do not create instances directly
*/
final class FileInfo extends \SplFileInfo
{
private string $relativePath;
public function __construct(string $file, string $relativePath = '')
{
parent::__construct($file);
$this->setInfoClass(static::class);
$this->relativePath = $relativePath;
}
/**
* Returns the relative directory path.
*/
public function getRelativePath(): string
{
return $this->relativePath;
}
/**
* Returns the relative path including file name.
*/
public function getRelativePathname(): string
{
return ($this->relativePath === '' ? '' : $this->relativePath . DIRECTORY_SEPARATOR)
. $this->getBasename();
}
/**
* Returns the contents of the file.
* @throws Nette\IOException
*/
public function read(): string
{
return FileSystem::read($this->getPathname());
}
/**
* Writes the contents to the file.
* @throws Nette\IOException
*/
public function write(string $content): void
{
FileSystem::write($this->getPathname(), $content);
}
}

View File

@@ -20,7 +20,7 @@ final class FileSystem
use Nette\StaticClass;
/**
* Creates a directory if it doesn't exist.
* Creates a directory if it does not exist, including parent directories.
* @throws Nette\IOException on error occurred
*/
public static function createDir(string $dir, int $mode = 0777): void
@@ -30,14 +30,14 @@ final class FileSystem
"Unable to create directory '%s' with mode %s. %s",
self::normalizePath($dir),
decoct($mode),
Helpers::getLastError()
Helpers::getLastError(),
));
}
}
/**
* Copies a file or a directory. Overwrites existing files and directories by default.
* Copies a file or an entire directory. Overwrites existing files and directories by default.
* @throws Nette\IOException on error occurred
* @throws Nette\InvalidStateException if $overwrite is set to false and destination already exists
*/
@@ -64,16 +64,12 @@ final class FileSystem
}
} else {
static::createDir(dirname($target));
if (
($s = @fopen($origin, 'rb'))
&& ($d = @fopen($target, 'wb'))
&& @stream_copy_to_stream($s, $d) === false
) { // @ is escalated to exception
if (@stream_copy_to_stream(static::open($origin, 'rb'), static::open($target, 'wb')) === false) { // @ is escalated to exception
throw new Nette\IOException(sprintf(
"Unable to copy file '%s' to '%s'. %s",
self::normalizePath($origin),
self::normalizePath($target),
Helpers::getLastError()
Helpers::getLastError(),
));
}
}
@@ -81,7 +77,26 @@ final class FileSystem
/**
* Deletes a file or directory if exists.
* Opens file and returns resource.
* @return resource
* @throws Nette\IOException on error occurred
*/
public static function open(string $path, string $mode)
{
$f = @fopen($path, $mode); // @ is escalated to exception
if (!$f) {
throw new Nette\IOException(sprintf(
"Unable to open file '%s'. %s",
self::normalizePath($path),
Helpers::getLastError(),
));
}
return $f;
}
/**
* Deletes a file or an entire directory if exists. If the directory is not empty, it deletes its contents first.
* @throws Nette\IOException on error occurred
*/
public static function delete(string $path): void
@@ -92,7 +107,7 @@ final class FileSystem
throw new Nette\IOException(sprintf(
"Unable to delete '%s'. %s",
self::normalizePath($path),
Helpers::getLastError()
Helpers::getLastError(),
));
}
} elseif (is_dir($path)) {
@@ -104,7 +119,7 @@ final class FileSystem
throw new Nette\IOException(sprintf(
"Unable to delete directory '%s'. %s",
self::normalizePath($path),
Helpers::getLastError()
Helpers::getLastError(),
));
}
}
@@ -135,7 +150,7 @@ final class FileSystem
"Unable to rename file or directory '%s' to '%s'. %s",
self::normalizePath($origin),
self::normalizePath($target),
Helpers::getLastError()
Helpers::getLastError(),
));
}
}
@@ -153,7 +168,7 @@ final class FileSystem
throw new Nette\IOException(sprintf(
"Unable to read file '%s'. %s",
self::normalizePath($file),
Helpers::getLastError()
Helpers::getLastError(),
));
}
@@ -161,6 +176,37 @@ final class FileSystem
}
/**
* Reads the file content line by line. Because it reads continuously as we iterate over the lines,
* it is possible to read files larger than the available memory.
* @return \Generator<int, string>
* @throws Nette\IOException on error occurred
*/
public static function readLines(string $file, bool $stripNewLines = true): \Generator
{
return (function ($f) use ($file, $stripNewLines) {
$counter = 0;
do {
$line = Callback::invokeSafe('fgets', [$f], fn($error) => throw new Nette\IOException(sprintf(
"Unable to read file '%s'. %s",
self::normalizePath($file),
$error,
)));
if ($line === false) {
fclose($f);
break;
}
if ($stripNewLines) {
$line = rtrim($line, "\r\n");
}
yield $counter++ => $line;
} while (true);
})(static::open($file, 'r'));
}
/**
* Writes the string to a file.
* @throws Nette\IOException on error occurred
@@ -172,7 +218,7 @@ final class FileSystem
throw new Nette\IOException(sprintf(
"Unable to write file '%s'. %s",
self::normalizePath($file),
Helpers::getLastError()
Helpers::getLastError(),
));
}
@@ -181,14 +227,15 @@ final class FileSystem
"Unable to chmod file '%s' to mode %s. %s",
self::normalizePath($file),
decoct($mode),
Helpers::getLastError()
Helpers::getLastError(),
));
}
}
/**
* Fixes permissions to a specific file or directory. Directories can be fixed recursively.
* Sets file permissions to `$fileMode` or directory permissions to `$dirMode`.
* Recursively traverses and sets permissions on the entire contents of the directory as well.
* @throws Nette\IOException on error occurred
*/
public static function makeWritable(string $path, int $dirMode = 0777, int $fileMode = 0666): void
@@ -199,7 +246,7 @@ final class FileSystem
"Unable to chmod file '%s' to mode %s. %s",
self::normalizePath($path),
decoct($fileMode),
Helpers::getLastError()
Helpers::getLastError(),
));
}
} elseif (is_dir($path)) {
@@ -212,7 +259,7 @@ final class FileSystem
"Unable to chmod directory '%s' to mode %s. %s",
self::normalizePath($path),
decoct($dirMode),
Helpers::getLastError()
Helpers::getLastError(),
));
}
} else {
@@ -258,4 +305,24 @@ final class FileSystem
{
return self::normalizePath(implode('/', $paths));
}
/**
* Converts backslashes to slashes.
*/
public static function unixSlashes(string $path): string
{
return strtr($path, '\\', '/');
}
/**
* Converts slashes to platform-specific directory separators.
*/
public static function platformSlashes(string $path): string
{
return DIRECTORY_SEPARATOR === '/'
? strtr($path, '\\', '/')
: str_replace(':\\\\', '://', strtr($path, '/', '\\')); // protocol://
}
}

505
vendor/nette/utils/src/Utils/Finder.php vendored Normal file
View File

@@ -0,0 +1,505 @@
<?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;
/**
* Finder allows searching through directory trees using iterator.
*
* Finder::findFiles('*.php')
* ->size('> 10kB')
* ->from('.')
* ->exclude('temp');
*
* @implements \IteratorAggregate<string, FileInfo>
*/
class Finder implements \IteratorAggregate
{
use Nette\SmartObject;
/** @var array<array{string, string}> */
private array $find = [];
/** @var string[] */
private array $in = [];
/** @var \Closure[] */
private array $filters = [];
/** @var \Closure[] */
private array $descentFilters = [];
/** @var array<string|self> */
private array $appends = [];
private bool $childFirst = false;
/** @var ?callable */
private $sort;
private int $maxDepth = -1;
private bool $ignoreUnreadableDirs = true;
/**
* Begins search for files and directories matching mask.
*/
public static function find(string|array $masks): static
{
$masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic
return (new static)->addMask($masks, 'dir')->addMask($masks, 'file');
}
/**
* Begins search for files matching mask.
*/
public static function findFiles(string|array $masks): static
{
$masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic
return (new static)->addMask($masks, 'file');
}
/**
* Begins search for directories matching mask.
*/
public static function findDirectories(string|array $masks): static
{
$masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic
return (new static)->addMask($masks, 'dir');
}
/**
* Finds files matching the specified masks.
*/
public function files(string|array $masks): static
{
return $this->addMask((array) $masks, 'file');
}
/**
* Finds directories matching the specified masks.
*/
public function directories(string|array $masks): static
{
return $this->addMask((array) $masks, 'dir');
}
private function addMask(array $masks, string $mode): static
{
foreach ($masks as $mask) {
$mask = FileSystem::unixSlashes($mask);
if ($mode === 'dir') {
$mask = rtrim($mask, '/');
}
if ($mask === '' || ($mode === 'file' && str_ends_with($mask, '/'))) {
throw new Nette\InvalidArgumentException("Invalid mask '$mask'");
}
if (str_starts_with($mask, '**/')) {
$mask = substr($mask, 3);
}
$this->find[] = [$mask, $mode];
}
return $this;
}
/**
* Searches in the given directories. Wildcards are allowed.
*/
public function in(string|array $paths): static
{
$paths = is_array($paths) ? $paths : func_get_args(); // compatibility with variadic
$this->addLocation($paths, '');
return $this;
}
/**
* Searches recursively from the given directories. Wildcards are allowed.
*/
public function from(string|array $paths): static
{
$paths = is_array($paths) ? $paths : func_get_args(); // compatibility with variadic
$this->addLocation($paths, '/**');
return $this;
}
private function addLocation(array $paths, string $ext): void
{
foreach ($paths as $path) {
if ($path === '') {
throw new Nette\InvalidArgumentException("Invalid directory '$path'");
}
$path = rtrim(FileSystem::unixSlashes($path), '/');
$this->in[] = $path . $ext;
}
}
/**
* Lists directory's contents before the directory itself. By default, this is disabled.
*/
public function childFirst(bool $state = true): static
{
$this->childFirst = $state;
return $this;
}
/**
* Ignores unreadable directories. By default, this is enabled.
*/
public function ignoreUnreadableDirs(bool $state = true): static
{
$this->ignoreUnreadableDirs = $state;
return $this;
}
/**
* Set a compare function for sorting directory entries. The function will be called to sort entries from the same directory.
* @param callable(FileInfo, FileInfo): int $callback
*/
public function sortBy(callable $callback): static
{
$this->sort = $callback;
return $this;
}
/**
* Sorts files in each directory naturally by name.
*/
public function sortByName(): static
{
$this->sort = fn(FileInfo $a, FileInfo $b): int => strnatcmp($a->getBasename(), $b->getBasename());
return $this;
}
/**
* Adds the specified paths or appends a new finder that returns.
*/
public function append(string|array|null $paths = null): static
{
if ($paths === null) {
return $this->appends[] = new static;
}
$this->appends = array_merge($this->appends, (array) $paths);
return $this;
}
/********************* filtering ****************d*g**/
/**
* Skips entries that matches the given masks relative to the ones defined with the in() or from() methods.
*/
public function exclude(string|array $masks): static
{
$masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic
foreach ($masks as $mask) {
$mask = FileSystem::unixSlashes($mask);
if (!preg_match('~^/?(\*\*/)?(.+)(/\*\*|/\*|/|)$~D', $mask, $m)) {
throw new Nette\InvalidArgumentException("Invalid mask '$mask'");
}
$end = $m[3];
$re = $this->buildPattern($m[2]);
$filter = fn(FileInfo $file): bool => ($end && !$file->isDir())
|| !preg_match($re, FileSystem::unixSlashes($file->getRelativePathname()));
$this->descentFilter($filter);
if ($end !== '/*') {
$this->filter($filter);
}
}
return $this;
}
/**
* Yields only entries which satisfy the given filter.
* @param callable(FileInfo): bool $callback
*/
public function filter(callable $callback): static
{
$this->filters[] = \Closure::fromCallable($callback);
return $this;
}
/**
* It descends only to directories that match the specified filter.
* @param callable(FileInfo): bool $callback
*/
public function descentFilter(callable $callback): static
{
$this->descentFilters[] = \Closure::fromCallable($callback);
return $this;
}
/**
* Sets the maximum depth of entries.
*/
public function limitDepth(?int $depth): static
{
$this->maxDepth = $depth ?? -1;
return $this;
}
/**
* Restricts the search by size. $operator accepts "[operator] [size] [unit]" example: >=10kB
*/
public function size(string $operator, ?int $size = null): static
{
if (func_num_args() === 1) { // in $operator is predicate
if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?((?:\d*\.)?\d+)\s*(K|M|G|)B?$#Di', $operator, $matches)) {
throw new Nette\InvalidArgumentException('Invalid size predicate format.');
}
[, $operator, $size, $unit] = $matches;
$units = ['' => 1, 'k' => 1e3, 'm' => 1e6, 'g' => 1e9];
$size *= $units[strtolower($unit)];
$operator = $operator ?: '=';
}
return $this->filter(fn(FileInfo $file): bool => !$file->isFile() || Helpers::compare($file->getSize(), $operator, $size));
}
/**
* Restricts the search by modified time. $operator accepts "[operator] [date]" example: >1978-01-23
*/
public function date(string $operator, string|int|\DateTimeInterface|null $date = null): static
{
if (func_num_args() === 1) { // in $operator is predicate
if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?(.+)$#Di', $operator, $matches)) {
throw new Nette\InvalidArgumentException('Invalid date predicate format.');
}
[, $operator, $date] = $matches;
$operator = $operator ?: '=';
}
$date = DateTime::from($date)->format('U');
return $this->filter(fn(FileInfo $file): bool => !$file->isFile() || Helpers::compare($file->getMTime(), $operator, $date));
}
/********************* iterator generator ****************d*g**/
/**
* Returns an array with all found files and directories.
*/
public function collect(): array
{
return iterator_to_array($this->getIterator());
}
/** @return \Generator<string, FileInfo> */
public function getIterator(): \Generator
{
$plan = $this->buildPlan();
foreach ($plan as $dir => $searches) {
yield from $this->traverseDir($dir, $searches);
}
foreach ($this->appends as $item) {
if ($item instanceof self) {
yield from $item->getIterator();
} else {
$item = FileSystem::platformSlashes($item);
yield $item => new FileInfo($item);
}
}
}
/**
* @param array<\stdClass{pattern: string, mode: string, recursive: bool}> $searches
* @param string[] $subdirs
* @return \Generator<string, FileInfo>
*/
private function traverseDir(string $dir, array $searches, array $subdirs = []): \Generator
{
if ($this->maxDepth >= 0 && count($subdirs) > $this->maxDepth) {
return;
} elseif (!is_dir($dir)) {
throw new Nette\InvalidStateException("Directory '$dir' not found.");
}
try {
$pathNames = new \FilesystemIterator($dir, \FilesystemIterator::FOLLOW_SYMLINKS | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::UNIX_PATHS);
} catch (\UnexpectedValueException $e) {
if ($this->ignoreUnreadableDirs) {
return;
} else {
throw new Nette\InvalidStateException($e->getMessage());
}
}
$files = $this->convertToFiles($pathNames, implode('/', $subdirs), FileSystem::isAbsolute($dir));
if ($this->sort) {
$files = iterator_to_array($files);
usort($files, $this->sort);
}
foreach ($files as $file) {
$pathName = $file->getPathname();
$cache = $subSearch = [];
if ($file->isDir()) {
foreach ($searches as $search) {
if ($search->recursive && $this->proveFilters($this->descentFilters, $file, $cache)) {
$subSearch[] = $search;
}
}
}
if ($this->childFirst && $subSearch) {
yield from $this->traverseDir($pathName, $subSearch, array_merge($subdirs, [$file->getBasename()]));
}
$relativePathname = FileSystem::unixSlashes($file->getRelativePathname());
foreach ($searches as $search) {
if (
$file->getType() === $search->mode
&& preg_match($search->pattern, $relativePathname)
&& $this->proveFilters($this->filters, $file, $cache)
) {
yield $pathName => $file;
break;
}
}
if (!$this->childFirst && $subSearch) {
yield from $this->traverseDir($pathName, $subSearch, array_merge($subdirs, [$file->getBasename()]));
}
}
}
private function convertToFiles(iterable $pathNames, string $relativePath, bool $absolute): \Generator
{
foreach ($pathNames as $pathName) {
if (!$absolute) {
$pathName = preg_replace('~\.?/~A', '', $pathName);
}
$pathName = FileSystem::platformSlashes($pathName);
yield new FileInfo($pathName, $relativePath);
}
}
private function proveFilters(array $filters, FileInfo $file, array &$cache): bool
{
foreach ($filters as $filter) {
$res = &$cache[spl_object_id($filter)];
$res ??= $filter($file);
if (!$res) {
return false;
}
}
return true;
}
/** @return array<string, array<\stdClass{pattern: string, mode: string, recursive: bool}>> */
private function buildPlan(): array
{
$plan = $dirCache = [];
foreach ($this->find as [$mask, $mode]) {
$splits = [];
if (FileSystem::isAbsolute($mask)) {
if ($this->in) {
throw new Nette\InvalidStateException("You cannot combine the absolute path in the mask '$mask' and the directory to search '{$this->in[0]}'.");
}
$splits[] = self::splitRecursivePart($mask);
} else {
foreach ($this->in ?: ['.'] as $in) {
$in = strtr($in, ['[' => '[[]', ']' => '[]]']); // in path, do not treat [ and ] as a pattern by glob()
$splits[] = self::splitRecursivePart($in . '/' . $mask);
}
}
foreach ($splits as [$base, $rest, $recursive]) {
$base = $base === '' ? '.' : $base;
$dirs = $dirCache[$base] ??= strpbrk($base, '*?[')
? glob($base, GLOB_NOSORT | GLOB_ONLYDIR | GLOB_NOESCAPE)
: [strtr($base, ['[[]' => '[', '[]]' => ']'])]; // unescape [ and ]
$search = (object) ['pattern' => $this->buildPattern($rest), 'mode' => $mode, 'recursive' => $recursive];
foreach ($dirs as $dir) {
$plan[$dir][] = $search;
}
}
}
return $plan;
}
/**
* Since glob() does not know ** wildcard, we divide the path into a part for glob and a part for manual traversal.
*/
private static function splitRecursivePart(string $path): array
{
$a = strrpos($path, '/');
$parts = preg_split('~(?<=^|/)\*\*($|/)~', substr($path, 0, $a + 1), 2);
return isset($parts[1])
? [$parts[0], $parts[1] . substr($path, $a + 1), true]
: [$parts[0], substr($path, $a + 1), false];
}
/**
* Converts wildcards to regular expression.
*/
private function buildPattern(string $mask): string
{
if ($mask === '*') {
return '##';
} elseif (str_starts_with($mask, './')) {
$anchor = '^';
$mask = substr($mask, 2);
} else {
$anchor = '(?:^|/)';
}
$pattern = strtr(
preg_quote($mask, '#'),
[
'\*\*/' => '(.+/)?',
'\*' => '[^/]*',
'\?' => '[^/]',
'\[\!' => '[^',
'\[' => '[',
'\]' => ']',
'\-' => '-',
],
);
return '#' . $anchor . $pattern . '$#D' . (defined('PHP_WINDOWS_VERSION_BUILD') ? 'i' : '');
}
}

View File

@@ -45,10 +45,8 @@ class Helpers
/**
* Converts false to null, does not change other values.
* @param mixed $value
* @return mixed
*/
public static function falseToNull($value)
public static function falseToNull(mixed $value): mixed
{
return $value === false ? null : $value;
}
@@ -56,12 +54,8 @@ class Helpers
/**
* Returns value clamped to the inclusive range of min and max.
* @param int|float $value
* @param int|float $min
* @param int|float $max
* @return int|float
*/
public static function clamp($value, $min, $max)
public static function clamp(int|float $value, int|float $min, int|float $max): int|float
{
if ($min > $max) {
throw new Nette\InvalidArgumentException("Minimum ($min) is not less than maximum ($max).");
@@ -88,4 +82,23 @@ class Helpers
return $best;
}
/**
* Compares two values in the same way that PHP does. Recognizes operators: >, >=, <, <=, =, ==, ===, !=, !==, <>
*/
public static function compare(mixed $left, string $operator, mixed $right): bool
{
return match ($operator) {
'>' => $left > $right,
'>=' => $left >= $right,
'<' => $left < $right,
'<=' => $left <= $right,
'=', '==' => $left == $right,
'===' => $left === $right,
'!=', '<>' => $left != $right,
'!==' => $left !== $right,
default => throw new Nette\InvalidArgumentException("Unknown operator '$operator'"),
};
}
}

View File

@@ -238,10 +238,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/** @var array<string, mixed> element's attributes */
public $attrs = [];
/** @var bool use XHTML syntax? */
public static $xhtml = false;
/** @var array<string, int> void elements */
/** void elements */
public static $emptyElements = [
'img' => 1, 'hr' => 1, 'br' => 1, 'input' => 1, 'meta' => 1, 'area' => 1, 'embed' => 1, 'keygen' => 1,
'source' => 1, 'base' => 1, 'col' => 1, 'link' => 1, 'param' => 1, 'basefont' => 1, 'frame' => 1,
@@ -251,19 +248,17 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/** @var array<int, HtmlStringable|string> nodes */
protected $children = [];
/** @var string element's name */
private $name;
/** element's name */
private string $name = '';
/** @var bool is element empty? */
private $isEmpty;
private bool $isEmpty = false;
/**
* Constructs new HTML element.
* @param array|string $attrs element's attributes or plain text content
* @return static
*/
public static function el(?string $name = null, $attrs = null)
public static function el(?string $name = null, array|string|null $attrs = null): static
{
$el = new static;
$parts = explode(' ', (string) $name, 2);
@@ -289,7 +284,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Returns an object representing HTML text.
*/
public static function fromHtml(string $html): self
public static function fromHtml(string $html): static
{
return (new static)->setHtml($html);
}
@@ -298,7 +293,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Returns an object representing plain text.
*/
public static function fromText(string $text): self
public static function fromText(string $text): static
{
return (new static)->setText($text);
}
@@ -333,9 +328,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Changes element's name.
* @return static
*/
final public function setName(string $name, ?bool $isEmpty = null)
final public function setName(string $name, ?bool $isEmpty = null): static
{
$this->name = $name;
$this->isEmpty = $isEmpty ?? isset(static::$emptyElements[$name]);
@@ -363,9 +357,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Sets multiple attributes.
* @return static
*/
public function addAttributes(array $attrs)
public function addAttributes(array $attrs): static
{
$this->attrs = array_merge($this->attrs, $attrs);
return $this;
@@ -374,11 +367,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Appends value to element's attribute.
* @param mixed $value
* @param mixed $option
* @return static
*/
public function appendAttribute(string $name, $value, $option = true)
public function appendAttribute(string $name, mixed $value, mixed $option = true): static
{
if (is_array($value)) {
$prev = isset($this->attrs[$name]) ? (array) $this->attrs[$name] : [];
@@ -400,10 +390,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Sets element's attribute.
* @param mixed $value
* @return static
*/
public function setAttribute(string $name, $value)
public function setAttribute(string $name, mixed $value): static
{
$this->attrs[$name] = $value;
return $this;
@@ -412,9 +400,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Returns element's attribute.
* @return mixed
*/
public function getAttribute(string $name)
public function getAttribute(string $name): mixed
{
return $this->attrs[$name] ?? null;
}
@@ -422,9 +409,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Unsets element's attribute.
* @return static
*/
public function removeAttribute(string $name)
public function removeAttribute(string $name): static
{
unset($this->attrs[$name]);
return $this;
@@ -433,9 +419,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Unsets element's attributes.
* @return static
*/
public function removeAttributes(array $attributes)
public function removeAttributes(array $attributes): static
{
foreach ($attributes as $name) {
unset($this->attrs[$name]);
@@ -447,9 +432,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Overloaded setter for element's attribute.
* @param mixed $value
*/
final public function __set(string $name, $value): void
final public function __set(string $name, mixed $value): void
{
$this->attrs[$name] = $value;
}
@@ -457,9 +441,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Overloaded getter for element's attribute.
* @return mixed
*/
final public function &__get(string $name)
final public function &__get(string $name): mixed
{
return $this->attrs[$name];
}
@@ -485,9 +468,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Overloaded setter for element's attribute.
* @return mixed
*/
final public function __call(string $m, array $args)
final public function __call(string $m, array $args): mixed
{
$p = substr($m, 0, 3);
if ($p === 'get' || $p === 'set' || $p === 'add') {
@@ -516,9 +498,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Special setter for element's attribute.
* @return static
*/
final public function href(string $path, ?array $query = null)
final public function href(string $path, array $query = []): static
{
if ($query) {
$query = http_build_query($query, '', '&');
@@ -534,10 +515,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Setter for data-* attributes. Booleans are converted to 'true' resp. 'false'.
* @param mixed $value
* @return static
*/
public function data(string $name, $value = null)
public function data(string $name, mixed $value = null): static
{
if (func_num_args() === 1) {
$this->attrs['data'] = $name;
@@ -553,10 +532,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Sets element's HTML content.
* @param HtmlStringable|string $html
* @return static
*/
final public function setHtml($html)
final public function setHtml(mixed $html): static
{
$this->children = [(string) $html];
return $this;
@@ -574,10 +551,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Sets element's textual content.
* @param HtmlStringable|string|int|float $text
* @return static
*/
final public function setText($text)
final public function setText(mixed $text): static
{
if (!$text instanceof HtmlStringable) {
$text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8');
@@ -599,10 +574,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Adds new element's child.
* @param HtmlStringable|string $child Html node or raw HTML string
* @return static
*/
final public function addHtml($child)
final public function addHtml(mixed $child): static
{
return $this->insert(null, $child);
}
@@ -610,10 +583,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Appends plain-text string to element content.
* @param HtmlStringable|string|int|float $text
* @return static
*/
public function addText($text)
public function addText(mixed $text): static
{
if (!$text instanceof HtmlStringable) {
$text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8');
@@ -625,10 +596,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Creates and adds a new Html child.
* @param array|string $attrs element's attributes or raw HTML string
* @return static created element
*/
final public function create(string $name, $attrs = null)
final public function create(string $name, array|string|null $attrs = null): static
{
$this->insert(null, $child = static::el($name, $attrs));
return $child;
@@ -637,10 +606,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Inserts child node.
* @param HtmlStringable|string $child Html node or raw HTML string
* @return static
*/
public function insert(?int $index, $child, bool $replace = false)
public function insert(?int $index, HtmlStringable|string $child, bool $replace = false): static
{
$child = $child instanceof self ? $child : (string) $child;
if ($index === null) { // append
@@ -668,10 +635,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Returns child node (\ArrayAccess implementation).
* @param int $index
* @return HtmlStringable|string
*/
#[\ReturnTypeWillChange]
final public function offsetGet($index)
final public function offsetGet($index): HtmlStringable|string
{
return $this->children[$index];
}
@@ -771,16 +736,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
final public function __toString(): string
{
try {
return $this->render();
} catch (\Throwable $e) {
if (PHP_VERSION_ID >= 70400) {
throw $e;
}
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
return '';
}
return $this->render();
}
@@ -790,7 +746,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
final public function startTag(): string
{
return $this->name
? '<' . $this->name . $this->attributes() . (static::$xhtml && $this->isEmpty ? ' />' : '>')
? '<' . $this->name . $this->attributes() . '>'
: '';
}
@@ -821,11 +777,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
continue;
} elseif ($value === true) {
if (static::$xhtml) {
$s .= ' ' . $key . '="' . $key . '"';
} else {
$s .= ' ' . $key;
}
$s .= ' ' . $key;
continue;
@@ -857,14 +809,14 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
$value = (string) $value;
}
$q = strpos($value, '"') === false ? '"' : "'";
$q = str_contains($value, '"') ? "'" : '"';
$s .= ' ' . $key . '=' . $q
. str_replace(
['&', $q, '<'],
['&amp;', $q === '"' ? '&quot;' : '&#39;', self::$xhtml ? '&lt;' : '<'],
$value
['&amp;', $q === '"' ? '&quot;' : '&#39;', '<'],
$value,
)
. (strpos($value, '`') !== false && strpbrk($value, ' <>"\'') === false ? ' ' : '')
. (str_contains($value, '`') && strpbrk($value, ' <>"\'') === false ? ' ' : '')
. $q;
}

View File

@@ -92,26 +92,44 @@ use Nette;
* @method array ttfText($size, $angle, $x, $y, $color, string $fontfile, string $text)
* @property-read int $width
* @property-read int $height
* @property-read resource|\GdImage $imageResource
* @property-read \GdImage $imageResource
*/
class Image
{
use Nette\SmartObject;
/** {@link resize()} only shrinks images */
public const SHRINK_ONLY = 0b0001;
/** Prevent from getting resized to a bigger size than the original */
public const ShrinkOnly = 0b0001;
/** {@link resize()} will ignore aspect ratio */
public const STRETCH = 0b0010;
/** Resizes to a specified width and height without keeping aspect ratio */
public const Stretch = 0b0010;
/** {@link resize()} fits in given area so its dimensions are less than or equal to the required dimensions */
public const FIT = 0b0000;
/** Resizes to fit into a specified width and height and preserves aspect ratio */
public const OrSmaller = 0b0000;
/** {@link resize()} fills given area so its dimensions are greater than or equal to the required dimensions */
public const FILL = 0b0100;
/** Resizes while bounding the smaller dimension to the specified width or height and preserves aspect ratio */
public const OrBigger = 0b0100;
/** {@link resize()} fills given area exactly */
public const EXACT = 0b1000;
/** Resizes to the smallest possible size to completely cover specified width and height and reserves aspect ratio */
public const Cover = 0b1000;
/** @deprecated use Image::ShrinkOnly */
public const SHRINK_ONLY = self::ShrinkOnly;
/** @deprecated use Image::Stretch */
public const STRETCH = self::Stretch;
/** @deprecated use Image::OrSmaller */
public const FIT = self::OrSmaller;
/** @deprecated use Image::OrBigger */
public const FILL = self::OrBigger;
/** @deprecated use Image::Cover */
public const EXACT = self::Cover;
/** @deprecated use Image::EmptyGIF */
public const EMPTY_GIF = self::EmptyGIF;
/** image types */
public const
@@ -122,12 +140,11 @@ class Image
AVIF = 19, // IMAGETYPE_AVIF,
BMP = IMAGETYPE_BMP;
public const EMPTY_GIF = "GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;";
public const EmptyGIF = "GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;";
private const Formats = [self::JPEG => 'jpeg', self::PNG => 'png', self::GIF => 'gif', self::WEBP => 'webp', self::AVIF => 'avif', self::BMP => 'bmp'];
/** @var resource|\GdImage */
private $image;
private \GdImage $image;
/**
@@ -148,9 +165,8 @@ class Image
* Reads an image from a file and returns its type in $type.
* @throws Nette\NotSupportedException if gd extension is not loaded
* @throws UnknownImageFileException if file not found or file type is not known
* @return static
*/
public static function fromFile(string $file, ?int &$type = null)
public static function fromFile(string $file, ?int &$type = null): static
{
if (!extension_loaded('gd')) {
throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
@@ -167,11 +183,10 @@ class Image
/**
* Reads an image from a string and returns its type in $type.
* @return static
* @throws Nette\NotSupportedException if gd extension is not loaded
* @throws ImageException
*/
public static function fromString(string $s, ?int &$type = null)
public static function fromString(string $s, ?int &$type = null): static
{
if (!extension_loaded('gd')) {
throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
@@ -186,7 +201,7 @@ class Image
}
private static function invokeSafe(string $func, string $arg, string $message, string $callee): self
private static function invokeSafe(string $func, string $arg, string $message, string $callee): static
{
$errors = [];
$res = Callback::invokeSafe($func, [$arg], function (string $message) use (&$errors): void {
@@ -205,10 +220,9 @@ class Image
/**
* Creates a new true color image of the given dimensions. The default color is black.
* @return static
* @throws Nette\NotSupportedException if gd extension is not loaded
*/
public static function fromBlank(int $width, int $height, ?array $color = null)
public static function fromBlank(int $width, int $height, ?array $color = null): static
{
if (!extension_loaded('gd')) {
throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
@@ -290,9 +304,8 @@ class Image
/**
* Wraps GD image.
* @param resource|\GdImage $image
*/
public function __construct($image)
public function __construct(\GdImage $image)
{
$this->setImageResource($image);
imagesavealpha($image, true);
@@ -319,15 +332,9 @@ class Image
/**
* Sets image resource.
* @param resource|\GdImage $image
* @return static
*/
protected function setImageResource($image)
protected function setImageResource(\GdImage $image): static
{
if (!$image instanceof \GdImage && !(is_resource($image) && get_resource_type($image) === 'gd')) {
throw new Nette\InvalidArgumentException('Image is not valid.');
}
$this->image = $image;
return $this;
}
@@ -335,27 +342,24 @@ class Image
/**
* Returns image GD resource.
* @return resource|\GdImage
*/
public function getImageResource()
public function getImageResource(): \GdImage
{
return $this->image;
}
/**
* Scales an image.
* @param int|string|null $width in pixels or percent
* @param int|string|null $height in pixels or percent
* @return static
* Scales an image. Width and height accept pixels or percent.
* @param self::OrSmaller|self::OrBigger|self::Stretch|self::Cover|self::ShrinkOnly $mode
*/
public function resize($width, $height, int $flags = self::FIT)
public function resize(int|string|null $width, int|string|null $height, int $mode = self::OrSmaller): static
{
if ($flags & self::EXACT) {
return $this->resize($width, $height, self::FILL)->crop('50%', '50%', $width, $height);
if ($mode & self::Cover) {
return $this->resize($width, $height, self::OrBigger)->crop('50%', '50%', $width, $height);
}
[$newWidth, $newHeight] = static::calculateSize($this->getWidth(), $this->getHeight(), $width, $height, $flags);
[$newWidth, $newHeight] = static::calculateSize($this->getWidth(), $this->getHeight(), $width, $height, $mode);
if ($newWidth !== $this->getWidth() || $newHeight !== $this->getHeight()) { // resize
$newImage = static::fromBlank($newWidth, $newHeight, self::rgb(0, 0, 0, 127))->getImageResource();
@@ -369,7 +373,7 @@ class Image
$newWidth,
$newHeight,
$this->getWidth(),
$this->getHeight()
$this->getHeight(),
);
$this->image = $newImage;
}
@@ -383,16 +387,15 @@ class Image
/**
* Calculates dimensions of resized image.
* @param int|string|null $newWidth in pixels or percent
* @param int|string|null $newHeight in pixels or percent
* Calculates dimensions of resized image. Width and height accept pixels or percent.
* @param self::OrSmaller|self::OrBigger|self::Stretch|self::Cover|self::ShrinkOnly $mode
*/
public static function calculateSize(
int $srcWidth,
int $srcHeight,
$newWidth,
$newHeight,
int $flags = self::FIT
int $mode = self::OrSmaller,
): array
{
if ($newWidth === null) {
@@ -406,19 +409,19 @@ class Image
if ($newHeight === null) {
} elseif (self::isPercent($newHeight)) {
$newHeight = (int) round($srcHeight / 100 * abs($newHeight));
$flags |= empty($percents) ? 0 : self::STRETCH;
$mode |= empty($percents) ? 0 : self::Stretch;
} else {
$newHeight = abs($newHeight);
}
if ($flags & self::STRETCH) { // non-proportional
if ($mode & self::Stretch) { // non-proportional
if (!$newWidth || !$newHeight) {
throw new Nette\InvalidArgumentException('For stretching must be both width and height specified.');
}
if ($flags & self::SHRINK_ONLY) {
$newWidth = (int) round($srcWidth * min(1, $newWidth / $srcWidth));
$newHeight = (int) round($srcHeight * min(1, $newHeight / $srcHeight));
if ($mode & self::ShrinkOnly) {
$newWidth = min($srcWidth, $newWidth);
$newHeight = min($srcHeight, $newHeight);
}
} else { // proportional
if (!$newWidth && !$newHeight) {
@@ -434,11 +437,11 @@ class Image
$scale[] = $newHeight / $srcHeight;
}
if ($flags & self::FILL) {
if ($mode & self::OrBigger) {
$scale = [max($scale)];
}
if ($flags & self::SHRINK_ONLY) {
if ($mode & self::ShrinkOnly) {
$scale[] = 1;
}
@@ -452,14 +455,9 @@ class Image
/**
* Crops image.
* @param int|string $left in pixels or percent
* @param int|string $top in pixels or percent
* @param int|string $width in pixels or percent
* @param int|string $height in pixels or percent
* @return static
* Crops image. Arguments accepts pixels or percent.
*/
public function crop($left, $top, $width, $height)
public function crop(int|string $left, int|string $top, int|string $width, int|string $height): static
{
[$r['x'], $r['y'], $r['width'], $r['height']]
= static::calculateCutout($this->getWidth(), $this->getHeight(), $left, $top, $width, $height);
@@ -477,13 +475,16 @@ class Image
/**
* Calculates dimensions of cutout in image.
* @param int|string $left in pixels or percent
* @param int|string $top in pixels or percent
* @param int|string $newWidth in pixels or percent
* @param int|string $newHeight in pixels or percent
* Calculates dimensions of cutout in image. Arguments accepts pixels or percent.
*/
public static function calculateCutout(int $srcWidth, int $srcHeight, $left, $top, $newWidth, $newHeight): array
public static function calculateCutout(
int $srcWidth,
int $srcHeight,
int|string $left,
int|string $top,
int|string $newWidth,
int|string $newHeight,
): array
{
if (self::isPercent($newWidth)) {
$newWidth = (int) round($srcWidth / 100 * $newWidth);
@@ -519,9 +520,8 @@ class Image
/**
* Sharpens image a little bit.
* @return static
*/
public function sharpen()
public function sharpen(): static
{
imageconvolution($this->image, [ // my magic numbers ;)
[-1, -1, -1],
@@ -533,13 +533,10 @@ class Image
/**
* Puts another image into this image.
* @param int|string $left in pixels or percent
* @param int|string $top in pixels or percent
* Puts another image into this image. Left and top accepts pixels or percent.
* @param int $opacity 0..100
* @return static
*/
public function place(self $image, $left = 0, $top = 0, int $opacity = 100)
public function place(self $image, int|string $left = 0, int|string $top = 0, int $opacity = 100): static
{
$opacity = max(0, min(100, $opacity));
if ($opacity === 0) {
@@ -591,7 +588,7 @@ class Image
0,
0,
$width,
$height
$height,
);
return $this;
}
@@ -603,7 +600,7 @@ class Image
*/
public function save(string $file, ?int $quality = null, ?int $type = null): void
{
$type = $type ?? self::extensionToType(pathinfo($file, PATHINFO_EXTENSION));
$type ??= self::extensionToType(pathinfo($file, PATHINFO_EXTENSION));
$this->output($type, $quality, $file);
}
@@ -613,7 +610,7 @@ class Image
*/
public function toString(int $type = self::JPEG, ?int $quality = null): string
{
return Helpers::capture(function () use ($type, $quality) {
return Helpers::capture(function () use ($type, $quality): void {
$this->output($type, $quality);
});
}
@@ -624,16 +621,7 @@ class Image
*/
public function __toString(): string
{
try {
return $this->toString();
} catch (\Throwable $e) {
if (func_num_args() || PHP_VERSION_ID >= 70400) {
throw $e;
}
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
return '';
}
return $this->toString();
}
@@ -695,10 +683,9 @@ class Image
/**
* Call to undefined method.
* @return mixed
* @throws Nette\MemberAccessException
*/
public function __call(string $name, array $args)
public function __call(string $name, array $args): mixed
{
$function = 'image' . $name;
if (!function_exists($function)) {
@@ -715,19 +702,19 @@ class Image
$value['red'],
$value['green'],
$value['blue'],
$value['alpha']
$value['alpha'],
) ?: imagecolorresolvealpha(
$this->image,
$value['red'],
$value['green'],
$value['blue'],
$value['alpha']
$value['alpha'],
);
}
}
$res = $function($this->image, ...$args);
return $res instanceof \GdImage || (is_resource($res) && get_resource_type($res) === 'gd')
return $res instanceof \GdImage
? $this->setImageResource($res)
: $res;
}
@@ -741,12 +728,9 @@ class Image
}
/**
* @param int|string $num in pixels or percent
*/
private static function isPercent(&$num): bool
private static function isPercent(int|string &$num): bool
{
if (is_string($num) && substr($num, -1) === '%') {
if (is_string($num) && str_ends_with($num, '%')) {
$num = (float) substr($num, 0, -1);
return true;
} elseif (is_int($num) || $num === (string) (int) $num) {

View File

@@ -19,22 +19,39 @@ final class Json
{
use Nette\StaticClass;
/** @deprecated use Json::decode(..., forceArrays: true) */
public const FORCE_ARRAY = JSON_OBJECT_AS_ARRAY;
/** @deprecated use Json::encode(..., pretty: true) */
public const PRETTY = JSON_PRETTY_PRINT;
/** @deprecated use Json::encode(..., asciiSafe: true) */
public const ESCAPE_UNICODE = 1 << 19;
/**
* Converts value to JSON format. The flag can be Json::PRETTY, which formats JSON for easier reading and clarity,
* and Json::ESCAPE_UNICODE for ASCII output.
* @param mixed $value
* Converts value to JSON format. Use $pretty for easier reading and clarity, $asciiSafe for ASCII output
* and $htmlSafe for HTML escaping, $forceObjects enforces the encoding of non-associateve arrays as objects.
* @throws JsonException
*/
public static function encode($value, int $flags = 0): string
public static function encode(
mixed $value,
bool|int $pretty = false,
bool $asciiSafe = false,
bool $htmlSafe = false,
bool $forceObjects = false,
): string
{
$flags = ($flags & self::ESCAPE_UNICODE ? 0 : JSON_UNESCAPED_UNICODE)
| JSON_UNESCAPED_SLASHES
| ($flags & ~self::ESCAPE_UNICODE)
if (is_int($pretty)) { // back compatibility
$flags = ($pretty & self::ESCAPE_UNICODE ? 0 : JSON_UNESCAPED_UNICODE) | ($pretty & ~self::ESCAPE_UNICODE);
} else {
$flags = ($asciiSafe ? 0 : JSON_UNESCAPED_UNICODE)
| ($pretty ? JSON_PRETTY_PRINT : 0)
| ($forceObjects ? JSON_FORCE_OBJECT : 0)
| ($htmlSafe ? JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG : 0);
}
$flags |= JSON_UNESCAPED_SLASHES
| (defined('JSON_PRESERVE_ZERO_FRACTION') ? JSON_PRESERVE_ZERO_FRACTION : 0); // since PHP 5.6.6 & PECL JSON-C 1.3.7
$json = json_encode($value, $flags);
@@ -47,13 +64,17 @@ final class Json
/**
* Parses JSON to PHP value. The flag can be Json::FORCE_ARRAY, which forces an array instead of an object as the return value.
* @return mixed
* Parses JSON to PHP value. The $forceArrays enforces the decoding of objects as arrays.
* @throws JsonException
*/
public static function decode(string $json, int $flags = 0)
public static function decode(string $json, bool|int $forceArrays = false): mixed
{
$value = json_decode($json, null, 512, $flags | JSON_BIGINT_AS_STRING);
$flags = is_int($forceArrays) // back compatibility
? $forceArrays
: ($forceArrays ? JSON_OBJECT_AS_ARRAY : 0);
$flags |= JSON_BIGINT_AS_STRING;
$value = json_decode($json, flags: $flags);
if ($error = json_last_error()) {
throw new JsonException(json_last_error_msg(), $error);
}

View File

@@ -15,6 +15,7 @@ use Nette\MemberAccessException;
/**
* Nette\SmartObject helpers.
* @internal
*/
final class ObjectHelpers
{
@@ -28,8 +29,8 @@ final class ObjectHelpers
{
$rc = new \ReflectionClass($class);
$hint = self::getSuggestion(array_merge(
array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }),
self::parseFullDoc($rc, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m')
array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic()),
self::parseFullDoc($rc, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m'),
), $name);
throw new MemberAccessException("Cannot read an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.'));
}
@@ -43,8 +44,8 @@ final class ObjectHelpers
{
$rc = new \ReflectionClass($class);
$hint = self::getSuggestion(array_merge(
array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }),
self::parseFullDoc($rc, '~^[ \t*]*@property(?:-write)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m')
array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic()),
self::parseFullDoc($rc, '~^[ \t*]*@property(?:-write)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m'),
), $name);
throw new MemberAccessException("Cannot write to an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.'));
}
@@ -76,7 +77,7 @@ final class ObjectHelpers
$hint = self::getSuggestion(array_merge(
get_class_methods($class),
self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:static[ \t]+)?(?:\S+[ \t]+)??(\w+)\(~m'),
$additionalMethods
$additionalMethods,
), $method);
throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
}
@@ -107,8 +108,8 @@ final class ObjectHelpers
} else {
$hint = self::getSuggestion(
array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), function ($m) { return $m->isStatic(); }),
$method
array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), fn($m) => $m->isStatic()),
$method,
);
throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
}
@@ -133,7 +134,7 @@ final class ObjectHelpers
'~^ [ \t*]* @property(|-read|-write|-deprecated) [ \t]+ [^\s$]+ [ \t]+ \$ (\w+) ()~mx',
(string) $rc->getDocComment(),
$matches,
PREG_SET_ORDER
PREG_SET_ORDER,
);
$props = [];
@@ -199,16 +200,16 @@ final class ObjectHelpers
}
} while ($rc = $rc->getParentClass());
return preg_match_all($pattern, implode($doc), $m) ? $m[1] : [];
return preg_match_all($pattern, implode('', $doc), $m) ? $m[1] : [];
}
/**
* Checks if the public non-static property exists.
* @return bool|string returns 'event' if the property exists and has event like name
* Returns 'event' if the property exists and has event like name
* @internal
*/
public static function hasProperty(string $class, string $name)
public static function hasProperty(string $class, string $name): bool|string
{
static $cache;
$prop = &$cache[$class][$name];

View File

@@ -1,41 +0,0 @@
<?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;
/**
* Nette\Object behaviour mixin.
* @deprecated
*/
final class ObjectMixin
{
use Nette\StaticClass;
/** @deprecated use ObjectHelpers::getSuggestion() */
public static function getSuggestion(array $possibilities, string $value): ?string
{
trigger_error(__METHOD__ . '() has been renamed to Nette\Utils\ObjectHelpers::getSuggestion()', E_USER_DEPRECATED);
return ObjectHelpers::getSuggestion($possibilities, $value);
}
public static function setExtensionMethod(): void
{
trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
}
public static function getExtensionMethod(): void
{
trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
}
}

View File

@@ -34,24 +34,16 @@ class Paginator
{
use Nette\SmartObject;
/** @var int */
private $base = 1;
/** @var int */
private $itemsPerPage = 1;
/** @var int */
private $page = 1;
/** @var int|null */
private $itemCount;
private int $base = 1;
private int $itemsPerPage = 1;
private int $page = 1;
private ?int $itemCount = null;
/**
* Sets current page number.
* @return static
*/
public function setPage(int $page)
public function setPage(int $page): static
{
$this->page = $page;
return $this;
@@ -109,9 +101,8 @@ class Paginator
/**
* Sets first page (base) number.
* @return static
*/
public function setBase(int $base)
public function setBase(int $base): static
{
$this->base = $base;
return $this;
@@ -172,9 +163,8 @@ class Paginator
/**
* Sets the number of items to display on a single page.
* @return static
*/
public function setItemsPerPage(int $itemsPerPage)
public function setItemsPerPage(int $itemsPerPage): static
{
$this->itemsPerPage = max(1, $itemsPerPage);
return $this;
@@ -192,9 +182,8 @@ class Paginator
/**
* Sets the total number of items.
* @return static
*/
public function setItemCount(?int $itemCount = null)
public function setItemCount(?int $itemCount = null): static
{
$this->itemCount = $itemCount === null ? null : max(0, $itemCount);
return $this;

View File

@@ -25,9 +25,12 @@ final class Random
*/
public static function generate(int $length = 10, string $charlist = '0-9a-z'): string
{
$charlist = count_chars(preg_replace_callback('#.-.#', function (array $m): string {
return implode('', range($m[0][0], $m[0][2]));
}, $charlist), 3);
$charlist = preg_replace_callback(
'#.-.#',
fn(array $m): string => implode('', range($m[0][0], $m[0][2])),
$charlist,
);
$charlist = count_chars($charlist, mode: 3);
$chLen = strlen($charlist);
if ($length < 1) {

View File

@@ -19,114 +19,22 @@ final class Reflection
{
use Nette\StaticClass;
/**
* Determines if type is PHP built-in type. Otherwise, it is the class name.
*/
/** @deprecated use Nette\Utils\Validator::isBuiltinType() */
public static function isBuiltinType(string $type): bool
{
return Validators::isBuiltinType($type);
}
/**
* Determines if type is special class name self/parent/static.
*/
/** @deprecated use Nette\Utils\Validator::isClassKeyword() */
public static function isClassKeyword(string $name): bool
{
return Validators::isClassKeyword($name);
}
/**
* 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 or intersection type, it throws Nette\InvalidStateException.
*/
public static function getReturnType(\ReflectionFunctionAbstract $func): ?string
{
$type = $func->getReturnType() ?? (PHP_VERSION_ID >= 80100 && $func instanceof \ReflectionMethod ? $func->getTentativeReturnType() : null);
return self::getType($func, $type);
}
/**
* @deprecated
*/
public static function getReturnTypes(\ReflectionFunctionAbstract $func): array
{
$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 or intersection type, it throws Nette\InvalidStateException.
*/
public static function getParameterType(\ReflectionParameter $param): ?string
{
return self::getType($param, $param->getType());
}
/**
* @deprecated
*/
public static function getParameterTypes(\ReflectionParameter $param): array
{
$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 or intersection type, it throws Nette\InvalidStateException.
*/
public static function getPropertyType(\ReflectionProperty $prop): ?string
{
return self::getType($prop, PHP_VERSION_ID >= 70400 ? $prop->getType() : null);
}
/**
* @deprecated
*/
public static function getPropertyTypes(\ReflectionProperty $prop): array
{
$type = Type::fromReflection($prop);
return $type ? $type->getNames() : [];
}
/**
* @param \ReflectionFunction|\ReflectionMethod|\ReflectionParameter|\ReflectionProperty $reflection
*/
private static function getType($reflection, ?\ReflectionType $type): ?string
{
if ($type === null) {
return null;
} elseif ($type instanceof \ReflectionNamedType) {
return Type::resolve($type->getName(), $reflection);
} 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));
}
}
/**
* Returns the default value of parameter. If it is a constant, it returns its value.
* @return mixed
* @throws \ReflectionException If the parameter does not have a default value or the constant cannot be resolved
*/
public static function getParameterDefaultValue(\ReflectionParameter $param)
/** @deprecated use native ReflectionParameter::getDefaultValue() */
public static function getParameterDefaultValue(\ReflectionParameter $param): mixed
{
if ($param->isDefaultValueConstant()) {
$const = $orig = $param->getDefaultValueConstantName();
@@ -307,7 +215,7 @@ final class Reflection
private static function parseUseStatements(string $code, ?string $forClass = null): array
{
try {
$tokens = token_get_all($code, TOKEN_PARSE);
$tokens = \PhpToken::tokenize($code, TOKEN_PARSE);
} catch (\ParseError $e) {
trigger_error($e->getMessage(), E_USER_NOTICE);
$tokens = [];
@@ -316,13 +224,11 @@ final class Reflection
$namespace = $class = $classLevel = $level = null;
$res = $uses = [];
$nameTokens = PHP_VERSION_ID < 80000
? [T_STRING, T_NS_SEPARATOR]
: [T_STRING, T_NS_SEPARATOR, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED];
$nameTokens = [T_STRING, T_NS_SEPARATOR, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED];
while ($token = current($tokens)) {
next($tokens);
switch (is_array($token) ? $token[0] : $token) {
switch ($token->id) {
case T_NAMESPACE:
$namespace = ltrim(self::fetch($tokens, $nameTokens) . '\\', '\\');
$uses = [];
@@ -378,11 +284,11 @@ final class Reflection
case T_CURLY_OPEN:
case T_DOLLAR_OPEN_CURLY_BRACES:
case '{':
case ord('{'):
$level++;
break;
case '}':
case ord('}'):
if ($level === $classLevel) {
$class = $classLevel = null;
}
@@ -395,14 +301,13 @@ final class Reflection
}
private static function fetch(array &$tokens, $take): ?string
private static function fetch(array &$tokens, string|int|array $take): ?string
{
$res = null;
while ($token = current($tokens)) {
[$token, $s] = is_array($token) ? $token : [$token, $token];
if (in_array($token, (array) $take, true)) {
$res .= $s;
} elseif (!in_array($token, [T_DOC_COMMENT, T_WHITESPACE, T_COMMENT], true)) {
if ($token->is($take)) {
$res .= $token->text;
} elseif (!$token->is([T_DOC_COMMENT, T_WHITESPACE, T_COMMENT])) {
break;
}

View File

@@ -21,11 +21,14 @@ class Strings
{
use Nette\StaticClass;
public const TRIM_CHARACTERS = " \t\n\r\0\x0B\u{A0}";
public const TrimCharacters = " \t\n\r\0\x0B\u{A0}";
/** @deprecated use Strings::TrimCharacters */
public const TRIM_CHARACTERS = self::TrimCharacters;
/**
* Checks if the string is valid in UTF-8 encoding.
* @deprecated use Nette\Utils\Validator::isUnicode()
*/
public static function checkEncoding(string $s): bool
{
@@ -60,29 +63,47 @@ class Strings
/**
* Starts the $haystack string with the prefix $needle?
* Returns a code point of specific character in UTF-8 (number in range 0x0000..D7FF or 0xE000..10FFFF).
*/
public static function ord(string $c): int
{
if (!extension_loaded('iconv')) {
throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.');
}
$tmp = iconv('UTF-8', 'UTF-32BE//IGNORE', $c);
if (!$tmp) {
throw new Nette\InvalidArgumentException('Invalid UTF-8 character "' . ($c === '' ? '' : '\x' . strtoupper(bin2hex($c))) . '".');
}
return unpack('N', $tmp)[1];
}
/**
* @deprecated use str_starts_with()
*/
public static function startsWith(string $haystack, string $needle): bool
{
return strncmp($haystack, $needle, strlen($needle)) === 0;
return str_starts_with($haystack, $needle);
}
/**
* Ends the $haystack string with the suffix $needle?
* @deprecated use str_ends_with()
*/
public static function endsWith(string $haystack, string $needle): bool
{
return $needle === '' || substr($haystack, -strlen($needle)) === $needle;
return str_ends_with($haystack, $needle);
}
/**
* Does $haystack contain $needle?
* @deprecated use str_contains()
*/
public static function contains(string $haystack, string $needle): bool
{
return strpos($haystack, $needle) !== false;
return str_contains($haystack, $needle);
}
@@ -117,7 +138,7 @@ class Strings
$s = $n;
}
$s = self::normalizeNewLines($s);
$s = self::unixNewLines($s);
// remove control characters; leave \t + \n
$s = self::pcre('preg_replace', ['#[\x00-\x08\x0B-\x1F\x7F-\x9F]+#u', '', $s]);
@@ -132,12 +153,30 @@ class Strings
}
/**
* Standardize line endings to unix-like.
*/
/** @deprecated use Strings::unixNewLines() */
public static function normalizeNewLines(string $s): string
{
return str_replace(["\r\n", "\r"], "\n", $s);
return self::unixNewLines($s);
}
/**
* Converts line endings to \n used on Unix-like systems.
* Line endings are: \n, \r, \r\n, U+2028 line separator, U+2029 paragraph separator.
*/
public static function unixNewLines(string $s): string
{
return preg_replace("~\r\n?|\u{2028}|\u{2029}~", "\n", $s);
}
/**
* Converts line endings to platform-specific, i.e. \r\n on Windows and \n elsewhere.
* Line endings are: \n, \r, \r\n, U+2028 line separator, U+2029 paragraph separator.
*/
public static function platformNewLines(string $s): string
{
return preg_replace("~\r\n?|\n|\u{2028}|\u{2029}~", PHP_EOL, $s);
}
@@ -187,7 +226,7 @@ class Strings
$s = strtr(
$s,
"\xa5\xa3\xbc\x8c\xa7\x8a\xaa\x8d\x8f\x8e\xaf\xb9\xb3\xbe\x9c\x9a\xba\x9d\x9f\x9e\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe\x96\xa0\x8b\x97\x9b\xa6\xad\xb7",
'ALLSSSSTZZZallssstzzzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTsraaaalccceeeeiiddnnooooruuuuyt- <->|-.'
'ALLSSSSTZZZallssstzzzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTsraaaalccceeeeiiddnnooooruuuuyt- <->|-.',
);
$s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]);
} else {
@@ -367,7 +406,7 @@ class Strings
/**
* Removes all left and right side spaces (or the characters passed as second argument) from a UTF-8 encoded string.
*/
public static function trim(string $s, string $charlist = self::TRIM_CHARACTERS): string
public static function trim(string $s, string $charlist = self::TrimCharacters): string
{
$charlist = preg_quote($charlist, '#');
return self::replace($s, '#^[' . $charlist . ']+|[' . $charlist . ']+$#Du', '');
@@ -483,54 +522,90 @@ class Strings
/**
* Splits a string into array by the regular expression. Parenthesized expression in the delimiter are captured.
* Parameter $flags can be any combination of PREG_SPLIT_NO_EMPTY and PREG_OFFSET_CAPTURE flags.
* Divides the string into arrays according to the regular expression. Expressions in parentheses will be captured and returned as well.
*/
public static function split(
string $subject,
#[Language('RegExp')]
string $pattern,
int $flags = 0
bool|int $captureOffset = false,
bool $skipEmpty = false,
int $limit = -1,
bool $utf8 = false,
): array
{
return self::pcre('preg_split', [$pattern, $subject, -1, $flags | PREG_SPLIT_DELIM_CAPTURE]);
$flags = is_int($captureOffset) // back compatibility
? $captureOffset
: ($captureOffset ? PREG_SPLIT_OFFSET_CAPTURE : 0) | ($skipEmpty ? PREG_SPLIT_NO_EMPTY : 0);
$pattern .= $utf8 ? 'u' : '';
$m = self::pcre('preg_split', [$pattern, $subject, $limit, $flags | PREG_SPLIT_DELIM_CAPTURE]);
return $utf8 && $captureOffset
? self::bytesToChars($subject, [$m])[0]
: $m;
}
/**
* Checks if given string matches a regular expression pattern and returns an array with first found match and each subpattern.
* Parameter $flags can be any combination of PREG_OFFSET_CAPTURE and PREG_UNMATCHED_AS_NULL flags.
* Searches the string for the part matching the regular expression and returns
* an array with the found expression and individual subexpressions, or `null`.
*/
public static function match(
string $subject,
#[Language('RegExp')]
string $pattern,
int $flags = 0,
int $offset = 0
bool|int $captureOffset = false,
int $offset = 0,
bool $unmatchedAsNull = false,
bool $utf8 = false,
): ?array
{
if ($offset > strlen($subject)) {
return null;
$flags = is_int($captureOffset) // back compatibility
? $captureOffset
: ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0);
if ($utf8) {
$offset = strlen(self::substring($subject, 0, $offset));
$pattern .= 'u';
}
return self::pcre('preg_match', [$pattern, $subject, &$m, $flags, $offset])
? $m
: null;
if ($offset > strlen($subject)) {
return null;
} elseif (!self::pcre('preg_match', [$pattern, $subject, &$m, $flags, $offset])) {
return null;
} elseif ($utf8 && $captureOffset) {
return self::bytesToChars($subject, [$m])[0];
} else {
return $m;
}
}
/**
* Finds all occurrences matching regular expression pattern and returns a two-dimensional array. Result is array of matches (ie uses by default PREG_SET_ORDER).
* Parameter $flags can be any combination of PREG_OFFSET_CAPTURE, PREG_UNMATCHED_AS_NULL and PREG_PATTERN_ORDER flags.
* Searches the string for all occurrences matching the regular expression and
* returns an array of arrays containing the found expression and each subexpression.
*/
public static function matchAll(
string $subject,
#[Language('RegExp')]
string $pattern,
int $flags = 0,
int $offset = 0
bool|int $captureOffset = false,
int $offset = 0,
bool $unmatchedAsNull = false,
bool $patternOrder = false,
bool $utf8 = false,
): array
{
$flags = is_int($captureOffset) // back compatibility
? $captureOffset
: ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0) | ($patternOrder ? PREG_PATTERN_ORDER : 0);
if ($utf8) {
$offset = strlen(self::substring($subject, 0, $offset));
$pattern .= 'u';
}
if ($offset > strlen($subject)) {
return [];
}
@@ -540,21 +615,25 @@ class Strings
($flags & PREG_PATTERN_ORDER) ? $flags : ($flags | PREG_SET_ORDER),
$offset,
]);
return $m;
return $utf8 && $captureOffset
? self::bytesToChars($subject, $m)
: $m;
}
/**
* Replaces all occurrences matching regular expression $pattern which can be string or array in the form `pattern => replacement`.
* @param string|array $pattern
* @param string|callable $replacement
*/
public static function replace(
string $subject,
#[Language('RegExp')]
$pattern,
$replacement = '',
int $limit = -1
string|array $pattern,
string|callable $replacement = '',
int $limit = -1,
bool $captureOffset = false,
bool $unmatchedAsNull = false,
bool $utf8 = false,
): string
{
if (is_object($replacement) || is_array($replacement)) {
@@ -562,17 +641,49 @@ class Strings
throw new Nette\InvalidStateException("Callback '$textual' is not callable.");
}
return self::pcre('preg_replace_callback', [$pattern, $replacement, $subject, $limit]);
$flags = ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0);
if ($utf8) {
$pattern .= 'u';
if ($captureOffset) {
$replacement = fn($m) => $replacement(self::bytesToChars($subject, [$m])[0]);
}
}
return self::pcre('preg_replace_callback', [$pattern, $replacement, $subject, $limit, 0, $flags]);
} elseif (is_array($pattern) && is_string(key($pattern))) {
$replacement = array_values($pattern);
$pattern = array_keys($pattern);
}
if ($utf8) {
$pattern = array_map(fn($item) => $item . 'u', (array) $pattern);
}
return self::pcre('preg_replace', [$pattern, $replacement, $subject, $limit]);
}
private static function bytesToChars(string $s, array $groups): array
{
$lastBytes = $lastChars = 0;
foreach ($groups as &$matches) {
foreach ($matches as &$match) {
if ($match[1] > $lastBytes) {
$lastChars += self::length(substr($s, $lastBytes, $match[1] - $lastBytes));
} elseif ($match[1] < $lastBytes) {
$lastChars -= self::length(substr($s, $match[1], $lastBytes - $match[1]));
}
$lastBytes = $match[1];
$match[1] = $lastChars;
}
}
return $groups;
}
/** @internal */
public static function pcre(string $func, array $args)
{
@@ -584,7 +695,7 @@ class Strings
if (($code = preg_last_error()) // run-time error, but preg_last_error & return code are liars
&& ($res === null || !in_array($func, ['preg_filter', 'preg_replace_callback', 'preg_replace'], true))
) {
throw new RegexpException((RegexpException::MESSAGES[$code] ?? 'Unknown error')
throw new RegexpException(preg_last_error_msg()
. ' (pattern: ' . implode(' or ', (array) $args[0]) . ')', $code);
}

View File

@@ -18,37 +18,28 @@ use Nette;
final class Type
{
/** @var array<int, string|self> */
private $types;
/** @var bool */
private $simple;
/** @var string |, & */
private $kind;
private array $types;
private bool $simple;
private string $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
public static function fromReflection(
\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $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();
}
$type = $reflection instanceof \ReflectionFunctionAbstract
? $reflection->getReturnType() ?? (PHP_VERSION_ID >= 80100 && $reflection instanceof \ReflectionMethod ? $reflection->getTentativeReturnType() : null)
: $reflection->getType();
return $type ? self::fromReflectionType($type, $reflection, true) : null;
}
private static function fromReflectionType(\ReflectionType $type, $of, bool $asObject)
private static function fromReflectionType(\ReflectionType $type, $of, bool $asObject): self|string
{
if ($type instanceof \ReflectionNamedType) {
$name = self::resolve($type->getName(), $of);
@@ -58,11 +49,8 @@ final class Type
} elseif ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) {
return new self(
array_map(
function ($t) use ($of) { return self::fromReflectionType($t, $of, false); },
$type->getTypes()
),
$type instanceof \ReflectionUnionType ? '|' : '&'
array_map(fn($t) => self::fromReflectionType($t, $of, false), $type->getTypes()),
$type instanceof \ReflectionUnionType ? '|' : '&',
);
} else {
@@ -98,9 +86,11 @@ final class Type
/**
* Resolves 'self', 'static' and 'parent' to the actual class name.
* @param \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $of
*/
public static function resolve(string $type, $of): string
public static function resolve(
string $type,
\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $of,
): string
{
$lower = strtolower($type);
if ($of instanceof \ReflectionFunction) {
@@ -150,9 +140,7 @@ final class Type
*/
public function getNames(): array
{
return array_map(function ($t) {
return $t instanceof self ? $t->getNames() : $t;
}, $this->types);
return array_map(fn($t) => $t instanceof self ? $t->getNames() : $t, $this->types);
}
@@ -162,9 +150,7 @@ final class Type
*/
public function getTypes(): array
{
return array_map(function ($t) {
return $t instanceof self ? $t : new self([$t]);
}, $this->types);
return array_map(fn($t) => $t instanceof self ? $t : new self([$t]), $this->types);
}
@@ -251,9 +237,7 @@ final class Type
$subtype = self::fromString($subtype);
return $subtype->isUnion()
? Arrays::every($subtype->types, function ($t) {
return $this->allows2($t instanceof self ? $t->types : [$t]);
})
? Arrays::every($subtype->types, fn($t) => $this->allows2($t instanceof self ? $t->types : [$t]))
: $this->allows2($subtype->types);
}
@@ -261,22 +245,21 @@ final class Type
private function allows2(array $subtypes): bool
{
return $this->isUnion()
? Arrays::some($this->types, function ($t) use ($subtypes) {
return $this->allows3($t instanceof self ? $t->types : [$t], $subtypes);
})
? Arrays::some($this->types, fn($t) => $this->allows3($t instanceof self ? $t->types : [$t], $subtypes))
: $this->allows3($this->types, $subtypes);
}
private function allows3(array $types, array $subtypes): bool
{
return Arrays::every($types, function ($type) use ($subtypes) {
$builtin = Validators::isBuiltinType($type);
return Arrays::some($subtypes, function ($subtype) use ($type, $builtin) {
return $builtin
return Arrays::every(
$types,
fn($type) => Arrays::some(
$subtypes,
fn($subtype) => Validators::isBuiltinType($type)
? strcasecmp($type, $subtype) === 0
: is_a($subtype, $type, true);
});
});
: is_a($subtype, $type, true)
)
);
}
}

View File

@@ -93,10 +93,9 @@ class Validators
/**
* Verifies that the value is of expected types separated by pipe.
* @param mixed $value
* @throws AssertionException
*/
public static function assert($value, string $expected, string $label = 'variable'): void
public static function assert(mixed $value, string $expected, string $label = 'variable'): void
{
if (!static::is($value, $expected)) {
$expected = str_replace(['|', ':'], [' or ', ' in range '], $expected);
@@ -105,7 +104,7 @@ class Validators
if (is_int($value) || is_float($value) || (is_string($value) && strlen($value) < 40)) {
$type .= ' ' . var_export($value, true);
} elseif (is_object($value)) {
$type .= ' ' . get_class($value);
$type .= ' ' . $value::class;
}
throw new AssertionException("The $label expects to be $expected, $type given.");
@@ -116,14 +115,13 @@ class Validators
/**
* Verifies that element $key in array is of expected types separated by pipe.
* @param mixed[] $array
* @param int|string $key
* @throws AssertionException
*/
public static function assertField(
array $array,
$key,
?string $expected = null,
string $label = "item '%' in array"
string $label = "item '%' in array",
): void
{
if (!array_key_exists($key, $array)) {
@@ -137,18 +135,17 @@ class Validators
/**
* Verifies that the value is of expected types separated by pipe.
* @param mixed $value
*/
public static function is($value, string $expected): bool
public static function is(mixed $value, string $expected): bool
{
foreach (explode('|', $expected) as $item) {
if (substr($item, -2) === '[]') {
if (str_ends_with($item, '[]')) {
if (is_iterable($value) && self::everyIs($value, substr($item, 0, -2))) {
return true;
}
continue;
} elseif (substr($item, 0, 1) === '?') {
} elseif (str_starts_with($item, '?')) {
$item = substr($item, 1);
if ($value === null) {
return true;
@@ -215,9 +212,8 @@ class Validators
/**
* Checks if the value is an integer or a float.
* @param mixed $value
*/
public static function isNumber($value): bool
public static function isNumber(mixed $value): bool
{
return is_int($value) || is_float($value);
}
@@ -225,9 +221,8 @@ class Validators
/**
* Checks if the value is an integer or a integer written in a string.
* @param mixed $value
*/
public static function isNumericInt($value): bool
public static function isNumericInt(mixed $value): bool
{
return is_int($value) || (is_string($value) && preg_match('#^[+-]?[0-9]+$#D', $value));
}
@@ -235,9 +230,8 @@ class Validators
/**
* Checks if the value is a number or a number written in a string.
* @param mixed $value
*/
public static function isNumeric($value): bool
public static function isNumeric(mixed $value): bool
{
return is_float($value) || is_int($value) || (is_string($value) && preg_match('#^[+-]?([0-9]++\.?[0-9]*|\.[0-9]+)$#D', $value));
}
@@ -245,9 +239,8 @@ class Validators
/**
* Checks if the value is a syntactically correct callback.
* @param mixed $value
*/
public static function isCallable($value): bool
public static function isCallable(mixed $value): bool
{
return $value && is_callable($value, true);
}
@@ -255,9 +248,8 @@ class Validators
/**
* Checks if the value is a valid UTF-8 string.
* @param mixed $value
*/
public static function isUnicode($value): bool
public static function isUnicode(mixed $value): bool
{
return is_string($value) && preg_match('##u', $value);
}
@@ -265,9 +257,8 @@ class Validators
/**
* Checks if the value is 0, '', false or null.
* @param mixed $value
*/
public static function isNone($value): bool
public static function isNone(mixed $value): bool
{
return $value == null; // intentionally ==
}
@@ -282,10 +273,9 @@ class Validators
/**
* Checks if a variable is a zero-based integer indexed array.
* @param mixed $value
* @deprecated use Nette\Utils\Arrays::isList
*/
public static function isList($value): bool
public static function isList(mixed $value): bool
{
return Arrays::isList($value);
}
@@ -294,9 +284,8 @@ class Validators
/**
* Checks if the value is in the given range [min, max], where the upper or lower limit can be omitted (null).
* Numbers, strings and DateTime objects can be compared.
* @param mixed $value
*/
public static function isInRange($value, array $range): bool
public static function isInRange(mixed $value, array $range): bool
{
if ($value === null || !(isset($range[0]) || isset($range[1]))) {
return false;
@@ -327,14 +316,13 @@ class Validators
$atom = "[-a-z0-9!#$%&'*+/=?^_`{|}~]"; // RFC 5322 unquoted characters in local-part
$alpha = "a-z\x80-\xFF"; // superset of IDN
return (bool) preg_match(<<<XX
(^
("([ !#-[\\]-~]*|\\\\[ -~])+"|$atom+(\\.$atom+)*) # quoted or unquoted
@
([0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)+ # domain - RFC 1034
[$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain
$)Dix
XX
, $value);
(^(?n)
("([ !#-[\\]-~]*|\\\\[ -~])+"|$atom+(\\.$atom+)*) # quoted or unquoted
@
([0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)+ # domain - RFC 1034
[$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain
$)Dix
XX, $value);
}
@@ -345,20 +333,19 @@ XX
{
$alpha = "a-z\x80-\xFF";
return (bool) preg_match(<<<XX
(^
https?://(
(([-_0-9$alpha]+\\.)* # subdomain
[0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)? # domain
[$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain
|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3} # IPv4
|\\[[0-9a-f:]{3,39}\\] # IPv6
)(:\\d{1,5})? # port
(/\\S*)? # path
(\\?\\S*)? # query
(\\#\\S*)? # fragment
$)Dix
XX
, $value);
(^(?n)
https?://(
(([-_0-9$alpha]+\\.)* # subdomain
[0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)? # domain
[$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain
|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3} # IPv4
|\\[[0-9a-f:]{3,39}\\] # IPv6
)(:\\d{1,5})? # port
(/\\S*)? # path
(\\?\\S*)? # query
(\\#\\S*)? # fragment
$)Dix
XX, $value);
}
@@ -373,6 +360,7 @@ XX
/**
* Checks whether the input is a class, interface or trait.
* @deprecated
*/
public static function isType(string $type): bool
{
@@ -413,12 +401,11 @@ XX
public static function isTypeDeclaration(string $type): bool
{
return (bool) preg_match(<<<'XX'
~(
\?? (?<type> \\? (?<name> [a-zA-Z_\x7f-\xff][\w\x7f-\xff]*) (\\ (?&name))* ) |
(?<intersection> (?&type) (& (?&type))+ ) |
(?<upart> (?&type) | \( (?&intersection) \) ) (\| (?&upart))+
)$~xAD
XX
, $type);
~((?n)
\?? (?<type> \\? (?<name> [a-zA-Z_\x7f-\xff][\w\x7f-\xff]*) (\\ (?&name))* ) |
(?<intersection> (?&type) (& (?&type))+ ) |
(?<upart> (?&type) | \( (?&intersection) \) ) (\| (?&upart))+
)$~xAD
XX, $type);
}
}

View File

@@ -29,7 +29,7 @@ class UnknownImageFileException extends ImageException
/**
* The exception that indicates error of JSON encoding/decoding.
*/
class JsonException extends \Exception
class JsonException extends \JsonException
{
}
@@ -39,14 +39,6 @@ class JsonException extends \Exception
*/
class RegexpException extends \Exception
{
public const MESSAGES = [
PREG_INTERNAL_ERROR => 'Internal error',
PREG_BACKTRACK_LIMIT_ERROR => 'Backtrack limit was exhausted',
PREG_RECURSION_LIMIT_ERROR => 'Recursion limit was exhausted',
PREG_BAD_UTF8_ERROR => 'Malformed UTF-8 data',
PREG_BAD_UTF8_OFFSET_ERROR => 'Offset didn\'t correspond to the begin of a valid UTF-8 code point',
6 => 'Failed due to limited JIT stack space', // PREG_JIT_STACKLIMIT_ERROR
];
}