Updated Rector to commit b71d0fea8eaf051d6d55aee74c9eece0028e2165

b71d0fea8e Bump rule-doc-generator to prefixed version (#5737)
This commit is contained in:
Tomas Votruba 2024-03-18 22:09:15 +00:00
parent 0268ca2b1c
commit b1eb18f499
42 changed files with 1573 additions and 588 deletions

View File

@ -19,12 +19,12 @@ final class VersionResolver
* @api
* @var string
*/
public const PACKAGE_VERSION = 'bf856e2ffa55c669e018e773341d7c05794f075e';
public const PACKAGE_VERSION = 'b71d0fea8eaf051d6d55aee74c9eece0028e2165';
/**
* @api
* @var string
*/
public const RELEASE_DATE = '2024-03-17 08:43:37';
public const RELEASE_DATE = '2024-03-18 22:06:47';
/**
* @var int
*/

2
vendor/autoload.php vendored
View File

@ -22,4 +22,4 @@ if (PHP_VERSION_ID < 50600) {
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit1a5f2ab6c5d7fa473cb6ef1c69de9082::getLoader();
return ComposerAutoloaderInit67be42e0079886f0083b7116ae1de531::getLoader();

View File

@ -607,20 +607,25 @@ return array(
'RectorPrefix202403\\Nette\\Utils\\AssertionException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php',
'RectorPrefix202403\\Nette\\Utils\\Callback' => $vendorDir . '/nette/utils/src/Utils/Callback.php',
'RectorPrefix202403\\Nette\\Utils\\DateTime' => $vendorDir . '/nette/utils/src/Utils/DateTime.php',
'RectorPrefix202403\\Nette\\Utils\\FileInfo' => $vendorDir . '/nette/utils/src/Utils/FileInfo.php',
'RectorPrefix202403\\Nette\\Utils\\FileSystem' => $vendorDir . '/nette/utils/src/Utils/FileSystem.php',
'RectorPrefix202403\\Nette\\Utils\\Finder' => $vendorDir . '/nette/utils/src/Utils/Finder.php',
'RectorPrefix202403\\Nette\\Utils\\Floats' => $vendorDir . '/nette/utils/src/Utils/Floats.php',
'RectorPrefix202403\\Nette\\Utils\\Helpers' => $vendorDir . '/nette/utils/src/Utils/Helpers.php',
'RectorPrefix202403\\Nette\\Utils\\Html' => $vendorDir . '/nette/utils/src/Utils/Html.php',
'RectorPrefix202403\\Nette\\Utils\\IHtmlString' => $vendorDir . '/nette/utils/src/compatibility.php',
'RectorPrefix202403\\Nette\\Utils\\Image' => $vendorDir . '/nette/utils/src/Utils/Image.php',
'RectorPrefix202403\\Nette\\Utils\\ImageColor' => $vendorDir . '/nette/utils/src/Utils/ImageColor.php',
'RectorPrefix202403\\Nette\\Utils\\ImageException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php',
'RectorPrefix202403\\Nette\\Utils\\ImageType' => $vendorDir . '/nette/utils/src/Utils/ImageType.php',
'RectorPrefix202403\\Nette\\Utils\\Iterables' => $vendorDir . '/nette/utils/src/Utils/Iterables.php',
'RectorPrefix202403\\Nette\\Utils\\Json' => $vendorDir . '/nette/utils/src/Utils/Json.php',
'RectorPrefix202403\\Nette\\Utils\\JsonException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php',
'RectorPrefix202403\\Nette\\Utils\\ObjectHelpers' => $vendorDir . '/nette/utils/src/Utils/ObjectHelpers.php',
'RectorPrefix202403\\Nette\\Utils\\ObjectMixin' => $vendorDir . '/nette/utils/src/Utils/ObjectMixin.php',
'RectorPrefix202403\\Nette\\Utils\\Paginator' => $vendorDir . '/nette/utils/src/Utils/Paginator.php',
'RectorPrefix202403\\Nette\\Utils\\Random' => $vendorDir . '/nette/utils/src/Utils/Random.php',
'RectorPrefix202403\\Nette\\Utils\\Reflection' => $vendorDir . '/nette/utils/src/Utils/Reflection.php',
'RectorPrefix202403\\Nette\\Utils\\ReflectionMethod' => $vendorDir . '/nette/utils/src/Utils/ReflectionMethod.php',
'RectorPrefix202403\\Nette\\Utils\\RegexpException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php',
'RectorPrefix202403\\Nette\\Utils\\Strings' => $vendorDir . '/nette/utils/src/Utils/Strings.php',
'RectorPrefix202403\\Nette\\Utils\\Type' => $vendorDir . '/nette/utils/src/Utils/Type.php',

View File

@ -2,7 +2,7 @@
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit1a5f2ab6c5d7fa473cb6ef1c69de9082
class ComposerAutoloaderInit67be42e0079886f0083b7116ae1de531
{
private static $loader;
@ -22,17 +22,17 @@ class ComposerAutoloaderInit1a5f2ab6c5d7fa473cb6ef1c69de9082
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit1a5f2ab6c5d7fa473cb6ef1c69de9082', 'loadClassLoader'), true, true);
spl_autoload_register(array('ComposerAutoloaderInit67be42e0079886f0083b7116ae1de531', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit1a5f2ab6c5d7fa473cb6ef1c69de9082', 'loadClassLoader'));
spl_autoload_unregister(array('ComposerAutoloaderInit67be42e0079886f0083b7116ae1de531', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit1a5f2ab6c5d7fa473cb6ef1c69de9082::getInitializer($loader));
call_user_func(\Composer\Autoload\ComposerStaticInit67be42e0079886f0083b7116ae1de531::getInitializer($loader));
$loader->setClassMapAuthoritative(true);
$loader->register(true);
$filesToLoad = \Composer\Autoload\ComposerStaticInit1a5f2ab6c5d7fa473cb6ef1c69de9082::$files;
$filesToLoad = \Composer\Autoload\ComposerStaticInit67be42e0079886f0083b7116ae1de531::$files;
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;

View File

@ -4,7 +4,7 @@
namespace Composer\Autoload;
class ComposerStaticInit1a5f2ab6c5d7fa473cb6ef1c69de9082
class ComposerStaticInit67be42e0079886f0083b7116ae1de531
{
public static $files = array (
'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php',
@ -826,20 +826,25 @@ class ComposerStaticInit1a5f2ab6c5d7fa473cb6ef1c69de9082
'RectorPrefix202403\\Nette\\Utils\\AssertionException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php',
'RectorPrefix202403\\Nette\\Utils\\Callback' => __DIR__ . '/..' . '/nette/utils/src/Utils/Callback.php',
'RectorPrefix202403\\Nette\\Utils\\DateTime' => __DIR__ . '/..' . '/nette/utils/src/Utils/DateTime.php',
'RectorPrefix202403\\Nette\\Utils\\FileInfo' => __DIR__ . '/..' . '/nette/utils/src/Utils/FileInfo.php',
'RectorPrefix202403\\Nette\\Utils\\FileSystem' => __DIR__ . '/..' . '/nette/utils/src/Utils/FileSystem.php',
'RectorPrefix202403\\Nette\\Utils\\Finder' => __DIR__ . '/..' . '/nette/utils/src/Utils/Finder.php',
'RectorPrefix202403\\Nette\\Utils\\Floats' => __DIR__ . '/..' . '/nette/utils/src/Utils/Floats.php',
'RectorPrefix202403\\Nette\\Utils\\Helpers' => __DIR__ . '/..' . '/nette/utils/src/Utils/Helpers.php',
'RectorPrefix202403\\Nette\\Utils\\Html' => __DIR__ . '/..' . '/nette/utils/src/Utils/Html.php',
'RectorPrefix202403\\Nette\\Utils\\IHtmlString' => __DIR__ . '/..' . '/nette/utils/src/compatibility.php',
'RectorPrefix202403\\Nette\\Utils\\Image' => __DIR__ . '/..' . '/nette/utils/src/Utils/Image.php',
'RectorPrefix202403\\Nette\\Utils\\ImageColor' => __DIR__ . '/..' . '/nette/utils/src/Utils/ImageColor.php',
'RectorPrefix202403\\Nette\\Utils\\ImageException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php',
'RectorPrefix202403\\Nette\\Utils\\ImageType' => __DIR__ . '/..' . '/nette/utils/src/Utils/ImageType.php',
'RectorPrefix202403\\Nette\\Utils\\Iterables' => __DIR__ . '/..' . '/nette/utils/src/Utils/Iterables.php',
'RectorPrefix202403\\Nette\\Utils\\Json' => __DIR__ . '/..' . '/nette/utils/src/Utils/Json.php',
'RectorPrefix202403\\Nette\\Utils\\JsonException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php',
'RectorPrefix202403\\Nette\\Utils\\ObjectHelpers' => __DIR__ . '/..' . '/nette/utils/src/Utils/ObjectHelpers.php',
'RectorPrefix202403\\Nette\\Utils\\ObjectMixin' => __DIR__ . '/..' . '/nette/utils/src/Utils/ObjectMixin.php',
'RectorPrefix202403\\Nette\\Utils\\Paginator' => __DIR__ . '/..' . '/nette/utils/src/Utils/Paginator.php',
'RectorPrefix202403\\Nette\\Utils\\Random' => __DIR__ . '/..' . '/nette/utils/src/Utils/Random.php',
'RectorPrefix202403\\Nette\\Utils\\Reflection' => __DIR__ . '/..' . '/nette/utils/src/Utils/Reflection.php',
'RectorPrefix202403\\Nette\\Utils\\ReflectionMethod' => __DIR__ . '/..' . '/nette/utils/src/Utils/ReflectionMethod.php',
'RectorPrefix202403\\Nette\\Utils\\RegexpException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php',
'RectorPrefix202403\\Nette\\Utils\\Strings' => __DIR__ . '/..' . '/nette/utils/src/Utils/Strings.php',
'RectorPrefix202403\\Nette\\Utils\\Type' => __DIR__ . '/..' . '/nette/utils/src/Utils/Type.php',
@ -2706,9 +2711,9 @@ class ComposerStaticInit1a5f2ab6c5d7fa473cb6ef1c69de9082
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit1a5f2ab6c5d7fa473cb6ef1c69de9082::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit1a5f2ab6c5d7fa473cb6ef1c69de9082::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit1a5f2ab6c5d7fa473cb6ef1c69de9082::$classMap;
$loader->prefixLengthsPsr4 = ComposerStaticInit67be42e0079886f0083b7116ae1de531::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit67be42e0079886f0083b7116ae1de531::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit67be42e0079886f0083b7116ae1de531::$classMap;
}, null, ClassLoader::class);
}

View File

@ -612,30 +612,31 @@
},
{
"name": "nette\/utils",
"version": "v3.2.10",
"version_normalized": "3.2.10.0",
"version": "v4.0.4",
"version_normalized": "4.0.4.0",
"source": {
"type": "git",
"url": "https:\/\/github.com\/nette\/utils.git",
"reference": "a4175c62652f2300c8017fb7e640f9ccb11648d2"
"reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218"
},
"dist": {
"type": "zip",
"url": "https:\/\/api.github.com\/repos\/nette\/utils\/zipball\/a4175c62652f2300c8017fb7e640f9ccb11648d2",
"reference": "a4175c62652f2300c8017fb7e640f9ccb11648d2",
"url": "https:\/\/api.github.com\/repos\/nette\/utils\/zipball\/d3ad0aa3b9f934602cb3e3902ebccf10be34d218",
"reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218",
"shasum": ""
},
"require": {
"php": ">=7.2 <8.4"
"php": ">=8.0 <8.4"
},
"conflict": {
"nette\/di": "<3.0.6"
"nette\/finder": "<3",
"nette\/schema": "<1.2.2"
},
"require-dev": {
"jetbrains\/phpstorm-attributes": "dev-master",
"nette\/tester": "~2.0",
"nette\/tester": "^2.5",
"phpstan\/phpstan": "^1.0",
"tracy\/tracy": "^2.3"
"tracy\/tracy": "^2.9"
},
"suggest": {
"ext-gd": "to use Image",
@ -643,14 +644,13 @@
"ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
"ext-json": "to use Nette\\Utils\\Json",
"ext-mbstring": "to use Strings::lower() etc...",
"ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()",
"ext-xml": "to use Strings::length() etc. when mbstring is not available"
"ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()"
},
"time": "2023-07-30T15:38:18+00:00",
"time": "2024-01-17T16:50:36+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.2-dev"
"dev-master": "4.0-dev"
}
},
"installation-source": "dist",
@ -695,7 +695,7 @@
],
"support": {
"issues": "https:\/\/github.com\/nette\/utils\/issues",
"source": "https:\/\/github.com\/nette\/utils\/tree\/v3.2.10"
"source": "https:\/\/github.com\/nette\/utils\/tree\/v4.0.4"
},
"install-path": "..\/nette\/utils"
},
@ -2670,21 +2670,20 @@
},
{
"name": "symplify\/rule-doc-generator-contracts",
"version": "11.1.26",
"version_normalized": "11.1.26.0",
"version": "11.2.0",
"version_normalized": "11.2.0.0",
"source": {
"type": "git",
"url": "https:\/\/github.com\/symplify\/rule-doc-generator-contracts.git",
"reference": "3e66b3fec678b74a076395ec629d535fb95293b5"
"reference": "479cfcfd46047f80624aba931d9789e50475b5c6"
},
"dist": {
"type": "zip",
"url": "https:\/\/api.github.com\/repos\/symplify\/rule-doc-generator-contracts\/zipball\/3e66b3fec678b74a076395ec629d535fb95293b5",
"reference": "3e66b3fec678b74a076395ec629d535fb95293b5",
"url": "https:\/\/api.github.com\/repos\/symplify\/rule-doc-generator-contracts\/zipball\/479cfcfd46047f80624aba931d9789e50475b5c6",
"reference": "479cfcfd46047f80624aba931d9789e50475b5c6",
"shasum": ""
},
"require": {
"nette\/utils": "^3.2",
"php": ">=8.1"
},
"require-dev": {
@ -2698,7 +2697,7 @@
"symplify\/phpstan-rules": "11.2.3.72",
"tomasvotruba\/unused-public": "^0.0.34"
},
"time": "2023-02-07T07:16:13+00:00",
"time": "2024-03-18T22:02:54+00:00",
"type": "library",
"extra": {
"branch-alias": {
@ -2717,7 +2716,7 @@
],
"description": "Contracts for production code of RuleDocGenerator",
"support": {
"source": "https:\/\/github.com\/symplify\/rule-doc-generator-contracts\/tree\/11.1.26"
"source": "https:\/\/github.com\/symplify\/rule-doc-generator-contracts\/tree\/11.2.0"
},
"funding": [
{

File diff suppressed because one or more lines are too long

View File

@ -34,23 +34,23 @@
}
],
"require": {
"php": ">=7.2 <8.4"
"php": ">=8.0 <8.4"
},
"require-dev": {
"nette\/tester": "~2.0",
"tracy\/tracy": "^2.3",
"nette\/tester": "^2.5",
"tracy\/tracy": "^2.9",
"phpstan\/phpstan": "^1.0",
"jetbrains\/phpstorm-attributes": "dev-master"
},
"conflict": {
"nette\/di": "<3.0.6"
"nette\/finder": "<3",
"nette\/schema": "<1.2.2"
},
"suggest": {
"ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
"ext-json": "to use Nette\\Utils\\Json",
"ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
"ext-mbstring": "to use Strings::lower() etc...",
"ext-xml": "to use Strings::length() etc. when mbstring is not available",
"ext-gd": "to use Image",
"ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()"
},
@ -66,7 +66,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "3.2-dev"
"dev-master": "4.0-dev"
}
}
}

View File

@ -17,6 +17,7 @@ In package nette/utils you will find a set of [useful classes](https://doc.nette
- [Callback](https://doc.nette.org/utils/callback) - PHP callbacks
- [Date and Time](https://doc.nette.org/utils/datetime) - modify times and dates
- [Filesystem](https://doc.nette.org/utils/filesystem) - copying, renaming, …
- [Finder](https://doc.nette.org/utils/finder) - finds files and directories
- [Helper Functions](https://doc.nette.org/utils/helpers)
- [HTML elements](https://doc.nette.org/utils/html-elements) - generate HTML
- [Images](https://doc.nette.org/utils/images) - crop, resize, rotate images
@ -39,6 +40,7 @@ The recommended way to install is via Composer:
composer require nette/utils
```
- Nette Utils 4.0 is compatible with PHP 8.0 to 8.3
- Nette Utils 3.2 is compatible with PHP 7.2 to 8.3
- Nette Utils 3.1 is compatible with PHP 7.1 to 8.0
- Nette Utils 3.0 is compatible with PHP 7.1 to 8.0

View File

@ -23,7 +23,9 @@ use RectorPrefix202403\Nette;
class CachingIterator extends \CachingIterator implements \Countable
{
use Nette\SmartObject;
/** @var int */
/**
* @var int
*/
private $counter = 0;
public function __construct($iterator)
{
@ -38,7 +40,7 @@ class CachingIterator extends \CachingIterator implements \Countable
} elseif ($iterator instanceof \Traversable) {
$iterator = new \IteratorIterator($iterator);
} else {
throw new Nette\InvalidArgumentException(\sprintf('Invalid argument passed to %s; array or Traversable expected, %s given.', self::class, \is_object($iterator) ? \get_class($iterator) : \gettype($iterator)));
throw new Nette\InvalidArgumentException(\sprintf('Invalid argument passed to %s; array or Traversable expected, %s given.', self::class, \get_debug_type($iterator)));
}
parent::__construct($iterator, 0);
}

View File

@ -19,7 +19,9 @@ class Mapper extends \IteratorIterator
parent::__construct($iterator);
$this->callback = $callback;
}
#[\ReturnTypeWillChange]
/**
* @return mixed
*/
public function current()
{
return ($this->callback)(parent::current(), parent::key());

View File

@ -18,6 +18,7 @@ use RectorPrefix202403\Nette\Utils\ObjectHelpers;
trait SmartObject
{
/**
* @return mixed
* @throws MemberAccessException
*/
public function __call(string $name, array $args)
@ -31,11 +32,11 @@ trait SmartObject
$handler(...$args);
}
} elseif ($handlers !== null) {
throw new UnexpectedValueException("Property {$class}::\${$name} must be iterable or null, " . \gettype($handlers) . ' given.');
throw new UnexpectedValueException("Property {$class}::\${$name} must be iterable or null, " . \get_debug_type($handlers) . ' given.');
}
} else {
ObjectHelpers::strictCall($class, $name);
return null;
}
ObjectHelpers::strictCall($class, $name);
}
/**
* @throws MemberAccessException
@ -75,11 +76,10 @@ trait SmartObject
}
}
/**
* @param mixed $value
* @return void
* @throws MemberAccessException if the property is not defined or is read-only
* @param mixed $value
*/
public function __set(string $name, $value)
public function __set(string $name, $value) : void
{
$class = static::class;
if (ObjectHelpers::hasProperty($class, $name)) {
@ -103,10 +103,9 @@ trait SmartObject
}
}
/**
* @return void
* @throws MemberAccessException
*/
public function __unset(string $name)
public function __unset(string $name) : void
{
$class = static::class;
if (!ObjectHelpers::hasProperty($class, $name)) {

View File

@ -13,17 +13,15 @@ namespace RectorPrefix202403\Nette;
trait StaticClass
{
/**
* @return never
* @throws \Error
* Class is static and cannot be instantiated.
*/
public final function __construct()
private function __construct()
{
throw new \Error('Class ' . static::class . ' is static and cannot be instantiated.');
}
/**
* Call to undefined static method.
* @return void
* @throws MemberAccessException
* @return mixed
*/
public static function __callStatic(string $name, array $args)
{

View File

@ -14,9 +14,10 @@ interface Translator
{
/**
* Translates the given string.
* @param mixed $message
* @param mixed ...$parameters
* @param string|\Stringable $message
* @return string|\Stringable
* @param mixed ...$parameters
*/
function translate($message, ...$parameters) : string;
function translate($message, ...$parameters);
}
\interface_exists(ITranslator::class);

View File

@ -11,7 +11,7 @@ use RectorPrefix202403\Nette;
/**
* Provides objects to work as array.
* @template T
* @implements \RecursiveArrayIterator<array-key, T>
* @implements \IteratorAggregate<array-key, T>
* @implements \ArrayAccess<array-key, T>
*/
class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \IteratorAggregate
@ -25,17 +25,19 @@ class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \Iterator
{
$obj = new static();
foreach ($array as $key => $value) {
$obj->{$key} = $recursive && \is_array($value) ? static::from($value, \true) : $value;
$obj->{$key} = $recursive && \is_array($value) ? static::from($value) : $value;
}
return $obj;
}
/**
* Returns an iterator over all items.
* @return \RecursiveArrayIterator<array-key, T>
* @return \Iterator<array-key, T>
*/
public function getIterator() : \RecursiveArrayIterator
public function &getIterator() : \Iterator
{
return new \RecursiveArrayIterator((array) $this);
foreach ((array) $this as $key => $foo) {
(yield $key => $this->{$key});
}
}
/**
* Returns items count.
@ -53,7 +55,7 @@ class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \Iterator
{
if (!\is_scalar($key)) {
// prevents null
throw new Nette\InvalidArgumentException(\sprintf('Key must be either a string or an integer, %s given.', \gettype($key)));
throw new Nette\InvalidArgumentException(\sprintf('Key must be either a string or an integer, %s given.', \get_debug_type($key)));
}
$this->{$key} = $value;
}

View File

@ -17,7 +17,9 @@ use RectorPrefix202403\Nette;
class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
{
use Nette\SmartObject;
/** @var mixed[] */
/**
* @var mixed[]
*/
private $list = [];
/**
* Transforms array to ArrayList.
@ -35,11 +37,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);
}
}
/**
* Returns items count.
@ -100,7 +104,7 @@ class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
}
/**
* Prepends a item.
* @param T $value
* @param mixed $value
*/
public function prepend($value) : void
{

View File

@ -21,7 +21,7 @@ class Arrays
* @template T
* @param array<T> $array
* @param array-key|array-key[] $key
* @param ?T $default
* @param mixed $default
* @return ?T
* @throws Nette\InvalidArgumentException if item does not exist and default value is not provided
*/
@ -80,8 +80,7 @@ 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
* @param string|int $key
*/
public static function getKeyOffset(array $array, $key) : ?int
{
@ -96,36 +95,68 @@ class Arrays
}
/**
* Tests an array for the presence of value.
* @param mixed $value
* @param mixed $value
*/
public static function contains(array $array, $value) : bool
{
return \in_array($value, $array, \true);
}
/**
* Returns the first item from the array or null if array is empty.
* Returns the first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null.
* The $predicate has the signature `function (mixed $value, int|string $key, array $array): bool`.
* @template T
* @param array<T> $array
* @return ?T
*/
public static function first(array $array)
public static function first(array $array, ?callable $predicate = null, ?callable $else = null)
{
return count($array) ? \reset($array) : null;
$key = self::firstKey($array, $predicate);
return $key === null ? $else ? $else() : null : $array[$key];
}
/**
* Returns the last item from the array or null if array is empty.
* Returns the last item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null.
* The $predicate has the signature `function (mixed $value, int|string $key, array $array): bool`.
* @template T
* @param array<T> $array
* @return ?T
*/
public static function last(array $array)
public static function last(array $array, ?callable $predicate = null, ?callable $else = null)
{
return count($array) ? \end($array) : null;
$key = self::lastKey($array, $predicate);
return $key === null ? $else ? $else() : null : $array[$key];
}
/**
* Returns the key of first item (matching the specified predicate if given) or null if there is no such item.
* The $predicate has the signature `function (mixed $value, int|string $key, array $array): bool`.
* @return int|string|null
*/
public static function firstKey(array $array, ?callable $predicate = null)
{
if (!$predicate) {
\reset($array);
return \key($array);
}
foreach ($array as $k => $v) {
if ($predicate($v, $k, $array)) {
return $k;
}
}
return null;
}
/**
* Returns the key of last item (matching the specified predicate if given) or null if there is no such item.
* The $predicate has the signature `function (mixed $value, int|string $key, array $array): bool`.
* @return int|string|null
*/
public static function lastKey(array $array, ?callable $predicate = null)
{
\end($array);
return $predicate ? self::firstKey(\array_reverse($array, \true), $predicate) : \key($array);
}
/**
* 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
* @param string|int|null $key
*/
public static function insertBefore(array &$array, $key, array $inserted) : void
{
@ -135,7 +166,7 @@ class Arrays
/**
* Inserts the contents of the $inserted array into the $array before the $key.
* If $key is null (or does not exist), it is inserted at the end.
* @param array-key|null $key
* @param string|int|null $key
*/
public static function insertAfter(array &$array, $key, array $inserted) : void
{
@ -146,8 +177,8 @@ class Arrays
}
/**
* Renames key in array.
* @param array-key $oldKey
* @param array-key $newKey
* @param string|int $oldKey
* @param string|int $newKey
*/
public static function renameKey(array &$array, $oldKey, $newKey) : bool
{
@ -166,6 +197,7 @@ class Arrays
* Returns only those array items, which matches a regular expression $pattern.
* @param string[] $array
* @return string[]
* @param bool|int $invert
*/
public static function grep(
array $array,
@ -173,9 +205,10 @@ class Arrays
* @language
*/
string $pattern,
int $flags = 0
$invert = \false
) : array
{
$flags = $invert ? \PREG_GREP_INVERT : 0;
return Strings::pcre('preg_grep', [$pattern, $array, $flags]);
}
/**
@ -194,7 +227,8 @@ class Arrays
}
/**
* Checks if the array is indexed in ascending order of numeric keys from zero, a.k.a list.
* @param mixed $value
* @return ($value is list ? true : false)
* @param mixed $value
*/
public static function isList($value) : bool
{
@ -219,7 +253,7 @@ class Arrays
/**
* Reformats table to associative tree. Path looks like 'field|field[]field->field=field'.
* @param string|string[] $path
* @return array|\stdClass
* @return mixed[]|\stdClass
*/
public static function associate(array $array, $path)
{
@ -261,7 +295,7 @@ class Arrays
}
/**
* Normalizes array to associative array. Replace numeric keys with their values, the new value will be $filling.
* @param mixed $filling
* @param mixed $filling
*/
public static function normalize(array $array, $filling = null) : array
{
@ -276,10 +310,10 @@ class Arrays
* or returns $default, if provided.
* @template T
* @param array<T> $array
* @param array-key $key
* @param ?T $default
* @param mixed $default
* @return ?T
* @throws Nette\InvalidArgumentException if item does not exist and default value is not provided
* @param string|int $key
*/
public static function pick(array &$array, $key, $default = null)
{
@ -294,13 +328,17 @@ class Arrays
}
}
/**
* Tests whether at least one element in the array passes the test implemented by the
* provided callback with signature `function ($value, $key, array $array): bool`.
* Tests whether at least one element in the array passes the test implemented by the provided function,
* which has the signature `function ($value, $key, array $array): bool`.
* @template K
* @template V
* @param iterable<K, V> $array
* @param callable(V, K, ($array is array ? array<K, V> : iterable<K, V>)): bool $predicate
*/
public static function some(iterable $array, callable $callback) : bool
public static function some(iterable $array, callable $predicate) : bool
{
foreach ($array as $k => $v) {
if ($callback($v, $k, $array)) {
if ($predicate($v, $k, $array)) {
return \true;
}
}
@ -309,25 +347,54 @@ class Arrays
/**
* Tests whether all elements in the array pass the test implemented by the provided function,
* which has the signature `function ($value, $key, array $array): bool`.
* @template K
* @template V
* @param iterable<K, V> $array
* @param callable(V, K, ($array is array ? array<K, V> : iterable<K, V>)): bool $predicate
*/
public static function every(iterable $array, callable $callback) : bool
public static function every(iterable $array, callable $predicate) : bool
{
foreach ($array as $k => $v) {
if (!$callback($v, $k, $array)) {
if (!$predicate($v, $k, $array)) {
return \false;
}
}
return \true;
}
/**
* Calls $callback on all elements in the array and returns the array of return values.
* The callback has the signature `function ($value, $key, array $array): bool`.
* Returns a new array containing all key-value pairs matching the given $predicate.
* The callback has the signature `function (mixed $value, int|string $key, array $array): bool`.
* @template K of array-key
* @template V
* @param array<K, V> $array
* @param callable(V, K, array<K, V>): bool $predicate
* @return array<K, V>
*/
public static function map(iterable $array, callable $callback) : array
public static function filter(array $array, callable $predicate) : array
{
$res = [];
foreach ($array as $k => $v) {
$res[$k] = $callback($v, $k, $array);
if ($predicate($v, $k, $array)) {
$res[$k] = $v;
}
}
return $res;
}
/**
* Returns an array containing the original keys and results of applying the given transform function to each element.
* The function has signature `function ($value, $key, array $array): mixed`.
* @template K of array-key
* @template V
* @template R
* @param iterable<K, V> $array
* @param callable(V, K, ($array is array ? array<K, V> : iterable<K, V>)): R $transformer
* @return array<K, R>
*/
public static function map(iterable $array, callable $transformer) : array
{
$res = [];
foreach ($array as $k => $v) {
$res[$k] = $transformer($v, $k, $array);
}
return $res;
}
@ -361,7 +428,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;
@ -370,8 +437,8 @@ class Arrays
}
/**
* Converts value to array key.
* @param mixed $value
* @return array-key
* @return int|string
* @param mixed $value
*/
public static function toKey($value)
{

View File

@ -15,41 +15,6 @@ use function is_array, is_object, is_string;
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
@ -75,9 +40,9 @@ 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
* @param mixed $callable
*/
public static function check($callable, bool $syntax = \false)
{
@ -88,15 +53,13 @@ final class Callback
}
/**
* Converts PHP callback to textual form. Class or method may not exists.
* @param mixed $callable
* @param mixed $callable
*/
public static function toString($callable) : string
{
if ($callable instanceof \Closure) {
$inner = self::unwrap($callable);
return '{closure' . ($inner instanceof \Closure ? '}' : ' ' . self::toString($inner) . '}');
} elseif (is_string($callable) && $callable[0] === "\x00") {
return '{lambda}';
} else {
\is_callable(is_object($callable) ? [$callable, '__invoke'] : $callable, \true, $textual);
return $textual;
@ -105,20 +68,20 @@ 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
* @return \ReflectionMethod|\ReflectionFunction
*/
public static function toReflection($callable) : \ReflectionFunctionAbstract
public static function toReflection($callable)
{
if ($callable instanceof \Closure) {
$callable = self::unwrap($callable);
}
if (is_string($callable) && \strpos($callable, '::')) {
return new \ReflectionMethod($callable);
if (is_string($callable) && \strpos($callable, '::') !== \false) {
return new ReflectionMethod($callable);
} elseif (is_array($callable)) {
return new \ReflectionMethod($callable[0], $callable[1]);
return new ReflectionMethod($callable[0], $callable[1]);
} elseif (is_object($callable) && !$callable instanceof \Closure) {
return new \ReflectionMethod($callable, '__invoke');
return new ReflectionMethod($callable, '__invoke');
} else {
return new \ReflectionFunction($callable);
}
@ -132,18 +95,18 @@ final class Callback
}
/**
* Unwraps closure created by Closure::fromCallable().
* @return callable|array
* @return callable|mixed[]
*/
public static function unwrap(\Closure $closure)
{
$r = new \ReflectionFunction($closure);
$class = $r->getClosureScopeClass();
if (\substr($r->name, -1) === '}') {
$class = ($nullsafeVariable1 = $r->getClosureScopeClass()) ? $nullsafeVariable1->name : null;
if (\substr_compare($r->name, '}', -\strlen('}')) === 0) {
return $closure;
} elseif (($obj = $r->getClosureThis()) && $class && \get_class($obj) === $class->name) {
} elseif (($obj = $r->getClosureThis()) && \get_class($obj) === $class) {
return [$obj, $r->name];
} elseif ($class) {
return [$class->name, $r->name];
return [$class, $r->name];
} else {
return $r->name;
}

View File

@ -28,9 +28,9 @@ class DateTime extends \DateTime implements \JsonSerializable
public const YEAR = 31557600;
/**
* 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.
* @param string|int|\DateTimeInterface|null $time
* @return static
*/
public static function from($time)
{
@ -40,7 +40,7 @@ class DateTime extends \DateTime implements \JsonSerializable
if ($time <= self::YEAR) {
$time += \time();
}
return (new static('@' . $time))->setTimezone(new \DateTimeZone(\date_default_timezone_get()));
return (new static())->setTimestamp((int) $time);
} else {
// textual or null
return new static((string) $time);
@ -48,8 +48,8 @@ class DateTime extends \DateTime implements \JsonSerializable
}
/**
* Creates DateTime object.
* @return static
* @throws Nette\InvalidArgumentException if the date and time are not valid.
* @return static
*/
public static function fromParts(int $year, int $month, int $day, int $hour = 0, int $minute = 0, float $second = 0.0)
{
@ -61,20 +61,15 @@ 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)
* @param string|\DateTimeZone|null $timezone
* @return static|false
*/
#[\ReturnTypeWillChange]
public static function createFromFormat($format, $time, $timezone = null)
public static function createFromFormat(string $format, string $time, $timezone = null)
{
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);
return $date ? static::from($date) : \false;
@ -94,7 +89,7 @@ class DateTime extends \DateTime implements \JsonSerializable
return $this->format('Y-m-d H:i:s');
}
/**
* Creates a copy with a modified time.
* You'd better use: (clone $dt)->modify(...)
* @return static
*/
public function modifyClone(string $modify = '')

View File

@ -0,0 +1,57 @@
<?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 RectorPrefix202403\Nette\Utils;
use RectorPrefix202403\Nette;
/**
* Represents the file or directory returned by the Finder.
* @internal do not create instances directly
*/
final class FileInfo extends \SplFileInfo
{
/**
* @var string
*/
private $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

@ -13,7 +13,6 @@ use RectorPrefix202403\Nette;
*/
final class FileSystem
{
use Nette\StaticClass;
/**
* Creates a directory if it does not exist, including parent directories.
* @throws Nette\IOException on error occurred
@ -50,12 +49,26 @@ final class FileSystem
}
} else {
static::createDir(\dirname($target));
if (($s = @\fopen($origin, 'rb')) && ($d = @\fopen($target, 'wb')) && @\stream_copy_to_stream($s, $d) === \false) {
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()));
}
}
}
/**
* 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
@ -113,6 +126,31 @@ final class FileSystem
}
return $content;
}
/**
* 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], function ($error) use($file) {
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
@ -183,4 +221,19 @@ 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://
}
}

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

@ -0,0 +1,456 @@
<?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 RectorPrefix202403\Nette\Utils;
use RectorPrefix202403\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 $find = [];
/** @var string[] */
private $in = [];
/** @var \Closure[] */
private $filters = [];
/** @var \Closure[] */
private $descentFilters = [];
/** @var array<string|self> */
private $appends = [];
/**
* @var bool
*/
private $childFirst = \false;
/** @var ?callable */
private $sort;
/**
* @var int
*/
private $maxDepth = -1;
/**
* @var bool
*/
private $ignoreUnreadableDirs = \true;
/**
* Begins search for files and directories matching mask.
* @param string|mixed[] $masks
* @return static
*/
public static function find($masks = ['*'])
{
$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.
* @param string|mixed[] $masks
* @return static
*/
public static function findFiles($masks = ['*'])
{
$masks = \is_array($masks) ? $masks : \func_get_args();
// compatibility with variadic
return (new static())->addMask($masks, 'file');
}
/**
* Begins search for directories matching mask.
* @param string|mixed[] $masks
* @return static
*/
public static function findDirectories($masks = ['*'])
{
$masks = \is_array($masks) ? $masks : \func_get_args();
// compatibility with variadic
return (new static())->addMask($masks, 'dir');
}
/**
* Finds files matching the specified masks.
* @param string|mixed[] $masks
* @return static
*/
public function files($masks = ['*'])
{
return $this->addMask((array) $masks, 'file');
}
/**
* Finds directories matching the specified masks.
* @param string|mixed[] $masks
* @return static
*/
public function directories($masks = ['*'])
{
return $this->addMask((array) $masks, 'dir');
}
/**
* @return static
*/
private function addMask(array $masks, string $mode)
{
foreach ($masks as $mask) {
$mask = FileSystem::unixSlashes($mask);
if ($mode === 'dir') {
$mask = \rtrim($mask, '/');
}
if ($mask === '' || $mode === 'file' && \substr_compare($mask, '/', -\strlen('/')) === 0) {
throw new Nette\InvalidArgumentException("Invalid mask '{$mask}'");
}
if (\strncmp($mask, '**/', \strlen('**/')) === 0) {
$mask = \substr($mask, 3);
}
$this->find[] = [$mask, $mode];
}
return $this;
}
/**
* Searches in the given directories. Wildcards are allowed.
* @param string|mixed[] $paths
* @return static
*/
public function in($paths)
{
$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.
* @param string|mixed[] $paths
* @return static
*/
public function from($paths)
{
$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.
* @return static
*/
public function childFirst(bool $state = \true)
{
$this->childFirst = $state;
return $this;
}
/**
* Ignores unreadable directories. By default, this is enabled.
* @return static
*/
public function ignoreUnreadableDirs(bool $state = \true)
{
$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
* @return static
*/
public function sortBy(callable $callback)
{
$this->sort = $callback;
return $this;
}
/**
* Sorts files in each directory naturally by name.
* @return static
*/
public function sortByName()
{
$this->sort = function (FileInfo $a, FileInfo $b) : int {
return \strnatcmp($a->getBasename(), $b->getBasename());
};
return $this;
}
/**
* Adds the specified paths or appends a new finder that returns.
* @param string|mixed[]|null $paths
* @return static
*/
public function append($paths = null)
{
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.
* @param string|mixed[] $masks
* @return static
*/
public function exclude($masks)
{
$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 = function (FileInfo $file) use($end, $re) : bool {
return $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
* @return static
*/
public function filter(callable $callback)
{
$this->filters[] = \Closure::fromCallable($callback);
return $this;
}
/**
* It descends only to directories that match the specified filter.
* @param callable(FileInfo): bool $callback
* @return static
*/
public function descentFilter(callable $callback)
{
$this->descentFilters[] = \Closure::fromCallable($callback);
return $this;
}
/**
* Sets the maximum depth of entries.
* @return static
*/
public function limitDepth(?int $depth)
{
$this->maxDepth = $depth ?? -1;
return $this;
}
/**
* Restricts the search by size. $operator accepts "[operator] [size] [unit]" example: >=10kB
* @return static
*/
public function size(string $operator, ?int $size = null)
{
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' => 1000.0, 'm' => 1000000.0, 'g' => 1000000000.0];
$size *= $units[\strtolower($unit)];
$operator = $operator ?: '=';
}
return $this->filter(function (FileInfo $file) use($operator, $size) : bool {
return !$file->isFile() || Helpers::compare($file->getSize(), $operator, $size);
});
}
/**
* Restricts the search by modified time. $operator accepts "[operator] [date]" example: >1978-01-23
* @param string|int|\DateTimeInterface|null $date
* @return static
*/
public function date(string $operator, $date = null)
{
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(function (FileInfo $file) use($operator, $date) : bool {
return !$file->isFile() || Helpers::compare($file->getMTime(), $operator, $date);
});
}
/********************* iterator generator ****************d*g**/
/**
* Returns an array with all found files and directories.
* @return list<FileInfo>
*/
public function collect() : array
{
return \iterator_to_array($this->getIterator(), \false);
}
/** @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<object{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(\sprintf("Directory '%s' does not exist.", \rtrim($dir, '/\\')));
}
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->{'is' . $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 = $res ?? $filter($file);
if (!$res) {
return \false;
}
}
return \true;
}
/** @return array<string, array<object{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] = $dirCache[$base] ?? (\strpbrk($base, '*?[') ? \glob($base, \GLOB_NOSORT | \GLOB_ONLYDIR | \GLOB_NOESCAPE) : [\strtr($base, ['[[]' => '[', '[]]' => ']'])]);
// unescape [ and ]
if (!$dirs) {
throw new Nette\InvalidStateException(\sprintf("Directory '%s' does not exist.", \rtrim($base, '/\\')));
}
$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 (\strncmp($mask, './', \strlen('./')) === 0) {
$anchor = '^';
$mask = \substr($mask, 2);
} else {
$anchor = '(?:^|/)';
}
$pattern = \strtr(\preg_quote($mask, '#'), ['\\*\\*/' => '(.+/)?', '\\*' => '[^/]*', '\\?' => '[^/]', '\\[\\!' => '[^', '\\[' => '[', '\\]' => ']', '\\-' => '-']);
return '#' . $anchor . $pattern . '$#D' . (\defined('PHP_WINDOWS_VERSION_BUILD') ? 'i' : '');
}
}

View File

@ -38,7 +38,7 @@ class Helpers
}
/**
* Converts false to null, does not change other values.
* @param mixed $value
* @param mixed $value
* @return mixed
*/
public static function falseToNull($value)
@ -47,9 +47,9 @@ 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
* @param int|float $value
* @param int|float $min
* @param int|float $max
* @return int|float
*/
public static function clamp($value, $min, $max)
@ -75,4 +75,34 @@ class Helpers
}
return $best;
}
/**
* Compares two values in the same way that PHP does. Recognizes operators: >, >=, <, <=, =, ==, ===, !=, !==, <>
* @param mixed $left
* @param mixed $right
*/
public static function compare($left, string $operator, $right) : bool
{
switch ($operator) {
case '>':
return $left > $right;
case '>=':
return $left >= $right;
case '<':
return $left < $right;
case '<=':
return $left <= $right;
case '=':
case '==':
return $left == $right;
case '===':
return $left === $right;
case '!=':
case '<>':
return $left != $right;
case '!==':
return $left !== $right;
default:
throw new Nette\InvalidArgumentException("Unknown operator '{$operator}'");
}
}
}

View File

@ -232,16 +232,17 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
use Nette\SmartObject;
/** @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, 'isindex' => 1, 'wbr' => 1, 'command' => 1, 'track' => 1];
/** @var array<int, HtmlStringable|string> nodes */
protected $children = [];
/** @var string element's name */
private $name;
/** @var bool is element empty? */
private $isEmpty;
/** element's name
* @var string */
private $name = '';
/**
* @var bool
*/
private $isEmpty = \false;
/**
* Constructs new HTML element.
* @param array|string $attrs element's attributes or plain text content
@ -266,15 +267,17 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
}
/**
* Returns an object representing HTML text.
* @return static
*/
public static function fromHtml(string $html) : self
public static function fromHtml(string $html)
{
return (new static())->setHtml($html);
}
/**
* Returns an object representing plain text.
* @return static
*/
public static function fromText(string $text) : self
public static function fromText(string $text)
{
return (new static())->setText($text);
}
@ -334,8 +337,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
}
/**
* Appends value to element's attribute.
* @param mixed $value
* @param mixed $option
* @param mixed $value
* @param mixed $option
* @return static
*/
public function appendAttribute(string $name, $value, $option = \true)
@ -356,7 +359,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
}
/**
* Sets element's attribute.
* @param mixed $value
* @param mixed $value
* @return static
*/
public function setAttribute(string $name, $value)
@ -394,7 +397,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
}
/**
* Overloaded setter for element's attribute.
* @param mixed $value
* @param mixed $value
*/
public final function __set(string $name, $value) : void
{
@ -453,7 +456,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
* Special setter for element's attribute.
* @return static
*/
public final function href(string $path, ?array $query = null)
public final function href(string $path, array $query = [])
{
if ($query) {
$query = \http_build_query($query, '', '&');
@ -466,7 +469,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
}
/**
* Setter for data-* attributes. Booleans are converted to 'true' resp. 'false'.
* @param mixed $value
* @param mixed $value
* @return static
*/
public function data(string $name, $value = null)
@ -480,7 +483,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
}
/**
* Sets element's HTML content.
* @param HtmlStringable|string $html
* @param mixed $html
* @return static
*/
public final function setHtml($html)
@ -497,7 +500,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
}
/**
* Sets element's textual content.
* @param HtmlStringable|string|int|float $text
* @param mixed $text
* @return static
*/
public final function setText($text)
@ -517,7 +520,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
}
/**
* Adds new element's child.
* @param HtmlStringable|string $child Html node or raw HTML string
* @param mixed $child
* @return static
*/
public final function addHtml($child)
@ -526,7 +529,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
}
/**
* Appends plain-text string to element content.
* @param HtmlStringable|string|int|float $text
* @param mixed $text
* @return static
*/
public function addText($text)
@ -538,8 +541,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
* @param mixed[]|string|null $attrs
* @return static
*/
public final function create(string $name, $attrs = null)
{
@ -548,7 +551,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
}
/**
* Inserts child node.
* @param HtmlStringable|string $child Html node or raw HTML string
* @param \Nette\HtmlStringable|string $child
* @return static
*/
public function insert(?int $index, $child, bool $replace = \false)
@ -575,7 +578,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Returns child node (\ArrayAccess implementation).
* @param int $index
* @return HtmlStringable|string
* @return \Nette\HtmlStringable|string
*/
#[\ReturnTypeWillChange]
public final function offsetGet($index)
@ -657,22 +660,14 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
}
public final 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();
}
/**
* Returns element's start tag.
*/
public final function startTag() : string
{
return $this->name ? '<' . $this->name . $this->attributes() . (static::$xhtml && $this->isEmpty ? ' />' : '>') : '';
return $this->name ? '<' . $this->name . $this->attributes() . '>' : '';
}
/**
* Returns element's end tag.
@ -696,11 +691,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
if ($value === null || $value === \false) {
continue;
} elseif ($value === \true) {
if (static::$xhtml) {
$s .= ' ' . $key . '="' . $key . '"';
} else {
$s .= ' ' . $key;
}
$s .= ' ' . $key;
continue;
} elseif (is_array($value)) {
if (\strncmp($key, 'data-', 5) === 0) {
@ -724,8 +715,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
} else {
$value = (string) $value;
}
$q = \strpos($value, '"') === \false ? '"' : "'";
$s .= ' ' . $key . '=' . $q . \str_replace(['&', $q, '<'], ['&amp;', $q === '"' ? '&quot;' : '&#39;', self::$xhtml ? '&lt;' : '<'], $value) . (\strpos($value, '`') !== \false && \strpbrk($value, ' <>"\'') === \false ? ' ' : '') . $q;
$q = \strpos($value, '"') !== \false ? "'" : '"';
$s .= ' ' . $key . '=' . $q . \str_replace(['&', $q, '<'], ['&amp;', $q === '"' ? '&quot;' : '&#39;', '<'], $value) . (\strpos($value, '`') !== \false && \strpbrk($value, ' <>"\'') === \false ? ' ' : '') . $q;
}
$s = \str_replace('@', '&#64;', $s);
return $s;

View File

@ -18,99 +18,108 @@ use RectorPrefix202403\Nette;
* $image->send();
* </code>
*
* @method Image affine(array $affine, array $clip = null)
* @method array affineMatrixConcat(array $m1, array $m2)
* @method array affineMatrixGet(int $type, mixed $options = null)
* @method void alphaBlending(bool $on)
* @method void antialias(bool $on)
* @method void arc($x, $y, $w, $h, $start, $end, $color)
* @method void char(int $font, $x, $y, string $char, $color)
* @method void charUp(int $font, $x, $y, string $char, $color)
* @method int colorAllocate($red, $green, $blue)
* @method int colorAllocateAlpha($red, $green, $blue, $alpha)
* @method int colorAt($x, $y)
* @method int colorClosest($red, $green, $blue)
* @method int colorClosestAlpha($red, $green, $blue, $alpha)
* @method int colorClosestHWB($red, $green, $blue)
* @method void colorDeallocate($color)
* @method int colorExact($red, $green, $blue)
* @method int colorExactAlpha($red, $green, $blue, $alpha)
* @method Image affine(array $affine, ?array $clip = null)
* @method void alphaBlending(bool $enable)
* @method void antialias(bool $enable)
* @method void arc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color)
* @method int colorAllocate(int $red, int $green, int $blue)
* @method int colorAllocateAlpha(int $red, int $green, int $blue, int $alpha)
* @method int colorAt(int $x, int $y)
* @method int colorClosest(int $red, int $green, int $blue)
* @method int colorClosestAlpha(int $red, int $green, int $blue, int $alpha)
* @method int colorClosestHWB(int $red, int $green, int $blue)
* @method void colorDeallocate(int $color)
* @method int colorExact(int $red, int $green, int $blue)
* @method int colorExactAlpha(int $red, int $green, int $blue, int $alpha)
* @method void colorMatch(Image $image2)
* @method int colorResolve($red, $green, $blue)
* @method int colorResolveAlpha($red, $green, $blue, $alpha)
* @method void colorSet($index, $red, $green, $blue)
* @method array colorsForIndex($index)
* @method int colorResolve(int $red, int $green, int $blue)
* @method int colorResolveAlpha(int $red, int $green, int $blue, int $alpha)
* @method void colorSet(int $index, int $red, int $green, int $blue, int $alpha = 0)
* @method array colorsForIndex(int $color)
* @method int colorsTotal()
* @method int colorTransparent($color = null)
* @method int colorTransparent(?int $color = null)
* @method void convolution(array $matrix, float $div, float $offset)
* @method void copy(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH)
* @method void copyMerge(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity)
* @method void copyMergeGray(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity)
* @method void copyResampled(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH)
* @method void copyResized(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH)
* @method Image cropAuto(int $mode = -1, float $threshold = .5, int $color = -1)
* @method void ellipse($cx, $cy, $w, $h, $color)
* @method void fill($x, $y, $color)
* @method void filledArc($cx, $cy, $w, $h, $s, $e, $color, $style)
* @method void filledEllipse($cx, $cy, $w, $h, $color)
* @method void filledPolygon(array $points, $numPoints, $color)
* @method void filledRectangle($x1, $y1, $x2, $y2, $color)
* @method void fillToBorder($x, $y, $border, $color)
* @method void filter($filtertype)
* @method void copy(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH)
* @method void copyMerge(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $pct)
* @method void copyMergeGray(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $pct)
* @method void copyResampled(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH)
* @method void copyResized(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH)
* @method Image cropAuto(int $mode = IMG_CROP_DEFAULT, float $threshold = .5, ?ImageColor $color = null)
* @method void ellipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color)
* @method void fill(int $x, int $y, ImageColor $color)
* @method void filledArc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color, int $style)
* @method void filledEllipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color)
* @method void filledPolygon(array $points, ImageColor $color)
* @method void filledRectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color)
* @method void fillToBorder(int $x, int $y, ImageColor $borderColor, ImageColor $color)
* @method void filter(int $filter, ...$args)
* @method void flip(int $mode)
* @method array ftText($size, $angle, $x, $y, $col, string $fontFile, string $text, array $extrainfo = null)
* @method array ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options = [])
* @method void gammaCorrect(float $inputgamma, float $outputgamma)
* @method array getClip()
* @method int interlace($interlace = null)
* @method int getInterpolation()
* @method int interlace(?bool $enable = null)
* @method bool isTrueColor()
* @method void layerEffect($effect)
* @method void line($x1, $y1, $x2, $y2, $color)
* @method void openPolygon(array $points, int $num_points, int $color)
* @method void layerEffect(int $effect)
* @method void line(int $x1, int $y1, int $x2, int $y2, ImageColor $color)
* @method void openPolygon(array $points, ImageColor $color)
* @method void paletteCopy(Image $source)
* @method void paletteToTrueColor()
* @method void polygon(array $points, $numPoints, $color)
* @method array psText(string $text, $font, $size, $color, $backgroundColor, $x, $y, $space = null, $tightness = null, float $angle = null, $antialiasSteps = null)
* @method void rectangle($x1, $y1, $x2, $y2, $col)
* @method mixed resolution(int $res_x = null, int $res_y = null)
* @method Image rotate(float $angle, $backgroundColor)
* @method void saveAlpha(bool $saveflag)
* @method void polygon(array $points, ImageColor $color)
* @method void rectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color)
* @method mixed resolution(?int $resolutionX = null, ?int $resolutionY = null)
* @method Image rotate(float $angle, ImageColor $backgroundColor)
* @method void saveAlpha(bool $enable)
* @method Image scale(int $newWidth, int $newHeight = -1, int $mode = IMG_BILINEAR_FIXED)
* @method void setBrush(Image $brush)
* @method void setClip(int $x1, int $y1, int $x2, int $y2)
* @method void setInterpolation(int $method = IMG_BILINEAR_FIXED)
* @method void setPixel($x, $y, $color)
* @method void setPixel(int $x, int $y, ImageColor $color)
* @method void setStyle(array $style)
* @method void setThickness($thickness)
* @method void setThickness(int $thickness)
* @method void setTile(Image $tile)
* @method void string($font, $x, $y, string $s, $col)
* @method void stringUp($font, $x, $y, string $s, $col)
* @method void trueColorToPalette(bool $dither, $ncolors)
* @method array ttfText($size, $angle, $x, $y, $color, string $fontfile, string $text)
* @method void trueColorToPalette(bool $dither, int $ncolors)
* @method array ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontfile, string $text, array $options = [])
* @property-read positive-int $width
* @property-read positive-int $height
* @property-read resource|\GdImage $imageResource
* @property-read \GdImage $imageResource
*/
class Image
{
use Nette\SmartObject;
/** Prevent from getting resized to a bigger size than the original */
public const SHRINK_ONLY = 0b1;
public const ShrinkOnly = 0b1;
/** Resizes to a specified width and height without keeping aspect ratio */
public const STRETCH = 0b10;
public const Stretch = 0b10;
/** Resizes to fit into a specified width and height and preserves aspect ratio */
public const FIT = 0b0;
public const OrSmaller = 0b0;
/** Resizes while bounding the smaller dimension to the specified width or height and preserves aspect ratio */
public const FILL = 0b100;
public const OrBigger = 0b100;
/** Resizes to the smallest possible size to completely cover specified width and height and reserves aspect ratio */
public const EXACT = 0b1000;
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 JPEG = \IMAGETYPE_JPEG, PNG = \IMAGETYPE_PNG, GIF = \IMAGETYPE_GIF, WEBP = \IMAGETYPE_WEBP, AVIF = 19, 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;";
private const Formats = [self::JPEG => 'jpeg', self::PNG => 'png', self::GIF => 'gif', self::WEBP => 'webp', self::AVIF => 'avif', self::BMP => 'bmp'];
/** @var resource|\GdImage */
public const JPEG = ImageType::JPEG, PNG = ImageType::PNG, GIF = ImageType::GIF, WEBP = ImageType::WEBP, AVIF = ImageType::AVIF, BMP = ImageType::BMP;
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 = [ImageType::JPEG => 'jpeg', ImageType::PNG => 'png', ImageType::GIF => 'gif', ImageType::WEBP => 'webp', ImageType::AVIF => 'avif', ImageType::BMP => 'bmp'];
/**
* @var \GdImage
*/
private $image;
/**
* Returns RGB color (0..255) and transparency (0..127).
* @deprecated use ImageColor::rgb()
*/
public static function rgb(int $red, int $green, int $blue, int $transparency = 0) : array
{
@ -135,9 +144,9 @@ 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
* @return static
*/
public static function fromString(string $s, ?int &$type = null)
{
@ -150,7 +159,10 @@ class Image
}
return self::invokeSafe('imagecreatefromstring', $s, 'Unable to open image from string.', __METHOD__);
}
private static function invokeSafe(string $func, string $arg, string $message, string $callee) : self
/**
* @return static
*/
private static function invokeSafe(string $func, string $arg, string $message, string $callee)
{
$errors = [];
$res = Callback::invokeSafe($func, [$arg], function (string $message) use(&$errors) : void {
@ -167,10 +179,11 @@ class Image
* Creates a new true color image of the given dimensions. The default color is black.
* @param positive-int $width
* @param positive-int $height
* @return static
* @throws Nette\NotSupportedException if gd extension is not loaded
* @param \Nette\Utils\ImageColor|mixed[]|null $color
* @return static
*/
public static function fromBlank(int $width, int $height, ?array $color = null)
public static function fromBlank(int $width, int $height, $color = null)
{
if (!\extension_loaded('gd')) {
throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
@ -178,18 +191,17 @@ class Image
if ($width < 1 || $height < 1) {
throw new Nette\InvalidArgumentException('Image width and height must be greater than zero.');
}
$image = \imagecreatetruecolor($width, $height);
$image = new static(\imagecreatetruecolor($width, $height));
if ($color) {
$color += ['alpha' => 0];
$color = \imagecolorresolvealpha($image, $color['red'], $color['green'], $color['blue'], $color['alpha']);
\imagealphablending($image, \false);
\imagefilledrectangle($image, 0, 0, $width - 1, $height - 1, $color);
\imagealphablending($image, \true);
$image->alphablending(\false);
$image->filledrectangle(0, 0, $width - 1, $height - 1, $color);
$image->alphablending(\true);
}
return new static($image);
return $image;
}
/**
* Returns the type of image from file.
* @return ImageType::*|null
*/
public static function detectTypeFromFile(string $file, &$width = null, &$height = null) : ?int
{
@ -199,6 +211,7 @@ class Image
}
/**
* Returns the type of image from string.
* @return ImageType::*|null
*/
public static function detectTypeFromString(string $s, &$width = null, &$height = null) : ?int
{
@ -207,7 +220,9 @@ class Image
return isset(self::Formats[$type]) ? $type : null;
}
/**
* Returns the file extension for the given `Image::XXX` constant.
* Returns the file extension for the given image type.
* @param ImageType::* $type
* @return value-of<self::Formats>
*/
public static function typeToExtension(int $type) : string
{
@ -217,11 +232,12 @@ class Image
return self::Formats[$type];
}
/**
* Returns the `Image::XXX` constant for given file extension.
* Returns the image type for given file extension.
* @return ImageType::*
*/
public static function extensionToType(string $extension) : int
{
$extensions = \array_flip(self::Formats) + ['jpg' => self::JPEG];
$extensions = \array_flip(self::Formats) + ['jpg' => ImageType::JPEG];
$extension = \strtolower($extension);
if (!isset($extensions[$extension])) {
throw new Nette\InvalidArgumentException("Unsupported file extension '{$extension}'.");
@ -229,17 +245,53 @@ class Image
return $extensions[$extension];
}
/**
* Returns the mime type for the given `Image::XXX` constant.
* Returns the mime type for the given image type.
* @param ImageType::* $type
*/
public static function typeToMimeType(int $type) : string
{
return 'image/' . self::typeToExtension($type);
}
/**
* Wraps GD image.
* @param resource|\GdImage $image
* @param ImageType::* $type
*/
public function __construct($image)
public static function isTypeSupported(int $type) : bool
{
switch ($type) {
case ImageType::JPEG:
return \IMG_JPG;
case ImageType::PNG:
return \IMG_PNG;
case ImageType::GIF:
return \IMG_GIF;
case ImageType::WEBP:
return \IMG_WEBP;
case ImageType::AVIF:
return 256;
case ImageType::BMP:
return \IMG_BMP;
default:
return 0;
}
}
/** @return ImageType[] */
public static function getSupportedTypes() : array
{
$flag = \imagetypes();
return \array_filter([
$flag & \IMG_GIF ? ImageType::GIF : null,
$flag & \IMG_JPG ? ImageType::JPEG : null,
$flag & \IMG_PNG ? ImageType::PNG : null,
$flag & \IMG_WEBP ? ImageType::WEBP : null,
$flag & 256 ? ImageType::AVIF : null,
// IMG_AVIF
$flag & \IMG_BMP ? ImageType::BMP : null,
]);
}
/**
* Wraps GD image.
*/
public function __construct(\GdImage $image)
{
$this->setImageResource($image);
\imagesavealpha($image, \true);
@ -262,40 +314,36 @@ class Image
}
/**
* Sets image resource.
* @param resource|\GdImage $image
* @return static
*/
protected function setImageResource($image)
protected function setImageResource(\GdImage $image)
{
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;
}
/**
* 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
* Scales an image. Width and height accept pixels or percent.
* @param int-mask-of<self::OrSmaller|self::OrBigger|self::Stretch|self::Cover|self::ShrinkOnly> $mode
* @param int|string|null $width
* @param int|string|null $height
* @return static
*/
public function resize($width, $height, int $flags = self::FIT)
public function resize($width, $height, int $mode = self::OrSmaller)
{
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();
$newImage = static::fromBlank($newWidth, $newHeight, ImageColor::rgb(0, 0, 0, 0))->getImageResource();
\imagecopyresampled($newImage, $this->image, 0, 0, 0, 0, $newWidth, $newHeight, $this->getWidth(), $this->getHeight());
$this->image = $newImage;
}
@ -305,11 +353,10 @@ class Image
return $this;
}
/**
* 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 int-mask-of<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) : array
public static function calculateSize(int $srcWidth, int $srcHeight, $newWidth, $newHeight, int $mode = self::OrSmaller) : array
{
if ($newWidth === null) {
} elseif (self::isPercent($newWidth)) {
@ -321,18 +368,18 @@ 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) {
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
@ -348,10 +395,10 @@ class Image
// fit height
$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;
}
$scale = \min($scale);
@ -361,11 +408,11 @@ class Image
return [\max($newWidth, 1), \max($newHeight, 1)];
}
/**
* 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
* Crops image. Arguments accepts pixels or percent.
* @param int|string $left
* @param int|string $top
* @param int|string $width
* @param int|string $height
* @return static
*/
public function crop($left, $top, $width, $height)
@ -375,18 +422,18 @@ class Image
$this->image = \imagecrop($this->image, $r);
\imagesavealpha($this->image, \true);
} else {
$newImage = static::fromBlank($r['width'], $r['height'], self::RGB(0, 0, 0, 127))->getImageResource();
$newImage = static::fromBlank($r['width'], $r['height'], ImageColor::rgb(0, 0, 0, 0))->getImageResource();
\imagecopy($newImage, $this->image, 0, 0, $r['x'], $r['y'], $r['width'], $r['height']);
$this->image = $newImage;
}
return $this;
}
/**
* 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.
* @param int|string $left
* @param int|string $top
* @param int|string $newWidth
* @param int|string $newHeight
*/
public static function calculateCutout(int $srcWidth, int $srcHeight, $left, $top, $newWidth, $newHeight) : array
{
@ -429,10 +476,10 @@ class Image
return $this;
}
/**
* 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<0, 100> $opacity 0..100
* @param int|string $left
* @param int|string $top
* @return static
*/
public function place(self $image, $left = 0, $top = 0, int $opacity = 100)
@ -474,8 +521,35 @@ class Image
\imagecopy($this->image, $output, $left, $top, 0, 0, $width, $height);
return $this;
}
/**
* Calculates the bounding box for a TrueType text. Returns keys left, top, width and height.
*/
public static function calculateTextBox(string $text, string $fontFile, float $size, float $angle = 0, array $options = []) : array
{
$box = \imagettfbbox($size, $angle, $fontFile, $text, $options);
return ['left' => $minX = \min([$box[0], $box[2], $box[4], $box[6]]), 'top' => $minY = \min([$box[1], $box[3], $box[5], $box[7]]), 'width' => \max([$box[0], $box[2], $box[4], $box[6]]) - $minX + 1, 'height' => \max([$box[1], $box[3], $box[5], $box[7]]) - $minY + 1];
}
/**
* Draw a rectangle.
*/
public function rectangleWH(int $x, int $y, int $width, int $height, ImageColor $color) : void
{
if ($width !== 0 && $height !== 0) {
$this->rectangle($x, $y, $x + $width + ($width > 0 ? -1 : 1), $y + $height + ($height > 0 ? -1 : 1), $color);
}
}
/**
* Draw a filled rectangle.
*/
public function filledRectangleWH(int $x, int $y, int $width, int $height, ImageColor $color) : void
{
if ($width !== 0 && $height !== 0) {
$this->filledRectangle($x, $y, $x + $width + ($width > 0 ? -1 : 1), $y + $height + ($height > 0 ? -1 : 1), $color);
}
}
/**
* Saves image to the file. Quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9).
* @param ImageType::*|null $type
* @throws ImageException
*/
public function save(string $file, ?int $quality = null, ?int $type = null) : void
@ -485,10 +559,11 @@ class Image
}
/**
* Outputs image to string. Quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9).
* @param ImageType::* $type
*/
public function toString(int $type = self::JPEG, ?int $quality = null) : string
public function toString(int $type = ImageType::JPEG, ?int $quality = null) : string
{
return Helpers::capture(function () use($type, $quality) {
return Helpers::capture(function () use($type, $quality) : void {
$this->output($type, $quality);
});
}
@ -497,57 +572,51 @@ 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();
}
/**
* Outputs image to browser. Quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9).
* @param ImageType::* $type
* @throws ImageException
*/
public function send(int $type = self::JPEG, ?int $quality = null) : void
public function send(int $type = ImageType::JPEG, ?int $quality = null) : void
{
\header('Content-Type: ' . self::typeToMimeType($type));
$this->output($type, $quality);
}
/**
* Outputs image to browser or file.
* @param ImageType::* $type
* @throws ImageException
*/
private function output(int $type, ?int $quality, ?string $file = null) : void
{
switch ($type) {
case self::JPEG:
case ImageType::JPEG:
$quality = $quality === null ? 85 : \max(0, \min(100, $quality));
$success = @\imagejpeg($this->image, $file, $quality);
// @ is escalated to exception
break;
case self::PNG:
case ImageType::PNG:
$quality = $quality === null ? 9 : \max(0, \min(9, $quality));
$success = @\imagepng($this->image, $file, $quality);
// @ is escalated to exception
break;
case self::GIF:
case ImageType::GIF:
$success = @\imagegif($this->image, $file);
// @ is escalated to exception
break;
case self::WEBP:
case ImageType::WEBP:
$quality = $quality === null ? 80 : \max(0, \min(100, $quality));
$success = @\imagewebp($this->image, $file, $quality);
// @ is escalated to exception
break;
case self::AVIF:
case ImageType::AVIF:
$quality = $quality === null ? 30 : \max(0, \min(100, $quality));
$success = @\imageavif($this->image, $file, $quality);
// @ is escalated to exception
break;
case self::BMP:
case ImageType::BMP:
$success = @\imagebmp($this->image, $file);
// @ is escalated to exception
break;
@ -560,8 +629,8 @@ class Image
}
/**
* Call to undefined method.
* @return mixed
* @throws Nette\MemberAccessException
* @return mixed
*/
public function __call(string $name, array $args)
{
@ -572,13 +641,12 @@ class Image
foreach ($args as $key => $value) {
if ($value instanceof self) {
$args[$key] = $value->getImageResource();
} elseif (\is_array($value) && isset($value['red'])) {
// rgb
$args[$key] = \imagecolorallocatealpha($this->image, $value['red'], $value['green'], $value['blue'], $value['alpha']) ?: \imagecolorresolvealpha($this->image, $value['red'], $value['green'], $value['blue'], $value['alpha']);
} elseif ($value instanceof ImageColor || \is_array($value) && isset($value['red'])) {
$args[$key] = $this->resolveColor($value);
}
}
$res = $function($this->image, ...$args);
return $res instanceof \GdImage || \is_resource($res) && \get_resource_type($res) === 'gd' ? $this->setImageResource($res) : $res;
return \is_resource($res) || $res instanceof \GdImage ? $this->setImageResource($res) : $res;
}
public function __clone()
{
@ -588,11 +656,11 @@ class Image
$this->setImageResource(\imagecreatefromstring(\ob_get_clean()));
}
/**
* @param int|string $num in pixels or percent
* @param int|string $num
*/
private static function isPercent(&$num) : bool
{
if (\is_string($num) && \substr($num, -1) === '%') {
if (\is_string($num) && \substr_compare($num, '%', -\strlen('%')) === 0) {
$num = (float) \substr($num, 0, -1);
return \true;
} elseif (\is_int($num) || $num === (string) (int) $num) {
@ -608,4 +676,12 @@ class Image
{
throw new Nette\NotSupportedException('You cannot serialize or unserialize ' . self::class . ' instances.');
}
/**
* @param \Nette\Utils\ImageColor|mixed[] $color
*/
public function resolveColor($color) : int
{
$color = $color instanceof ImageColor ? $color->toRGBA() : \array_values($color);
return \imagecolorallocatealpha($this->image, ...$color) ?: \imagecolorresolvealpha($this->image, ...$color);
}
}

View File

@ -0,0 +1,66 @@
<?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 RectorPrefix202403\Nette\Utils;
use RectorPrefix202403\Nette;
/**
* Represent RGB color (0..255) with opacity (0..1).
*/
class ImageColor
{
/**
* @var int
*/
public $red;
/**
* @var int
*/
public $green;
/**
* @var int
*/
public $blue;
/**
* @var float
*/
public $opacity = 1;
public static function rgb(int $red, int $green, int $blue, float $opacity = 1) : self
{
return new self($red, $green, $blue, $opacity);
}
/**
* Accepts formats #RRGGBB, #RRGGBBAA, #RGB, #RGBA
*/
public static function hex(string $hex) : self
{
$hex = \ltrim($hex, '#');
$len = \strlen($hex);
if ($len === 3 || $len === 4) {
return new self((int) \hexdec($hex[0]) * 17, (int) \hexdec($hex[1]) * 17, (int) \hexdec($hex[2]) * 17, (int) \hexdec($hex[3] ?? 'F') * 17 / 255);
} elseif ($len === 6 || $len === 8) {
return new self((int) \hexdec($hex[0] . $hex[1]), (int) \hexdec($hex[2] . $hex[3]), (int) \hexdec($hex[4] . $hex[5]), (int) \hexdec(($hex[6] ?? 'F') . ($hex[7] ?? 'F')) / 255);
} else {
throw new Nette\InvalidArgumentException('Invalid hex color format.');
}
}
private function __construct(int $red, int $green, int $blue, float $opacity = 1)
{
$this->red = $red;
$this->green = $green;
$this->blue = $blue;
$this->opacity = $opacity;
$this->red = \max(0, \min(255, $red));
$this->green = \max(0, \min(255, $green));
$this->blue = \max(0, \min(255, $blue));
$this->opacity = \max(0, \min(1, $opacity));
}
public function toRGBA() : array
{
return [\max(0, \min(255, $this->red)), \max(0, \min(255, $this->green)), \max(0, \min(255, $this->blue)), \max(0, \min(127, (int) \round(127 - $this->opacity * 127)))];
}
}

View File

@ -0,0 +1,17 @@
<?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 RectorPrefix202403\Nette\Utils;
/**
* Type of image file.
*/
/*enum*/
final class ImageType
{
public const JPEG = \IMAGETYPE_JPEG, PNG = \IMAGETYPE_PNG, GIF = \IMAGETYPE_GIF, WEBP = \IMAGETYPE_WEBP, AVIF = 19, BMP = \IMAGETYPE_BMP;
}

View File

@ -0,0 +1,142 @@
<?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 RectorPrefix202403\Nette\Utils;
use RectorPrefix202403\Nette;
/**
* Utilities for iterables.
*/
final class Iterables
{
use Nette\StaticClass;
/**
* Tests for the presence of value.
* @param mixed $value
*/
public static function contains(iterable $iterable, $value) : bool
{
foreach ($iterable as $v) {
if ($v === $value) {
return \true;
}
}
return \false;
}
/**
* Tests for the presence of key.
* @param mixed $key
*/
public static function containsKey(iterable $iterable, $key) : bool
{
foreach ($iterable as $k => $v) {
if ($k === $key) {
return \true;
}
}
return \false;
}
/**
* Returns the first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null.
* The $predicate has the signature `function (mixed $value, mixed $key, iterable $iterable): bool`.
* @template T
* @param iterable<T> $iterable
* @return ?T
*/
public static function first(iterable $iterable, ?callable $predicate = null, ?callable $else = null)
{
foreach ($iterable as $k => $v) {
if (!$predicate || $predicate($v, $k, $iterable)) {
return $v;
}
}
return $else ? $else() : null;
}
/**
* Returns the key of first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null.
* The $predicate has the signature `function (mixed $value, mixed $key, iterable $iterable): bool`.
* @template T
* @param iterable<T, mixed> $iterable
* @return ?T
*/
public static function firstKey(iterable $iterable, ?callable $predicate = null, ?callable $else = null)
{
foreach ($iterable as $k => $v) {
if (!$predicate || $predicate($v, $k, $iterable)) {
return $k;
}
}
return $else ? $else() : null;
}
/**
* Tests whether at least one element in the iterator passes the test implemented by the
* provided callback with signature `function (mixed $value, mixed $key, iterable $iterable): bool`.
* @template K
* @template V
* @param iterable<K, V> $iterable
* @param callable(V, K, iterable<K, V>): bool $predicate
*/
public static function some(iterable $iterable, callable $predicate) : bool
{
foreach ($iterable as $k => $v) {
if ($predicate($v, $k, $iterable)) {
return \true;
}
}
return \false;
}
/**
* Tests whether all elements in the iterator pass the test implemented by the provided function,
* which has the signature `function (mixed $value, mixed $key, iterable $iterable): bool`.
* @template K
* @template V
* @param iterable<K, V> $iterable
* @param callable(V, K, iterable<K, V>): bool $predicate
*/
public static function every(iterable $iterable, callable $predicate) : bool
{
foreach ($iterable as $k => $v) {
if (!$predicate($v, $k, $iterable)) {
return \false;
}
}
return \true;
}
/**
* Iterator that filters elements according to a given $predicate. Maintains original keys.
* The callback has the signature `function (mixed $value, mixed $key, iterable $iterable): bool`.
* @template K
* @template V
* @param iterable<K, V> $iterable
* @param callable(V, K, iterable<K, V>): bool $predicate
* @return \Generator<K, V>
*/
public static function filter(iterable $iterable, callable $predicate) : \Generator
{
foreach ($iterable as $k => $v) {
if ($predicate($v, $k, $iterable)) {
(yield $k => $v);
}
}
}
/**
* Iterator that transforms values by calling $transformer. Maintains original keys.
* The callback has the signature `function (mixed $value, mixed $key, iterable $iterable): bool`.
* @template K
* @template V
* @template R
* @param iterable<K, V> $iterable
* @param callable(V, K, iterable<K, V>): R $transformer
* @return \Generator<K, R>
*/
public static function map(iterable $iterable, callable $transformer) : \Generator
{
foreach ($iterable as $k => $v) {
(yield $k => $transformer($v, $k, $iterable));
}
}
}

View File

@ -14,18 +14,28 @@ use RectorPrefix202403\Nette;
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
* @param bool|int $pretty
* @param mixed $value
*/
public static function encode($value, int $flags = 0) : string
public static function encode($value, $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 | (\defined('JSON_PRESERVE_ZERO_FRACTION') ? \JSON_PRESERVE_ZERO_FRACTION : 0);
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);
if ($error = \json_last_error()) {
@ -34,13 +44,16 @@ final class Json
return $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
* @param bool|int $forceArrays
* @return mixed
*/
public static function decode(string $json, int $flags = 0)
public static function decode(string $json, $forceArrays = \false)
{
$value = \json_decode($json, null, 512, $flags | \JSON_BIGINT_AS_STRING);
$flags = \is_int($forceArrays) ? $forceArrays : ($forceArrays ? \JSON_OBJECT_AS_ARRAY : 0);
$flags |= \JSON_BIGINT_AS_STRING;
$value = \json_decode($json, null, 512, $flags);
if ($error = \json_last_error()) {
throw new JsonException(\json_last_error_msg(), $error);
}

View File

@ -148,12 +148,13 @@ final class ObjectHelpers
$traits += $trait->getTraits();
}
} 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
* @return bool|string
*/
public static function hasProperty(string $class, string $name)
{

View File

@ -1,32 +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 RectorPrefix202403\Nette\Utils;
use RectorPrefix202403\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

@ -29,13 +29,17 @@ use RectorPrefix202403\Nette;
class Paginator
{
use Nette\SmartObject;
/** @var int */
/**
* @var int
*/
private $base = 1;
/** @var int */
/** @var positive-int */
private $itemsPerPage = 1;
/** @var int */
/**
* @var int
*/
private $page = 1;
/** @var int|null */
/** @var int<0, max>|null */
private $itemCount;
/**
* Sets current page number.

View File

@ -8,6 +8,7 @@ declare (strict_types=1);
namespace RectorPrefix202403\Nette\Utils;
use RectorPrefix202403\Nette;
use Random\Randomizer;
/**
* Secure random string generator.
*/
@ -20,14 +21,17 @@ 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 {
$charlist = \preg_replace_callback('#.-.#', function (array $m) : string {
return \implode('', \range($m[0][0], $m[0][2]));
}, $charlist), 3);
}, $charlist);
$charlist = \count_chars($charlist, 3);
$chLen = \strlen($charlist);
if ($length < 1) {
throw new Nette\InvalidArgumentException('Length must be greater than zero.');
} elseif ($chLen < 2) {
throw new Nette\InvalidArgumentException('Character list must contain at least two chars.');
} elseif (\PHP_VERSION_ID >= 80300) {
return (new Randomizer())->getBytesFromString($charlist, $length);
}
$res = '';
for ($i = 0; $i < $length; $i++) {

View File

@ -14,95 +14,18 @@ use RectorPrefix202403\Nette;
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.
* @deprecated use Nette\Utils\Type::fromReflection()
*/
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.
* @deprecated use Nette\Utils\Type::fromReflection()
*/
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.
* @deprecated use Nette\Utils\Type::fromReflection()
*/
public static function getPropertyType(\ReflectionProperty $prop) : ?string
{
return self::getType($prop, \PHP_VERSION_ID >= 70400 ? \method_exists($prop, 'getType') ? $prop->getType() : null : 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
*/
/** @deprecated use native ReflectionParameter::getDefaultValue()
* @return mixed */
public static function getParameterDefaultValue(\ReflectionParameter $param)
{
if ($param->isDefaultValueConstant()) {
@ -248,10 +171,10 @@ final class Reflection
$namespace = $class = null;
$classLevel = $level = 0;
$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 = [];
@ -297,10 +220,10 @@ final class Reflection
break;
case \T_CURLY_OPEN:
case \T_DOLLAR_OPEN_CURLY_BRACES:
case '{':
case \ord('{'):
$level++;
break;
case '}':
case \ord('}'):
if ($level === $classLevel) {
$class = $classLevel = 0;
}
@ -309,14 +232,16 @@ final class Reflection
}
return $res;
}
/**
* @param string|int|mixed[] $take
*/
private static function fetch(array &$tokens, $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;
}
\next($tokens);

View File

@ -0,0 +1,35 @@
<?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 RectorPrefix202403\Nette\Utils;
/**
* ReflectionMethod preserving the original class name.
* @internal
*/
final class ReflectionMethod extends \ReflectionMethod
{
/**
* @var \ReflectionClass
*/
private $originalClass;
/**
* @param object|string $objectOrMethod
*/
public function __construct($objectOrMethod, ?string $method = null)
{
if (\is_string($objectOrMethod) && \strpos($objectOrMethod, '::') !== \false) {
[$objectOrMethod, $method] = \explode('::', $objectOrMethod, 2);
}
parent::__construct($objectOrMethod, $method);
$this->originalClass = new \ReflectionClass($objectOrMethod);
}
public function getOriginalClass() : \ReflectionClass
{
return $this->originalClass;
}
}

View File

@ -16,9 +16,11 @@ use function is_array, is_object, strlen;
class Strings
{
use Nette\StaticClass;
public const TRIM_CHARACTERS = " \t\n\r\x00\v ";
public const TrimCharacters = " \t\n\r\x00\v  ";
/** @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
{
@ -46,21 +48,35 @@ class Strings
return \iconv('UTF-32BE', 'UTF-8//IGNORE', \pack('N', $code));
}
/**
* 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;
}
/**
* 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 \substr_compare($haystack, $needle, -strlen($needle)) === 0;
}
/**
* Does $haystack contain $needle?
* @deprecated use str_contains()
*/
public static function contains(string $haystack, string $needle) : bool
{
@ -95,7 +111,7 @@ class Strings
if (\class_exists('Normalizer', \false) && ($n = \Normalizer::normalize($s, \Normalizer::FORM_C)) !== \false) {
$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]);
// right trim
@ -104,12 +120,26 @@ class Strings
$s = \trim($s, "\n");
return $s;
}
/**
* 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?||~", "\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||~", \PHP_EOL, $s);
}
/**
* Converts UTF-8 string to ASCII, ie removes diacritics etc.
@ -297,12 +327,19 @@ class Strings
*/
public static function length(string $s) : int
{
return \function_exists('mb_strlen') ? \mb_strlen($s, 'UTF-8') : strlen(\utf8_decode($s));
switch (\true) {
case \extension_loaded('mbstring'):
return \mb_strlen($s, 'UTF-8');
case \extension_loaded('iconv'):
return \iconv_strlen($s, 'UTF-8');
default:
return strlen(@\utf8_decode($s));
}
}
/**
* 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', '');
@ -394,8 +431,8 @@ class Strings
return Helpers::falseToNull($pos);
}
/**
* 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.
* @param bool|int $captureOffset
*/
public static function split(
string $subject,
@ -403,14 +440,21 @@ class Strings
* @language
*/
string $pattern,
int $flags = 0
$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) ? $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`.
* @param bool|int $captureOffset
*/
public static function match(
string $subject,
@ -418,18 +462,31 @@ class Strings
* @language
*/
string $pattern,
int $flags = 0,
int $offset = 0
$captureOffset = \false,
int $offset = 0,
bool $unmatchedAsNull = \false,
bool $utf8 = \false
) : ?array
{
$flags = \is_int($captureOffset) ? $captureOffset : ($captureOffset ? \PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? \PREG_UNMATCHED_AS_NULL : 0);
if ($utf8) {
$offset = strlen(self::substring($subject, 0, $offset));
$pattern .= 'u';
}
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;
}
return self::pcre('preg_match', [$pattern, $subject, &$m, $flags, $offset]) ? $m : null;
}
/**
* 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.
* @param bool|int $captureOffset
*/
public static function matchAll(
string $subject,
@ -437,20 +494,28 @@ class Strings
* @language
*/
string $pattern,
int $flags = 0,
int $offset = 0
$captureOffset = \false,
int $offset = 0,
bool $unmatchedAsNull = \false,
bool $patternOrder = \false,
bool $utf8 = \false
) : array
{
$flags = \is_int($captureOffset) ? $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 [];
}
self::pcre('preg_match_all', [$pattern, $subject, &$m, $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
* @param string|mixed[] $pattern
* @param string|callable $replacement
*/
public static function replace(
string $subject,
@ -459,20 +524,53 @@ class Strings
*/
$pattern,
$replacement = '',
int $limit = -1
int $limit = -1,
bool $captureOffset = \false,
bool $unmatchedAsNull = \false,
bool $utf8 = \false
) : string
{
if (is_object($replacement) || is_array($replacement)) {
if (!\is_callable($replacement, \false, $textual)) {
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 = function ($m) use($replacement, $subject) {
return $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(function ($item) {
return $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)
{
@ -481,7 +579,7 @@ class Strings
throw new RegexpException($message . ' in pattern: ' . \implode(' or ', (array) $args[0]));
});
if (($code = \preg_last_error()) && ($res === null || !\in_array($func, ['preg_filter', 'preg_replace_callback', 'preg_replace'], \true))) {
throw new RegexpException((RegexpException::MESSAGES[$code] ?? 'Unknown error') . ' (pattern: ' . \implode(' or ', (array) $args[0]) . ')', $code);
throw new RegexpException(\preg_last_error_msg() . ' (pattern: ' . \implode(' or ', (array) $args[0]) . ')', $code);
}
return $res;
}

View File

@ -15,26 +15,28 @@ final class Type
{
/** @var array<int, string|self> */
private $types;
/** @var bool */
/**
* @var bool
*/
private $simple;
/** @var string |, & */
/**
* @var string
*/
private $kind;
// | &
/**
* Creates a Type object based on reflection. Resolves self, static and parent to the actual class name.
* If the subject has no type, it returns null.
* @param \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $reflection
* @param \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $reflection
*/
public static function fromReflection($reflection) : ?self
{
if ($reflection instanceof \ReflectionProperty && \PHP_VERSION_ID < 70400) {
return null;
} elseif ($reflection instanceof \ReflectionMethod) {
$type = $reflection->getReturnType() ?? (\PHP_VERSION_ID >= 80100 ? $reflection->getTentativeReturnType() : null);
} else {
$type = $reflection instanceof \ReflectionFunctionAbstract ? $reflection->getReturnType() : $reflection->getType();
}
$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;
}
/**
* @return $this|string
*/
private static function fromReflectionType(\ReflectionType $type, $of, bool $asObject)
{
if ($type instanceof \ReflectionNamedType) {
@ -68,15 +70,17 @@ final class Type
}
/**
* Resolves 'self', 'static' and 'parent' to the actual class name.
* @param \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $of
* @param \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $of
*/
public static function resolve(string $type, $of) : string
{
$lower = \strtolower($type);
if ($of instanceof \ReflectionFunction) {
return $type;
} elseif ($lower === 'self' || $lower === 'static') {
} elseif ($lower === 'self') {
return $of->getDeclaringClass()->name;
} elseif ($lower === 'static') {
return ($of instanceof ReflectionMethod ? $of->getOriginalClass() : $of->getDeclaringClass())->name;
} elseif ($lower === 'parent' && $of->getDeclaringClass()->getParentClass()) {
return $of->getDeclaringClass()->getParentClass()->name;
} else {
@ -203,9 +207,8 @@ final class Type
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 ? \strcasecmp($type, $subtype) === 0 : \is_a($subtype, $type, \true);
return Arrays::some($subtypes, function ($subtype) use($type) {
return Validators::isBuiltinType($type) ? \strcasecmp($type, $subtype) === 0 : \is_a($subtype, $type, \true);
});
});
}

View File

@ -64,8 +64,8 @@ class Validators
protected static $counters = ['string' => 'strlen', 'unicode' => [Strings::class, 'length'], 'array' => 'count', 'list' => 'count', 'alnum' => 'strlen', 'alpha' => 'strlen', 'digit' => 'strlen', 'lower' => 'strlen', 'space' => 'strlen', 'upper' => 'strlen', 'xdigit' => 'strlen'];
/**
* Verifies that the value is of expected types separated by pipe.
* @param mixed $value
* @throws AssertionException
* @param mixed $value
*/
public static function assert($value, string $expected, string $label = 'variable') : void
{
@ -84,7 +84,6 @@ 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") : void
@ -97,17 +96,17 @@ class Validators
}
/**
* Verifies that the value is of expected types separated by pipe.
* @param mixed $value
* @param mixed $value
*/
public static function is($value, string $expected) : bool
{
foreach (\explode('|', $expected) as $item) {
if (\substr($item, -2) === '[]') {
if (\substr_compare($item, '[]', -\strlen('[]')) === 0) {
if (\is_iterable($value) && self::everyIs($value, \substr($item, 0, -2))) {
return \true;
}
continue;
} elseif (\substr($item, 0, 1) === '?') {
} elseif (\strncmp($item, '?', \strlen('?')) === 0) {
$item = \substr($item, 1);
if ($value === null) {
return \true;
@ -162,7 +161,8 @@ class Validators
}
/**
* Checks if the value is an integer or a float.
* @param mixed $value
* @return ($value is int|float ? true : false)
* @param mixed $value
*/
public static function isNumber($value) : bool
{
@ -170,7 +170,8 @@ class Validators
}
/**
* Checks if the value is an integer or a integer written in a string.
* @param mixed $value
* @return ($value is non-empty-string ? bool : ($value is int ? true : false))
* @param mixed $value
*/
public static function isNumericInt($value) : bool
{
@ -178,7 +179,8 @@ class Validators
}
/**
* Checks if the value is a number or a number written in a string.
* @param mixed $value
* @return ($value is non-empty-string ? bool : ($value is int|float ? true : false))
* @param mixed $value
*/
public static function isNumeric($value) : bool
{
@ -186,7 +188,7 @@ class Validators
}
/**
* Checks if the value is a syntactically correct callback.
* @param mixed $value
* @param mixed $value
*/
public static function isCallable($value) : bool
{
@ -194,7 +196,7 @@ class Validators
}
/**
* Checks if the value is a valid UTF-8 string.
* @param mixed $value
* @param mixed $value
*/
public static function isUnicode($value) : bool
{
@ -202,7 +204,8 @@ class Validators
}
/**
* Checks if the value is 0, '', false or null.
* @param mixed $value
* @return ($value is 0|''|false|null ? true : false)
* @param mixed $value
*/
public static function isNone($value) : bool
{
@ -216,9 +219,9 @@ class Validators
}
/**
* Checks if a variable is a zero-based integer indexed array.
* @param mixed $value
* @deprecated use Nette\Utils\Arrays::isList
* @return ($value is list ? true : false)
* @param mixed $value
*/
public static function isList($value) : bool
{
@ -227,7 +230,7 @@ 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
* @param mixed $value
*/
public static function isInRange($value, array $range) : bool
{
@ -258,12 +261,12 @@ class Validators
$alpha = "a-z\x80-\xff";
// superset of IDN
return (bool) \preg_match(<<<XX
\t\t(^
\t\t\t("([ !#-[\\]-~]*|\\\\[ -~])+"|{$atom}+(\\.{$atom}+)*) # quoted or unquoted
\t\t\t@
\t\t\t([0-9{$alpha}]([-0-9{$alpha}]{0,61}[0-9{$alpha}])?\\.)+ # domain - RFC 1034
\t\t\t[{$alpha}]([-0-9{$alpha}]{0,17}[{$alpha}])? # top domain
\t\t\$)Dix
(^(?n)
\t("([ !#-[\\]-~]*|\\\\[ -~])+"|{$atom}+(\\.{$atom}+)*) # quoted or unquoted
\t@
\t([0-9{$alpha}]([-0-9{$alpha}]{0,61}[0-9{$alpha}])?\\.)+ # domain - RFC 1034
\t[{$alpha}]([-0-9{$alpha}]{0,17}[{$alpha}])? # top domain
\$)Dix
XX
, $value);
}
@ -274,18 +277,18 @@ XX
{
$alpha = "a-z\x80-\xff";
return (bool) \preg_match(<<<XX
\t\t(^
\t\t\thttps?://(
\t\t\t\t(([-_0-9{$alpha}]+\\.)* # subdomain
\t\t\t\t\t[0-9{$alpha}]([-0-9{$alpha}]{0,61}[0-9{$alpha}])?\\.)? # domain
\t\t\t\t\t[{$alpha}]([-0-9{$alpha}]{0,17}[{$alpha}])? # top domain
\t\t\t\t|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3} # IPv4
\t\t\t\t|\\[[0-9a-f:]{3,39}\\] # IPv6
\t\t\t)(:\\d{1,5})? # port
\t\t\t(/\\S*)? # path
\t\t\t(\\?\\S*)? # query
\t\t\t(\\#\\S*)? # fragment
\t\t\$)Dix
(^(?n)
\thttps?://(
\t\t(([-_0-9{$alpha}]+\\.)* # subdomain
\t\t\t[0-9{$alpha}]([-0-9{$alpha}]{0,61}[0-9{$alpha}])?\\.)? # domain
\t\t\t[{$alpha}]([-0-9{$alpha}]{0,17}[{$alpha}])? # top domain
\t\t|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3} # IPv4
\t\t|\\[[0-9a-f:]{3,39}\\] # IPv6
\t)(:\\d{1,5})? # port
\t(/\\S*)? # path
\t(\\?\\S*)? # query
\t(\\#\\S*)? # fragment
\$)Dix
XX
, $value);
}
@ -298,6 +301,7 @@ XX
}
/**
* Checks whether the input is a class, interface or trait.
* @deprecated
*/
public static function isType(string $type) : bool
{
@ -330,11 +334,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
~((?n)
\?? (?<type> \\? (?<name> [a-zA-Z_\x7f-\xff][\w\x7f-\xff]*) (\\ (?&name))* ) |
(?<intersection> (?&type) (& (?&type))+ ) |
(?<upart> (?&type) | \( (?&intersection) \) ) (\| (?&upart))+
)$~xAD
XX
, $type);
}

View File

@ -22,7 +22,7 @@ class UnknownImageFileException extends ImageException
/**
* The exception that indicates error of JSON encoding/decoding.
*/
class JsonException extends \Exception
class JsonException extends \JsonException
{
}
/**
@ -30,7 +30,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'];
}
/**
* The exception that indicates assertion error.

View File

@ -30,7 +30,7 @@ if (!function_exists('humbug_phpscoper_expose_class')) {
}
}
humbug_phpscoper_expose_class('AutoloadIncluder', 'RectorPrefix202403\AutoloadIncluder');
humbug_phpscoper_expose_class('ComposerAutoloaderInit1a5f2ab6c5d7fa473cb6ef1c69de9082', 'RectorPrefix202403\ComposerAutoloaderInit1a5f2ab6c5d7fa473cb6ef1c69de9082');
humbug_phpscoper_expose_class('ComposerAutoloaderInit67be42e0079886f0083b7116ae1de531', 'RectorPrefix202403\ComposerAutoloaderInit67be42e0079886f0083b7116ae1de531');
humbug_phpscoper_expose_class('Product', 'RectorPrefix202403\Product');
// Function aliases. For more information see:

View File

@ -3,8 +3,7 @@
"description": "Contracts for production code of RuleDocGenerator",
"license": "MIT",
"require": {
"php": ">=8.1",
"nette\/utils": "^3.2"
"php": ">=8.1"
},
"require-dev": {
"php-parallel-lint\/php-parallel-lint": "^1.3",

View File

@ -3,7 +3,6 @@
declare (strict_types=1);
namespace Symplify\RuleDocGenerator\ValueObject;
use RectorPrefix202403\Nette\Utils\Strings;
use Symplify\RuleDocGenerator\Contract\CodeSampleInterface;
use Symplify\RuleDocGenerator\Exception\PoorDocumentationException;
use Symplify\RuleDocGenerator\Exception\ShouldNotHappenException;
@ -70,7 +69,8 @@ final class RuleDefinition
if ($this->ruleClass === null) {
throw new ShouldNotHappenException();
}
return (string) Strings::after($this->ruleClass, '\\', -1);
// get short class name
return \basename(\str_replace('\\', '/', $this->ruleClass));
}
/**
* @return CodeSampleInterface[]