Update tools
This commit is contained in:
parent
150ed9dd55
commit
46fbf4d175
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phive xmlns="https://phar.io/phive">
|
||||
<phar name="php-cs-fixer" version="^3.71" installed="3.92.3" location="./tools/php-cs-fixer" copy="true"/>
|
||||
<phar name="php-cs-fixer" version="^3.71" installed="3.95.1" location="./tools/php-cs-fixer" copy="true"/>
|
||||
<phar name="infection" version="^0.29" installed="0.29.14" location="./tools/infection" copy="true"/>
|
||||
</phive>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^2.1.33",
|
||||
"phpstan/phpstan": "^2.1.54",
|
||||
"phpstan/extension-installer": "^1.4.3",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.7",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.11",
|
||||
"tomasvotruba/type-coverage": "^2.1.0",
|
||||
"ergebnis/phpstan-rules": "^2.12.0"
|
||||
"ergebnis/phpstan-rules": "^2.13.1"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
|
|
|
|||
|
|
@ -4,40 +4,40 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "4409cdc49212f7e878b14b7ae9285bb2",
|
||||
"content-hash": "5352dc46c7b6a0ee40e2263c4eac895f",
|
||||
"packages": [],
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "ergebnis/phpstan-rules",
|
||||
"version": "2.12.0",
|
||||
"version": "2.13.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ergebnis/phpstan-rules.git",
|
||||
"reference": "c4e0121a937b3b551f800a86e7d78794da2783ea"
|
||||
"reference": "f69db86b98595c34fc1f61c89fe3b380141aa519"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ergebnis/phpstan-rules/zipball/c4e0121a937b3b551f800a86e7d78794da2783ea",
|
||||
"reference": "c4e0121a937b3b551f800a86e7d78794da2783ea",
|
||||
"url": "https://api.github.com/repos/ergebnis/phpstan-rules/zipball/f69db86b98595c34fc1f61c89fe3b380141aa519",
|
||||
"reference": "f69db86b98595c34fc1f61c89fe3b380141aa519",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
|
||||
"phpstan/phpstan": "^2.1.8"
|
||||
"phpstan/phpstan": "^2.1.35"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "^4.0.0 || ^5.0.0",
|
||||
"doctrine/orm": "^2.20.0 || ^3.3.0",
|
||||
"ergebnis/composer-normalize": "^2.47.0",
|
||||
"ergebnis/license": "^2.6.0",
|
||||
"ergebnis/php-cs-fixer-config": "^6.54.0",
|
||||
"ergebnis/composer-normalize": "^2.49.0",
|
||||
"ergebnis/license": "^2.7.0",
|
||||
"ergebnis/php-cs-fixer-config": "^6.59.0",
|
||||
"ergebnis/phpunit-slow-test-detector": "^2.20.0",
|
||||
"fakerphp/faker": "^1.24.1",
|
||||
"phpstan/extension-installer": "^1.4.3",
|
||||
"phpstan/phpstan-deprecation-rules": "^2.0.3",
|
||||
"phpstan/phpstan-phpunit": "^2.0.7",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.6",
|
||||
"phpstan/phpstan-phpunit": "^2.0.12",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.8",
|
||||
"phpunit/phpunit": "^9.6.21",
|
||||
"psr/container": "^2.0.2",
|
||||
"symfony/finder": "^5.4.45",
|
||||
|
|
@ -78,20 +78,20 @@
|
|||
"security": "https://github.com/ergebnis/phpstan-rules/blob/main/.github/SECURITY.md",
|
||||
"source": "https://github.com/ergebnis/phpstan-rules"
|
||||
},
|
||||
"time": "2025-09-07T13:31:33+00:00"
|
||||
"time": "2026-01-27T17:13:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nette/utils",
|
||||
"version": "v4.1.1",
|
||||
"version": "v4.1.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nette/utils.git",
|
||||
"reference": "c99059c0315591f1a0db7ad6002000288ab8dc72"
|
||||
"reference": "7da6c396d7ebe142bc857c20479d5e70a5e1aac7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nette/utils/zipball/c99059c0315591f1a0db7ad6002000288ab8dc72",
|
||||
"reference": "c99059c0315591f1a0db7ad6002000288ab8dc72",
|
||||
"url": "https://api.github.com/repos/nette/utils/zipball/7da6c396d7ebe142bc857c20479d5e70a5e1aac7",
|
||||
"reference": "7da6c396d7ebe142bc857c20479d5e70a5e1aac7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -103,8 +103,10 @@
|
|||
},
|
||||
"require-dev": {
|
||||
"jetbrains/phpstorm-attributes": "^1.2",
|
||||
"nette/phpstan-rules": "^1.0",
|
||||
"nette/tester": "^2.5",
|
||||
"phpstan/phpstan-nette": "^2.0@stable",
|
||||
"phpstan/extension-installer": "^1.4@stable",
|
||||
"phpstan/phpstan": "^2.1@stable",
|
||||
"tracy/tracy": "^2.9"
|
||||
},
|
||||
"suggest": {
|
||||
|
|
@ -165,9 +167,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nette/utils/issues",
|
||||
"source": "https://github.com/nette/utils/tree/v4.1.1"
|
||||
"source": "https://github.com/nette/utils/tree/v4.1.4"
|
||||
},
|
||||
"time": "2025-12-22T12:14:32+00:00"
|
||||
"time": "2026-05-11T20:49:54+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/extension-installer",
|
||||
|
|
@ -219,11 +221,11 @@
|
|||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "2.1.33",
|
||||
"version": "2.1.54",
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/9e800e6bee7d5bd02784d4c6069b48032d16224f",
|
||||
"reference": "9e800e6bee7d5bd02784d4c6069b48032d16224f",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/8be50c3992107dc837b17da4d140fbbdf9a5c5bd",
|
||||
"reference": "8be50c3992107dc837b17da4d140fbbdf9a5c5bd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -268,25 +270,25 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-05T10:24:31+00:00"
|
||||
"time": "2026-04-29T13:31:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-strict-rules",
|
||||
"version": "2.0.7",
|
||||
"version": "2.0.11",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
|
||||
"reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538"
|
||||
"reference": "9b000a578b85b32945b358b172c7b20e91189024"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/d6211c46213d4181054b3d77b10a5c5cb0d59538",
|
||||
"reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/9b000a578b85b32945b358b172c7b20e91189024",
|
||||
"reference": "9b000a578b85b32945b358b172c7b20e91189024",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0",
|
||||
"phpstan/phpstan": "^2.1.29"
|
||||
"phpstan/phpstan": "^2.1.39"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-parallel-lint/php-parallel-lint": "^1.2",
|
||||
|
|
@ -312,11 +314,14 @@
|
|||
"MIT"
|
||||
],
|
||||
"description": "Extra strict and opinionated rules for PHPStan",
|
||||
"keywords": [
|
||||
"static analysis"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
|
||||
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.7"
|
||||
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.11"
|
||||
},
|
||||
"time": "2025-09-26T11:19:08+00:00"
|
||||
"time": "2026-05-02T06:54:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "tomasvotruba/type-coverage",
|
||||
|
|
|
|||
|
|
@ -49,6 +49,9 @@ return array(
|
|||
'Nette\\Utils\\JsonException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php',
|
||||
'Nette\\Utils\\ObjectHelpers' => $vendorDir . '/nette/utils/src/Utils/ObjectHelpers.php',
|
||||
'Nette\\Utils\\Paginator' => $vendorDir . '/nette/utils/src/Utils/Paginator.php',
|
||||
'Nette\\Utils\\Process' => $vendorDir . '/nette/utils/src/Utils/Process.php',
|
||||
'Nette\\Utils\\ProcessFailedException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php',
|
||||
'Nette\\Utils\\ProcessTimeoutException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php',
|
||||
'Nette\\Utils\\Random' => $vendorDir . '/nette/utils/src/Utils/Random.php',
|
||||
'Nette\\Utils\\Reflection' => $vendorDir . '/nette/utils/src/Utils/Reflection.php',
|
||||
'Nette\\Utils\\ReflectionMethod' => $vendorDir . '/nette/utils/src/Utils/ReflectionMethod.php',
|
||||
|
|
|
|||
|
|
@ -97,6 +97,9 @@ class ComposerStaticInitf9e7218f71d5874b5632927df4f72bd7
|
|||
'Nette\\Utils\\JsonException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php',
|
||||
'Nette\\Utils\\ObjectHelpers' => __DIR__ . '/..' . '/nette/utils/src/Utils/ObjectHelpers.php',
|
||||
'Nette\\Utils\\Paginator' => __DIR__ . '/..' . '/nette/utils/src/Utils/Paginator.php',
|
||||
'Nette\\Utils\\Process' => __DIR__ . '/..' . '/nette/utils/src/Utils/Process.php',
|
||||
'Nette\\Utils\\ProcessFailedException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php',
|
||||
'Nette\\Utils\\ProcessTimeoutException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php',
|
||||
'Nette\\Utils\\Random' => __DIR__ . '/..' . '/nette/utils/src/Utils/Random.php',
|
||||
'Nette\\Utils\\Reflection' => __DIR__ . '/..' . '/nette/utils/src/Utils/Reflection.php',
|
||||
'Nette\\Utils\\ReflectionMethod' => __DIR__ . '/..' . '/nette/utils/src/Utils/ReflectionMethod.php',
|
||||
|
|
|
|||
|
|
@ -2,42 +2,42 @@
|
|||
"packages": [
|
||||
{
|
||||
"name": "ergebnis/phpstan-rules",
|
||||
"version": "2.12.0",
|
||||
"version_normalized": "2.12.0.0",
|
||||
"version": "2.13.1",
|
||||
"version_normalized": "2.13.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ergebnis/phpstan-rules.git",
|
||||
"reference": "c4e0121a937b3b551f800a86e7d78794da2783ea"
|
||||
"reference": "f69db86b98595c34fc1f61c89fe3b380141aa519"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ergebnis/phpstan-rules/zipball/c4e0121a937b3b551f800a86e7d78794da2783ea",
|
||||
"reference": "c4e0121a937b3b551f800a86e7d78794da2783ea",
|
||||
"url": "https://api.github.com/repos/ergebnis/phpstan-rules/zipball/f69db86b98595c34fc1f61c89fe3b380141aa519",
|
||||
"reference": "f69db86b98595c34fc1f61c89fe3b380141aa519",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
|
||||
"phpstan/phpstan": "^2.1.8"
|
||||
"phpstan/phpstan": "^2.1.35"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "^4.0.0 || ^5.0.0",
|
||||
"doctrine/orm": "^2.20.0 || ^3.3.0",
|
||||
"ergebnis/composer-normalize": "^2.47.0",
|
||||
"ergebnis/license": "^2.6.0",
|
||||
"ergebnis/php-cs-fixer-config": "^6.54.0",
|
||||
"ergebnis/composer-normalize": "^2.49.0",
|
||||
"ergebnis/license": "^2.7.0",
|
||||
"ergebnis/php-cs-fixer-config": "^6.59.0",
|
||||
"ergebnis/phpunit-slow-test-detector": "^2.20.0",
|
||||
"fakerphp/faker": "^1.24.1",
|
||||
"phpstan/extension-installer": "^1.4.3",
|
||||
"phpstan/phpstan-deprecation-rules": "^2.0.3",
|
||||
"phpstan/phpstan-phpunit": "^2.0.7",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.6",
|
||||
"phpstan/phpstan-phpunit": "^2.0.12",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.8",
|
||||
"phpunit/phpunit": "^9.6.21",
|
||||
"psr/container": "^2.0.2",
|
||||
"symfony/finder": "^5.4.45",
|
||||
"symfony/process": "^5.4.47"
|
||||
},
|
||||
"time": "2025-09-07T13:31:33+00:00",
|
||||
"time": "2026-01-27T17:13:06+00:00",
|
||||
"type": "phpstan-extension",
|
||||
"extra": {
|
||||
"phpstan": {
|
||||
|
|
@ -78,17 +78,17 @@
|
|||
},
|
||||
{
|
||||
"name": "nette/utils",
|
||||
"version": "v4.1.1",
|
||||
"version_normalized": "4.1.1.0",
|
||||
"version": "v4.1.4",
|
||||
"version_normalized": "4.1.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nette/utils.git",
|
||||
"reference": "c99059c0315591f1a0db7ad6002000288ab8dc72"
|
||||
"reference": "7da6c396d7ebe142bc857c20479d5e70a5e1aac7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nette/utils/zipball/c99059c0315591f1a0db7ad6002000288ab8dc72",
|
||||
"reference": "c99059c0315591f1a0db7ad6002000288ab8dc72",
|
||||
"url": "https://api.github.com/repos/nette/utils/zipball/7da6c396d7ebe142bc857c20479d5e70a5e1aac7",
|
||||
"reference": "7da6c396d7ebe142bc857c20479d5e70a5e1aac7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -100,8 +100,10 @@
|
|||
},
|
||||
"require-dev": {
|
||||
"jetbrains/phpstorm-attributes": "^1.2",
|
||||
"nette/phpstan-rules": "^1.0",
|
||||
"nette/tester": "^2.5",
|
||||
"phpstan/phpstan-nette": "^2.0@stable",
|
||||
"phpstan/extension-installer": "^1.4@stable",
|
||||
"phpstan/phpstan": "^2.1@stable",
|
||||
"tracy/tracy": "^2.9"
|
||||
},
|
||||
"suggest": {
|
||||
|
|
@ -112,7 +114,7 @@
|
|||
"ext-mbstring": "to use Strings::lower() etc...",
|
||||
"ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()"
|
||||
},
|
||||
"time": "2025-12-22T12:14:32+00:00",
|
||||
"time": "2026-05-11T20:49:54+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
|
|
@ -164,7 +166,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nette/utils/issues",
|
||||
"source": "https://github.com/nette/utils/tree/v4.1.1"
|
||||
"source": "https://github.com/nette/utils/tree/v4.1.4"
|
||||
},
|
||||
"install-path": "../nette/utils"
|
||||
},
|
||||
|
|
@ -221,12 +223,12 @@
|
|||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "2.1.33",
|
||||
"version_normalized": "2.1.33.0",
|
||||
"version": "2.1.54",
|
||||
"version_normalized": "2.1.54.0",
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/9e800e6bee7d5bd02784d4c6069b48032d16224f",
|
||||
"reference": "9e800e6bee7d5bd02784d4c6069b48032d16224f",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/8be50c3992107dc837b17da4d140fbbdf9a5c5bd",
|
||||
"reference": "8be50c3992107dc837b17da4d140fbbdf9a5c5bd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -235,7 +237,7 @@
|
|||
"conflict": {
|
||||
"phpstan/phpstan-shim": "*"
|
||||
},
|
||||
"time": "2025-12-05T10:24:31+00:00",
|
||||
"time": "2026-04-29T13:31:09+00:00",
|
||||
"bin": [
|
||||
"phpstan",
|
||||
"phpstan.phar"
|
||||
|
|
@ -277,22 +279,22 @@
|
|||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-strict-rules",
|
||||
"version": "2.0.7",
|
||||
"version_normalized": "2.0.7.0",
|
||||
"version": "2.0.11",
|
||||
"version_normalized": "2.0.11.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
|
||||
"reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538"
|
||||
"reference": "9b000a578b85b32945b358b172c7b20e91189024"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/d6211c46213d4181054b3d77b10a5c5cb0d59538",
|
||||
"reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/9b000a578b85b32945b358b172c7b20e91189024",
|
||||
"reference": "9b000a578b85b32945b358b172c7b20e91189024",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0",
|
||||
"phpstan/phpstan": "^2.1.29"
|
||||
"phpstan/phpstan": "^2.1.39"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-parallel-lint/php-parallel-lint": "^1.2",
|
||||
|
|
@ -300,7 +302,7 @@
|
|||
"phpstan/phpstan-phpunit": "^2.0",
|
||||
"phpunit/phpunit": "^9.6"
|
||||
},
|
||||
"time": "2025-09-26T11:19:08+00:00",
|
||||
"time": "2026-05-02T06:54:10+00:00",
|
||||
"type": "phpstan-extension",
|
||||
"extra": {
|
||||
"phpstan": {
|
||||
|
|
@ -320,9 +322,12 @@
|
|||
"MIT"
|
||||
],
|
||||
"description": "Extra strict and opinionated rules for PHPStan",
|
||||
"keywords": [
|
||||
"static analysis"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
|
||||
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.7"
|
||||
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.11"
|
||||
},
|
||||
"install-path": "../phpstan/phpstan-strict-rules"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<?php return array(
|
||||
'root' => array(
|
||||
'name' => '__root__',
|
||||
'pretty_version' => 'dev-main',
|
||||
'version' => 'dev-main',
|
||||
'reference' => 'd4d9bba3c5904cdebea5c9bebab27d8abd6c1af1',
|
||||
'pretty_version' => '7.0.x-dev',
|
||||
'version' => '7.0.9999999.9999999-dev',
|
||||
'reference' => '150ed9dd5535041d546fe5416a20dd4be31f6c36',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
|
|
@ -11,27 +11,27 @@
|
|||
),
|
||||
'versions' => array(
|
||||
'__root__' => array(
|
||||
'pretty_version' => 'dev-main',
|
||||
'version' => 'dev-main',
|
||||
'reference' => 'd4d9bba3c5904cdebea5c9bebab27d8abd6c1af1',
|
||||
'pretty_version' => '7.0.x-dev',
|
||||
'version' => '7.0.9999999.9999999-dev',
|
||||
'reference' => '150ed9dd5535041d546fe5416a20dd4be31f6c36',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'ergebnis/phpstan-rules' => array(
|
||||
'pretty_version' => '2.12.0',
|
||||
'version' => '2.12.0.0',
|
||||
'reference' => 'c4e0121a937b3b551f800a86e7d78794da2783ea',
|
||||
'pretty_version' => '2.13.1',
|
||||
'version' => '2.13.1.0',
|
||||
'reference' => 'f69db86b98595c34fc1f61c89fe3b380141aa519',
|
||||
'type' => 'phpstan-extension',
|
||||
'install_path' => __DIR__ . '/../ergebnis/phpstan-rules',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'nette/utils' => array(
|
||||
'pretty_version' => 'v4.1.1',
|
||||
'version' => '4.1.1.0',
|
||||
'reference' => 'c99059c0315591f1a0db7ad6002000288ab8dc72',
|
||||
'pretty_version' => 'v4.1.4',
|
||||
'version' => '4.1.4.0',
|
||||
'reference' => '7da6c396d7ebe142bc857c20479d5e70a5e1aac7',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../nette/utils',
|
||||
'aliases' => array(),
|
||||
|
|
@ -47,18 +47,18 @@
|
|||
'dev_requirement' => true,
|
||||
),
|
||||
'phpstan/phpstan' => array(
|
||||
'pretty_version' => '2.1.33',
|
||||
'version' => '2.1.33.0',
|
||||
'reference' => '9e800e6bee7d5bd02784d4c6069b48032d16224f',
|
||||
'pretty_version' => '2.1.54',
|
||||
'version' => '2.1.54.0',
|
||||
'reference' => '8be50c3992107dc837b17da4d140fbbdf9a5c5bd',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phpstan/phpstan',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'phpstan/phpstan-strict-rules' => array(
|
||||
'pretty_version' => '2.0.7',
|
||||
'version' => '2.0.7.0',
|
||||
'reference' => 'd6211c46213d4181054b3d77b10a5c5cb0d59538',
|
||||
'pretty_version' => '2.0.11',
|
||||
'version' => '2.0.11.0',
|
||||
'reference' => '9b000a578b85b32945b358b172c7b20e91189024',
|
||||
'type' => 'phpstan-extension',
|
||||
'install_path' => __DIR__ . '/../phpstan/phpstan-strict-rules',
|
||||
'aliases' => array(),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,27 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||
|
||||
## Unreleased
|
||||
|
||||
For a full diff see [`2.12.0...main`][2.12.0...main].
|
||||
For a full diff see [`2.13.1...main`][2.13.1...main].
|
||||
|
||||
## [`2.13.1`][2.13.1]
|
||||
|
||||
For a full diff see [`2.13.0...2.13.1`][2.13.0...2.13.1].
|
||||
|
||||
### Fixed
|
||||
|
||||
- Adjusted `Files\NoPhpstanIgnoreRule` to traverse into all nodes ([#1011]), by [@localheinz]
|
||||
|
||||
## [`2.13.0`][2.13.0]
|
||||
|
||||
For a full diff see [`2.12.0...2.13.0`][2.12.0...2.13.0].
|
||||
|
||||
### Added
|
||||
|
||||
- Added `Files\NoPhpstanIgnoreRule`, which reports an error when a `@phpstan-ignore`, `@phpstan-ignore-line`, or `@phpstan-ignore-next-line` tag is used to suppress an error ([#1009]), by [@localheinz]
|
||||
|
||||
### Changed
|
||||
|
||||
- Required `phpstan/phpstan:^2.1.35` ([#1010]), by [@localheinz]
|
||||
|
||||
## [`2.12.0`][2.12.0]
|
||||
|
||||
|
|
@ -588,6 +608,8 @@ For a full diff see [`362c7ea...0.1.0`][362c7ea...0.1.0].
|
|||
[2.10.5]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.10.5
|
||||
[2.11.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.11.0
|
||||
[2.12.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.12.0
|
||||
[2.13.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.13.0
|
||||
[2.13.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.13.1
|
||||
|
||||
[362c7ea...0.1.0]: https://github.com/ergebnis/phpstan-rules/compare/362c7ea...0.1.0
|
||||
[0.1.0...0.2.0]: https://github.com/ergebnis/phpstan-rules/compare/0.1.0...0.2.0
|
||||
|
|
@ -638,7 +660,9 @@ For a full diff see [`362c7ea...0.1.0`][362c7ea...0.1.0].
|
|||
[2.10.4...2.10.5]: https://github.com/ergebnis/phpstan-rules/compare/2.10.4...2.10.5
|
||||
[2.10.5...2.11.0]: https://github.com/ergebnis/phpstan-rules/compare/2.10.5...2.11.0
|
||||
[2.11.0...2.12.0]: https://github.com/ergebnis/phpstan-rules/compare/2.11.0...2.12.0
|
||||
[2.12.0...main]: https://github.com/ergebnis/phpstan-rules/compare/2.12.0...main
|
||||
[2.12.0...2.13.0]: https://github.com/ergebnis/phpstan-rules/compare/2.12.0...2.13.0
|
||||
[2.13.0...2.13.1]: https://github.com/ergebnis/phpstan-rules/compare/2.13.0...2.13.1
|
||||
[2.13.1...main]: https://github.com/ergebnis/phpstan-rules/compare/2.13.1...main
|
||||
|
||||
[#1]: https://github.com/ergebnis/phpstan-rules/pull/1
|
||||
[#4]: https://github.com/ergebnis/phpstan-rules/pull/4
|
||||
|
|
@ -736,6 +760,9 @@ For a full diff see [`362c7ea...0.1.0`][362c7ea...0.1.0].
|
|||
[#958]: https://github.com/ergebnis/phpstan-rules/pull/958
|
||||
[#972]: https://github.com/ergebnis/phpstan-rules/pull/972
|
||||
[#977]: https://github.com/ergebnis/phpstan-rules/pull/977
|
||||
[#1009]: https://github.com/ergebnis/phpstan-rules/pull/1009
|
||||
[#1010]: https://github.com/ergebnis/phpstan-rules/pull/1010
|
||||
[#1011]: https://github.com/ergebnis/phpstan-rules/pull/1011
|
||||
|
||||
[@cosmastech]: https://github.com/cosmastech
|
||||
[@enumag]: https://github.com/enumag
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2025 Andreas Möller
|
||||
Copyright (c) 2018-2026 Andreas Möller
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the _Software_), to deal in the Software without restriction, including without limitation the
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ This package provides the following rules for use with [`phpstan/phpstan`](https
|
|||
- [`Ergebnis\PHPStan\Rules\Expressions\NoEvalRule`](https://github.com/ergebnis/phpstan-rules#expressionsnoevalrule)
|
||||
- [`Ergebnis\PHPStan\Rules\Expressions\NoIssetRule`](https://github.com/ergebnis/phpstan-rules#expressionsnoissetrule)
|
||||
- [`Ergebnis\PHPStan\Rules\Files\DeclareStrictTypesRule`](https://github.com/ergebnis/phpstan-rules#filesdeclarestricttypesrule)
|
||||
- [`Ergebnis\PHPStan\Rules\Files\NoPhpstanIgnoreRule`](https://github.com/ergebnis/phpstan-rules#filesnophpstanignorerule)
|
||||
- [`Ergebnis\PHPStan\Rules\Functions\NoNullableReturnTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#functionsnonullablereturntypedeclarationrule)
|
||||
- [`Ergebnis\PHPStan\Rules\Functions\NoParameterPassedByReferenceRule`](https://github.com/ergebnis/phpstan-rules#functionsnoparameterpassedbyreferencerule)
|
||||
- [`Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullableTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#functionsnoparameterwithnullabletypedeclarationrule)
|
||||
|
|
@ -348,6 +349,21 @@ parameters:
|
|||
enabled: false
|
||||
```
|
||||
|
||||
#### `Files\NoPhpstanIgnoreRule`
|
||||
|
||||
This rule reports an error when a `@phpstan-ignore`, `@phpstan-ignore-line`, or `@phpstan-ignore-next-line` tag is used to [ignore errors reported by `phpstan/phpstan`](https://phpstan.org/user-guide/ignoring-errors#ignoring-in-code-using-phpdocs).
|
||||
|
||||
##### Disabling the rule
|
||||
|
||||
You can set the `enabled` parameter to `false` to disable this rule.
|
||||
|
||||
```neon
|
||||
parameters:
|
||||
ergebnis:
|
||||
noPhpstanIgnore:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
### Functions
|
||||
|
||||
#### `Functions\NoNullableReturnTypeDeclarationRule`
|
||||
|
|
|
|||
|
|
@ -23,20 +23,20 @@
|
|||
"require": {
|
||||
"php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
|
||||
"ext-mbstring": "*",
|
||||
"phpstan/phpstan": "^2.1.8"
|
||||
"phpstan/phpstan": "^2.1.35"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "^4.0.0 || ^5.0.0",
|
||||
"doctrine/orm": "^2.20.0 || ^3.3.0",
|
||||
"ergebnis/composer-normalize": "^2.47.0",
|
||||
"ergebnis/license": "^2.6.0",
|
||||
"ergebnis/php-cs-fixer-config": "^6.54.0",
|
||||
"ergebnis/composer-normalize": "^2.49.0",
|
||||
"ergebnis/license": "^2.7.0",
|
||||
"ergebnis/php-cs-fixer-config": "^6.59.0",
|
||||
"ergebnis/phpunit-slow-test-detector": "^2.20.0",
|
||||
"fakerphp/faker": "^1.24.1",
|
||||
"phpstan/extension-installer": "^1.4.3",
|
||||
"phpstan/phpstan-deprecation-rules": "^2.0.3",
|
||||
"phpstan/phpstan-phpunit": "^2.0.7",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.6",
|
||||
"phpstan/phpstan-phpunit": "^2.0.12",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.8",
|
||||
"phpunit/phpunit": "^9.6.21",
|
||||
"psr/container": "^2.0.2",
|
||||
"symfony/finder": "^5.4.45",
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ conditionalTags:
|
|||
phpstan.rules.rule: %ergebnis.noIsset.enabled%
|
||||
Ergebnis\PHPStan\Rules\Files\DeclareStrictTypesRule:
|
||||
phpstan.rules.rule: %ergebnis.declareStrictTypes.enabled%
|
||||
Ergebnis\PHPStan\Rules\Files\NoPhpstanIgnoreRule:
|
||||
phpstan.rules.rule: %ergebnis.noPhpstanIgnore.enabled%
|
||||
Ergebnis\PHPStan\Rules\Functions\NoNullableReturnTypeDeclarationRule:
|
||||
phpstan.rules.rule: %ergebnis.noNullableReturnTypeDeclaration.enabled%
|
||||
Ergebnis\PHPStan\Rules\Functions\NoParameterPassedByReferenceRule:
|
||||
|
|
@ -102,6 +104,8 @@ parameters:
|
|||
enabled: %ergebnis.allRules%
|
||||
noParameterWithNullDefaultValue:
|
||||
enabled: %ergebnis.allRules%
|
||||
noPhpstanIgnore:
|
||||
enabled: %ergebnis.allRules%
|
||||
noReturnByReference:
|
||||
enabled: %ergebnis.allRules%
|
||||
noSwitch:
|
||||
|
|
@ -176,6 +180,9 @@ parametersSchema:
|
|||
noParameterWithNullDefaultValue: structure([
|
||||
enabled: bool(),
|
||||
])
|
||||
noPhpstanIgnore: structure([
|
||||
enabled: bool(),
|
||||
])
|
||||
noReturnByReference: structure([
|
||||
enabled: bool(),
|
||||
])
|
||||
|
|
@ -238,6 +245,9 @@ services:
|
|||
-
|
||||
class: Ergebnis\PHPStan\Rules\Files\DeclareStrictTypesRule
|
||||
|
||||
-
|
||||
class: Ergebnis\PHPStan\Rules\Files\NoPhpstanIgnoreRule
|
||||
|
||||
-
|
||||
class: Ergebnis\PHPStan\Rules\Functions\NoNullableReturnTypeDeclarationRule
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
@ -110,6 +110,11 @@ final class ErrorIdentifier
|
|||
return new self('noNullableReturnTypeDeclaration');
|
||||
}
|
||||
|
||||
public static function noPhpstanIgnore(): self
|
||||
{
|
||||
return new self('noPhpstanIgnore');
|
||||
}
|
||||
|
||||
public static function noReturnByReference(): self
|
||||
{
|
||||
return new self('noReturnByReference');
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
70
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Files/NoPhpstanIgnoreRule.php
vendored
Normal file
70
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Files/NoPhpstanIgnoreRule.php
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Files;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeFinder;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Node\FileNode;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<FileNode>
|
||||
*/
|
||||
final class NoPhpstanIgnoreRule implements Rules\Rule
|
||||
{
|
||||
public function getNodeType(): string
|
||||
{
|
||||
return FileNode::class;
|
||||
}
|
||||
|
||||
public function processNode(
|
||||
Node $node,
|
||||
Analyser\Scope $scope
|
||||
): array {
|
||||
$nodeFinder = new NodeFinder();
|
||||
|
||||
$foundNodes = $nodeFinder->find($node->getNodes(), static function (Node $node): bool {
|
||||
return true;
|
||||
});
|
||||
|
||||
$errors = [];
|
||||
|
||||
foreach ($foundNodes as $foundNode) {
|
||||
foreach ($foundNode->getComments() as $comment) {
|
||||
foreach (\explode("\n", $comment->getText()) as $index => $line) {
|
||||
if (0 === \preg_match('/@phpstan-ignore(-line|-next-line)?(?=\s|$|,)/', $line, $matches)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tag = $matches[0];
|
||||
|
||||
$message = \sprintf(
|
||||
'Errors reported by phpstan/phpstan should not be ignored via "%s", fix the error or use the baseline instead.',
|
||||
$tag,
|
||||
);
|
||||
|
||||
$errors[] = Rules\RuleErrorBuilder::message($message)
|
||||
->identifier(ErrorIdentifier::noPhpstanIgnore()->toString())
|
||||
->line($comment->getStartLine() + $index)
|
||||
->nonIgnorable()
|
||||
->build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2025 Andreas Möller
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PHPSTORM_META;
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@
|
|||
"require-dev": {
|
||||
"nette/tester": "^2.5",
|
||||
"tracy/tracy": "^2.9",
|
||||
"phpstan/phpstan-nette": "^2.0@stable",
|
||||
"phpstan/phpstan": "^2.1@stable",
|
||||
"phpstan/extension-installer": "^1.4@stable",
|
||||
"nette/phpstan-rules": "^1.0",
|
||||
"jetbrains/phpstorm-attributes": "^1.2"
|
||||
},
|
||||
"conflict": {
|
||||
|
|
@ -50,5 +52,10 @@
|
|||
"branch-alias": {
|
||||
"dev-master": "4.1-dev"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"phpstan/extension-installer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Represents object convertible to HTML string.
|
||||
*/
|
||||
interface HtmlStringable
|
||||
{
|
||||
/**
|
||||
* Returns string in HTML format
|
||||
* Returns string in HTML format.
|
||||
*/
|
||||
function __toString(): string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,29 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Iterators;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Smarter caching iterator.
|
||||
* Enhanced caching iterator with first/last/counter tracking.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @extends \CachingIterator<TKey, TValue, \Iterator<TKey, TValue>>
|
||||
* @property-read bool $first
|
||||
* @property-read bool $last
|
||||
* @property-read bool $empty
|
||||
* @property-read bool $odd
|
||||
* @property-read bool $even
|
||||
* @property-read int $counter
|
||||
* @property-read mixed $nextKey
|
||||
* @property-read mixed $nextValue
|
||||
* @property-read TKey $nextKey
|
||||
* @property-read TValue $nextValue
|
||||
*/
|
||||
class CachingIterator extends \CachingIterator implements \Countable
|
||||
{
|
||||
|
|
@ -31,6 +32,7 @@ class CachingIterator extends \CachingIterator implements \Countable
|
|||
private int $counter = 0;
|
||||
|
||||
|
||||
/** @param iterable<TKey, TValue>|\stdClass $iterable */
|
||||
public function __construct(iterable|\stdClass $iterable)
|
||||
{
|
||||
$iterable = $iterable instanceof \stdClass
|
||||
|
|
@ -58,45 +60,30 @@ class CachingIterator extends \CachingIterator implements \Countable
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the iterator empty?
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return $this->counter === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the counter odd?
|
||||
*/
|
||||
public function isOdd(): bool
|
||||
{
|
||||
return $this->counter % 2 === 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the counter even?
|
||||
*/
|
||||
public function isEven(): bool
|
||||
{
|
||||
return $this->counter % 2 === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the counter.
|
||||
*/
|
||||
public function getCounter(): int
|
||||
{
|
||||
return $this->counter;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the count of elements.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
$inner = $this->getInnerIterator();
|
||||
|
|
@ -131,18 +118,14 @@ class CachingIterator extends \CachingIterator implements \Countable
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the next key.
|
||||
*/
|
||||
/** @return TKey */
|
||||
public function getNextKey(): mixed
|
||||
{
|
||||
return $this->getInnerIterator()->key();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the next element.
|
||||
*/
|
||||
/** @return TValue */
|
||||
public function getNextValue(): mixed
|
||||
{
|
||||
return $this->getInnerIterator()->current();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Iterators;
|
||||
|
||||
|
||||
|
|
@ -15,14 +13,13 @@ namespace Nette\Iterators;
|
|||
*/
|
||||
class Mapper extends \IteratorIterator
|
||||
{
|
||||
/** @var callable */
|
||||
private $callback;
|
||||
private \Closure $callback;
|
||||
|
||||
|
||||
public function __construct(\Traversable $iterator, callable $callback)
|
||||
{
|
||||
parent::__construct($iterator);
|
||||
$this->callback = $callback;
|
||||
$this->callback = $callback(...);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette;
|
||||
|
||||
use Nette\Utils\ObjectHelpers;
|
||||
|
|
@ -22,6 +20,7 @@ use Nette\Utils\ObjectHelpers;
|
|||
trait SmartObject
|
||||
{
|
||||
/**
|
||||
* @param mixed[] $args
|
||||
* @return mixed
|
||||
* @throws MemberAccessException
|
||||
*/
|
||||
|
|
@ -47,6 +46,8 @@ trait SmartObject
|
|||
|
||||
|
||||
/**
|
||||
* @param mixed[] $args
|
||||
* @return never
|
||||
* @throws MemberAccessException
|
||||
*/
|
||||
public static function __callStatic(string $name, array $args)
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Static class.
|
||||
* Prevents instantiation.
|
||||
*/
|
||||
trait StaticClass
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Localization;
|
||||
|
||||
|
||||
/**
|
||||
* Translator adapter.
|
||||
* Translation provider.
|
||||
*/
|
||||
interface Translator
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -14,7 +12,7 @@ use function count, is_array, is_scalar, sprintf;
|
|||
|
||||
|
||||
/**
|
||||
* Provides objects to work as array.
|
||||
* Array-like object with property access.
|
||||
* @template T
|
||||
* @implements \IteratorAggregate<array-key, T>
|
||||
* @implements \ArrayAccess<array-key, T>
|
||||
|
|
@ -39,7 +37,6 @@ class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \Iterator
|
|||
|
||||
|
||||
/**
|
||||
* Returns an iterator over all items.
|
||||
* @return \Iterator<array-key, T>
|
||||
*/
|
||||
public function &getIterator(): \Iterator
|
||||
|
|
@ -50,9 +47,6 @@ class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \Iterator
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns items count.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count((array) $this);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -14,13 +12,14 @@ use function array_slice, array_splice, count, is_int;
|
|||
|
||||
|
||||
/**
|
||||
* Provides the base class for a generic list (items can be accessed by index).
|
||||
* Generic list with integer indices.
|
||||
* @template T
|
||||
* @implements \IteratorAggregate<int, T>
|
||||
* @implements \ArrayAccess<int, T>
|
||||
*/
|
||||
class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
|
||||
{
|
||||
/** @var list<T> */
|
||||
private array $list = [];
|
||||
|
||||
|
||||
|
|
@ -41,7 +40,6 @@ class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
|
|||
|
||||
|
||||
/**
|
||||
* Returns an iterator over all items.
|
||||
* @return \Iterator<int, T>
|
||||
*/
|
||||
public function &getIterator(): \Iterator
|
||||
|
|
@ -52,9 +50,6 @@ class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns items count.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->list);
|
||||
|
|
@ -63,7 +58,7 @@ class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
|
|||
|
||||
/**
|
||||
* Replaces or appends an item.
|
||||
* @param int|null $index
|
||||
* @param ?int $index
|
||||
* @param T $value
|
||||
* @throws Nette\OutOfRangeException
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use JetBrains\PhpStorm\Language;
|
||||
|
|
@ -16,7 +14,7 @@ use const PREG_GREP_INVERT, PREG_SPLIT_DELIM_CAPTURE, PREG_SPLIT_NO_EMPTY;
|
|||
|
||||
|
||||
/**
|
||||
* Array tools library.
|
||||
* Array manipulation utilities.
|
||||
*/
|
||||
class Arrays
|
||||
{
|
||||
|
|
@ -72,14 +70,14 @@ class Arrays
|
|||
|
||||
|
||||
/**
|
||||
* Recursively merges two fields. It is useful, for example, for merging tree structures. It behaves as
|
||||
* the + operator for array, ie. it adds a key/value pair from the second array to the first one and retains
|
||||
* the value from the first array in the case of a key collision.
|
||||
* Recursively merges two arrays. Useful for merging tree structures. Behaves like the + operator:
|
||||
* key/value pairs from the second array are added to the first, with the first array's values taking
|
||||
* precedence on key collisions. Nested arrays are merged recursively instead of replaced.
|
||||
* @template T1
|
||||
* @template T2
|
||||
* @param array<T1> $array1
|
||||
* @param array<T2> $array2
|
||||
* @return array<T1|T2>
|
||||
* @return array<T1|T2|array<mixed>>
|
||||
*/
|
||||
public static function mergeTree(array $array1, array $array2): array
|
||||
{
|
||||
|
|
@ -96,6 +94,7 @@ class Arrays
|
|||
|
||||
/**
|
||||
* Returns zero-indexed position of given array key. Returns null if key is not found.
|
||||
* @param array<mixed> $array
|
||||
*/
|
||||
public static function getKeyOffset(array $array, string|int $key): ?int
|
||||
{
|
||||
|
|
@ -104,9 +103,10 @@ class Arrays
|
|||
|
||||
|
||||
/**
|
||||
* @param array<mixed> $array
|
||||
* @deprecated use getKeyOffset()
|
||||
*/
|
||||
public static function searchKey(array $array, $key): ?int
|
||||
public static function searchKey(array $array, string|int $key): ?int
|
||||
{
|
||||
return self::getKeyOffset($array, $key);
|
||||
}
|
||||
|
|
@ -114,10 +114,11 @@ class Arrays
|
|||
|
||||
/**
|
||||
* Tests an array for the presence of value.
|
||||
* @param array<mixed> $array
|
||||
*/
|
||||
public static function contains(array $array, mixed $value): bool
|
||||
{
|
||||
return in_array($value, $array, true);
|
||||
return in_array($value, $array, strict: true);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -125,9 +126,11 @@ class Arrays
|
|||
* Returns the first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null.
|
||||
* @template K of int|string
|
||||
* @template V
|
||||
* @template E
|
||||
* @param array<K, V> $array
|
||||
* @param ?callable(V, K, array<K, V>): bool $predicate
|
||||
* @return ?V
|
||||
* @param ?callable(): E $else
|
||||
* @return ($else is null ? ?V : V|E)
|
||||
*/
|
||||
public static function first(array $array, ?callable $predicate = null, ?callable $else = null): mixed
|
||||
{
|
||||
|
|
@ -142,9 +145,11 @@ class Arrays
|
|||
* Returns the last item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null.
|
||||
* @template K of int|string
|
||||
* @template V
|
||||
* @template E
|
||||
* @param array<K, V> $array
|
||||
* @param ?callable(V, K, array<K, V>): bool $predicate
|
||||
* @return ?V
|
||||
* @param ?callable(): E $else
|
||||
* @return ($else is null ? ?V : V|E)
|
||||
*/
|
||||
public static function last(array $array, ?callable $predicate = null, ?callable $else = null): mixed
|
||||
{
|
||||
|
|
@ -194,8 +199,10 @@ class Arrays
|
|||
|
||||
|
||||
/**
|
||||
* Inserts the contents of the $inserted array into the $array immediately after the $key.
|
||||
* Inserts the contents of the $inserted array into the $array immediately before the $key.
|
||||
* If $key is null (or does not exist), it is inserted at the beginning.
|
||||
* @param array<mixed> $array
|
||||
* @param array<mixed> $inserted
|
||||
*/
|
||||
public static function insertBefore(array &$array, string|int|null $key, array $inserted): void
|
||||
{
|
||||
|
|
@ -207,8 +214,10 @@ class Arrays
|
|||
|
||||
|
||||
/**
|
||||
* Inserts the contents of the $inserted array into the $array before the $key.
|
||||
* 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 end.
|
||||
* @param array<mixed> $array
|
||||
* @param array<mixed> $inserted
|
||||
*/
|
||||
public static function insertAfter(array &$array, string|int|null $key, array $inserted): void
|
||||
{
|
||||
|
|
@ -224,6 +233,7 @@ class Arrays
|
|||
|
||||
/**
|
||||
* Renames key in array.
|
||||
* @param array<mixed> $array
|
||||
*/
|
||||
public static function renameKey(array &$array, string|int $oldKey, string|int $newKey): bool
|
||||
{
|
||||
|
|
@ -260,6 +270,8 @@ class Arrays
|
|||
|
||||
/**
|
||||
* Transforms multidimensional array to flat array.
|
||||
* @param array<mixed> $array
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public static function flatten(array $array, bool $preserveKeys = false): array
|
||||
{
|
||||
|
|
@ -283,17 +295,19 @@ class Arrays
|
|||
|
||||
|
||||
/**
|
||||
* Reformats table to associative tree. Path looks like 'field|field[]field->field=field'.
|
||||
* @param string|string[] $path
|
||||
* Transforms a flat array of rows into an associative tree using a path expression like 'field|field[]field->field=field'.
|
||||
* @param array<mixed> $array
|
||||
* @param string|list<string> $path
|
||||
* @return array<mixed>|\stdClass
|
||||
*/
|
||||
public static function associate(array $array, $path): array|\stdClass
|
||||
public static function associate(array $array, string|array $path): array|\stdClass
|
||||
{
|
||||
$parts = is_array($path)
|
||||
? $path
|
||||
: preg_split('#(\[\]|->|=|\|)#', $path, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
if (!$parts || $parts === ['->'] || $parts[0] === '=' || $parts[0] === '|') {
|
||||
throw new Nette\InvalidArgumentException("Invalid path '$path'.");
|
||||
throw new Nette\InvalidArgumentException("Invalid path '" . (is_array($path) ? implode('', $path) : $path) . "'.");
|
||||
}
|
||||
|
||||
$res = $parts[0] === '->' ? new \stdClass : [];
|
||||
|
|
@ -312,6 +326,8 @@ class Arrays
|
|||
$x = $row[$parts[$i]];
|
||||
$row = null;
|
||||
}
|
||||
break; // '=' is always the final operation
|
||||
|
||||
} elseif ($part === '->') {
|
||||
if (isset($parts[++$i])) {
|
||||
if ($x === null) {
|
||||
|
|
@ -337,7 +353,9 @@ class Arrays
|
|||
|
||||
|
||||
/**
|
||||
* Normalizes array to associative array. Replace numeric keys with their values, the new value will be $filling.
|
||||
* Converts array to associative: items with numeric keys are converted to keys, with $filling as their value.
|
||||
* @param array<mixed> $array
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function normalize(array $array, mixed $filling = null): array
|
||||
{
|
||||
|
|
@ -480,9 +498,10 @@ class Arrays
|
|||
|
||||
/**
|
||||
* Invokes all callbacks and returns array of results.
|
||||
* @param callable[] $callbacks
|
||||
* @param iterable<callable> $callbacks
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public static function invoke(iterable $callbacks, ...$args): array
|
||||
public static function invoke(iterable $callbacks, mixed ...$args): array
|
||||
{
|
||||
$res = [];
|
||||
foreach ($callbacks as $k => $cb) {
|
||||
|
|
@ -495,9 +514,10 @@ class Arrays
|
|||
|
||||
/**
|
||||
* Invokes method on every object in an array and returns array of results.
|
||||
* @param object[] $objects
|
||||
* @param iterable<object> $objects
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public static function invokeMethod(iterable $objects, string $method, ...$args): array
|
||||
public static function invokeMethod(iterable $objects, string $method, mixed ...$args): array
|
||||
{
|
||||
$res = [];
|
||||
foreach ($objects as $k => $obj) {
|
||||
|
|
@ -511,6 +531,7 @@ class Arrays
|
|||
/**
|
||||
* Copies the elements of the $array array to the $object object and then returns it.
|
||||
* @template T of object
|
||||
* @param iterable<mixed> $array
|
||||
* @param T $object
|
||||
* @return T
|
||||
*/
|
||||
|
|
@ -534,8 +555,7 @@ class Arrays
|
|||
|
||||
|
||||
/**
|
||||
* Returns copy of the $array where every item is converted to string
|
||||
* and prefixed by $prefix and suffixed by $suffix.
|
||||
* Returns a copy of $array where every item is cast to string and wrapped with $prefix and $suffix.
|
||||
* @param string[] $array
|
||||
* @return string[]
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -22,21 +20,24 @@ final class Callback
|
|||
|
||||
/**
|
||||
* Invokes internal PHP function with own error handler.
|
||||
* @param callable-string $function
|
||||
* @param list<mixed> $args
|
||||
* @param callable(string, int): (bool|void|null) $onError
|
||||
*/
|
||||
public static function invokeSafe(string $function, array $args, callable $onError): mixed
|
||||
{
|
||||
$prev = set_error_handler(function ($severity, $message, $file) use ($onError, &$prev, $function): ?bool {
|
||||
$prev = set_error_handler(function (int $severity, string $message, string $file, int $line) use ($onError, &$prev, $function): bool {
|
||||
if ($file === __FILE__) {
|
||||
$msg = ini_get('html_errors')
|
||||
? Html::htmlToText($message)
|
||||
: $message;
|
||||
$msg = preg_replace("#^$function\\(.*?\\): #", '', $msg);
|
||||
$msg = (string) preg_replace("#^$function\\(.*?\\): #", '', $msg);
|
||||
if ($onError($msg, $severity) !== false) {
|
||||
return null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return $prev ? $prev(...func_get_args()) : false;
|
||||
return $prev ? $prev(...func_get_args()) !== false : false;
|
||||
});
|
||||
|
||||
try {
|
||||
|
|
@ -48,12 +49,12 @@ 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.
|
||||
* Checks that $callable is a valid PHP callback and returns it. With $syntax set to true, only verifies
|
||||
* the structural validity without checking whether the class or method actually exists.
|
||||
* @return callable
|
||||
* @throws Nette\InvalidArgumentException
|
||||
*/
|
||||
public static function check(mixed $callable, bool $syntax = false)
|
||||
public static function check(mixed $callable, bool $syntax = false): mixed
|
||||
{
|
||||
if (!is_callable($callable, $syntax)) {
|
||||
throw new Nette\InvalidArgumentException(
|
||||
|
|
@ -87,7 +88,7 @@ final class Callback
|
|||
* @param callable $callable type check is escalated to ReflectionException
|
||||
* @throws \ReflectionException if callback is not valid
|
||||
*/
|
||||
public static function toReflection($callable): \ReflectionMethod|\ReflectionFunction
|
||||
public static function toReflection(mixed $callable): \ReflectionMethod|\ReflectionFunction
|
||||
{
|
||||
if ($callable instanceof \Closure) {
|
||||
$callable = self::unwrap($callable);
|
||||
|
|
@ -100,6 +101,7 @@ final class Callback
|
|||
} elseif (is_object($callable) && !$callable instanceof \Closure) {
|
||||
return new ReflectionMethod($callable, '__invoke');
|
||||
} else {
|
||||
assert($callable instanceof \Closure || is_string($callable));
|
||||
return new \ReflectionFunction($callable);
|
||||
}
|
||||
}
|
||||
|
|
@ -116,8 +118,9 @@ final class Callback
|
|||
|
||||
/**
|
||||
* Unwraps closure created by Closure::fromCallable().
|
||||
* @return callable|array{object|class-string, string}|string
|
||||
*/
|
||||
public static function unwrap(\Closure $closure): callable|array
|
||||
public static function unwrap(\Closure $closure): callable|array|string
|
||||
{
|
||||
$r = new \ReflectionFunction($closure);
|
||||
$class = $r->getClosureScopeClass()?->name;
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use function array_merge, checkdate, implode, is_numeric, is_string, preg_replace_callback, sprintf, time, trim;
|
||||
|
||||
|
||||
/**
|
||||
* DateTime.
|
||||
* Extends PHP's DateTime with strict validation and additional factory methods.
|
||||
*/
|
||||
class DateTime extends \DateTime implements \JsonSerializable
|
||||
{
|
||||
|
|
@ -142,7 +140,7 @@ class DateTime extends \DateTime implements \JsonSerializable
|
|||
}
|
||||
|
||||
|
||||
private function apply(string $datetime, $timezone = null, bool $ctr = false): void
|
||||
private function apply(string $datetime, ?\DateTimeZone $timezone = null, bool $ctr = false): void
|
||||
{
|
||||
$relPart = '';
|
||||
$absPart = preg_replace_callback(
|
||||
|
|
@ -189,7 +187,7 @@ class DateTime extends \DateTime implements \JsonSerializable
|
|||
|
||||
|
||||
/**
|
||||
* You'd better use: (clone $dt)->modify(...)
|
||||
* Returns a modified copy of the object. Use (clone $dt)->modify(...) for better type safety.
|
||||
*/
|
||||
public function modifyClone(string $modify = ''): static
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -19,14 +17,12 @@ use const DIRECTORY_SEPARATOR;
|
|||
*/
|
||||
final class FileInfo extends \SplFileInfo
|
||||
{
|
||||
private readonly string $relativePath;
|
||||
|
||||
|
||||
public function __construct(string $file, string $relativePath = '')
|
||||
{
|
||||
public function __construct(
|
||||
string $file,
|
||||
private readonly string $relativePath = '',
|
||||
) {
|
||||
parent::__construct($file);
|
||||
$this->setInfoClass(static::class);
|
||||
$this->relativePath = $relativePath;
|
||||
$this->setInfoClass(self::class);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -52,14 +50,15 @@ final class FileSystem
|
|||
} elseif (is_dir($origin)) {
|
||||
static::createDir($target);
|
||||
foreach (new \FilesystemIterator($target) as $item) {
|
||||
\assert($item instanceof \SplFileInfo);
|
||||
static::delete($item->getPathname());
|
||||
}
|
||||
|
||||
foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($origin, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item) {
|
||||
if ($item->isDir()) {
|
||||
static::createDir($target . '/' . $iterator->getSubPathName());
|
||||
static::createDir($target . '/' . $iterator->getSubPathname());
|
||||
} else {
|
||||
static::copy($item->getPathname(), $target . '/' . $iterator->getSubPathName());
|
||||
static::copy($item->getPathname(), $target . '/' . $iterator->getSubPathname());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -112,6 +111,7 @@ final class FileSystem
|
|||
}
|
||||
} elseif (is_dir($path)) {
|
||||
foreach (new \FilesystemIterator($path) as $item) {
|
||||
\assert($item instanceof \SplFileInfo);
|
||||
static::delete($item->getPathname());
|
||||
}
|
||||
|
||||
|
|
@ -208,7 +208,7 @@ final class FileSystem
|
|||
|
||||
|
||||
/**
|
||||
* Writes the string to a file.
|
||||
* Writes the string to a file. Creates the parent directory if it does not exist. Pass null as $mode to skip chmod.
|
||||
* @throws Nette\IOException on error occurred
|
||||
*/
|
||||
public static function write(string $file, string $content, ?int $mode = 0o666): void
|
||||
|
|
@ -251,6 +251,7 @@ final class FileSystem
|
|||
}
|
||||
} elseif (is_dir($path)) {
|
||||
foreach (new \FilesystemIterator($path) as $item) {
|
||||
\assert($item instanceof \SplFileInfo);
|
||||
static::makeWritable($item->getPathname(), $dirMode, $fileMode);
|
||||
}
|
||||
|
||||
|
|
@ -277,6 +278,19 @@ final class FileSystem
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether the string is a valid cross-platform filename without any path information.
|
||||
*/
|
||||
public static function isValidFilename(string $name): bool
|
||||
{
|
||||
[$stem] = explode('.', $name, 2);
|
||||
return $name !== '' && $name !== '.' && $name !== '..'
|
||||
&& !preg_match('#[\x00-\x1F<>:"|?*\\\/]#', $name) // control and reserved characters
|
||||
&& !str_ends_with($name, '.') && !str_ends_with($name, ' ') // trailing dots/spaces
|
||||
&& !preg_match('#^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$#i', $stem); // Windows reserved device names
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Normalizes `..` and `.` and directory separators in path.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -15,7 +13,7 @@ use const GLOB_NOESCAPE, GLOB_NOSORT, GLOB_ONLYDIR;
|
|||
|
||||
|
||||
/**
|
||||
* Finder allows searching through directory trees using iterator.
|
||||
* Searches for files and directories in directory trees.
|
||||
*
|
||||
* Finder::findFiles('*.php')
|
||||
* ->size('> 10kB')
|
||||
|
|
@ -32,24 +30,25 @@ class Finder implements \IteratorAggregate
|
|||
/** @var string[] */
|
||||
private array $in = [];
|
||||
|
||||
/** @var \Closure[] */
|
||||
/** @var array<\Closure(FileInfo): bool> */
|
||||
private array $filters = [];
|
||||
|
||||
/** @var \Closure[] */
|
||||
/** @var array<\Closure(FileInfo): bool> */
|
||||
private array $descentFilters = [];
|
||||
|
||||
/** @var array<string|self> */
|
||||
private array $appends = [];
|
||||
private bool $childFirst = false;
|
||||
|
||||
/** @var ?callable */
|
||||
private $sort;
|
||||
/** @var ?(\Closure(FileInfo, FileInfo): int) */
|
||||
private ?\Closure $sort = null;
|
||||
private int $maxDepth = -1;
|
||||
private bool $ignoreUnreadableDirs = true;
|
||||
|
||||
|
||||
/**
|
||||
* Begins search for files and directories matching mask.
|
||||
* @param string|list<string> $masks
|
||||
*/
|
||||
public static function find(string|array $masks = ['*']): static
|
||||
{
|
||||
|
|
@ -60,6 +59,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
/**
|
||||
* Begins search for files matching mask.
|
||||
* @param string|list<string> $masks
|
||||
*/
|
||||
public static function findFiles(string|array $masks = ['*']): static
|
||||
{
|
||||
|
|
@ -70,6 +70,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
/**
|
||||
* Begins search for directories matching mask.
|
||||
* @param string|list<string> $masks
|
||||
*/
|
||||
public static function findDirectories(string|array $masks = ['*']): static
|
||||
{
|
||||
|
|
@ -80,6 +81,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
/**
|
||||
* Finds files matching the specified masks.
|
||||
* @param string|list<string> $masks
|
||||
*/
|
||||
public function files(string|array $masks = ['*']): static
|
||||
{
|
||||
|
|
@ -89,6 +91,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
/**
|
||||
* Finds directories matching the specified masks.
|
||||
* @param string|list<string> $masks
|
||||
*/
|
||||
public function directories(string|array $masks = ['*']): static
|
||||
{
|
||||
|
|
@ -96,6 +99,7 @@ class Finder implements \IteratorAggregate
|
|||
}
|
||||
|
||||
|
||||
/** @param list<string> $masks */
|
||||
private function addMask(array $masks, string $mode): static
|
||||
{
|
||||
foreach ($masks as $mask) {
|
||||
|
|
@ -117,6 +121,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
/**
|
||||
* Searches in the given directories. Wildcards are allowed.
|
||||
* @param string|list<string> $paths
|
||||
*/
|
||||
public function in(string|array $paths): static
|
||||
{
|
||||
|
|
@ -128,6 +133,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
/**
|
||||
* Searches recursively from the given directories. Wildcards are allowed.
|
||||
* @param string|list<string> $paths
|
||||
*/
|
||||
public function from(string|array $paths): static
|
||||
{
|
||||
|
|
@ -137,6 +143,7 @@ class Finder implements \IteratorAggregate
|
|||
}
|
||||
|
||||
|
||||
/** @param list<string> $paths */
|
||||
private function addLocation(array $paths, string $ext): void
|
||||
{
|
||||
foreach ($paths as $path) {
|
||||
|
|
@ -170,12 +177,12 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
|
||||
/**
|
||||
* Set a compare function for sorting directory entries. The function will be called to sort entries from the same directory.
|
||||
* Sets a comparison function for sorting entries within each directory.
|
||||
* @param callable(FileInfo, FileInfo): int $callback
|
||||
*/
|
||||
public function sortBy(callable $callback): static
|
||||
{
|
||||
$this->sort = $callback;
|
||||
$this->sort = $callback(...);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -191,7 +198,8 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
|
||||
/**
|
||||
* Adds the specified paths or appends a new finder that returns.
|
||||
* Appends the specified file paths to results. Passing null creates and returns a new sub-finder whose results are appended.
|
||||
* @param string|list<string>|null $paths
|
||||
*/
|
||||
public function append(string|array|null $paths = null): static
|
||||
{
|
||||
|
|
@ -209,6 +217,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
/**
|
||||
* Skips entries that matches the given masks relative to the ones defined with the in() or from() methods.
|
||||
* @param string|list<string> $masks
|
||||
*/
|
||||
public function exclude(string|array $masks): static
|
||||
{
|
||||
|
|
@ -239,7 +248,7 @@ class Finder implements \IteratorAggregate
|
|||
*/
|
||||
public function filter(callable $callback): static
|
||||
{
|
||||
$this->filters[] = \Closure::fromCallable($callback);
|
||||
$this->filters[] = $callback(...);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -250,7 +259,7 @@ class Finder implements \IteratorAggregate
|
|||
*/
|
||||
public function descentFilter(callable $callback): static
|
||||
{
|
||||
$this->descentFilters[] = \Closure::fromCallable($callback);
|
||||
$this->descentFilters[] = $callback(...);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -267,6 +276,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
/**
|
||||
* Restricts the search by size. $operator accepts "[operator] [size] [unit]" example: >=10kB
|
||||
* @param '>'|'>='|'<'|'<='|'='|'=='|'==='|'!='|'!=='|'<>' $operator or predicate string
|
||||
*/
|
||||
public function size(string $operator, ?int $size = null): static
|
||||
{
|
||||
|
|
@ -277,7 +287,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
[, $operator, $size, $unit] = $matches;
|
||||
$units = ['' => 1, 'k' => 1e3, 'm' => 1e6, 'g' => 1e9];
|
||||
$size *= $units[strtolower($unit)];
|
||||
$size = (float) $size * $units[strtolower($unit)];
|
||||
$operator = $operator ?: '=';
|
||||
}
|
||||
|
||||
|
|
@ -287,6 +297,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
/**
|
||||
* Restricts the search by modified time. $operator accepts "[operator] [date]" example: >1978-01-23
|
||||
* @param '>'|'>='|'<'|'<='|'='|'=='|'==='|'!='|'!=='|'<>' $operator or predicate string
|
||||
*/
|
||||
public function date(string $operator, string|int|\DateTimeInterface|null $date = null): static
|
||||
{
|
||||
|
|
@ -401,6 +412,7 @@ class Finder implements \IteratorAggregate
|
|||
}
|
||||
|
||||
|
||||
/** @param iterable<string> $pathNames */
|
||||
private function convertToFiles(iterable $pathNames, string $relativePath, bool $absolute): \Generator
|
||||
{
|
||||
foreach ($pathNames as $pathName) {
|
||||
|
|
@ -413,6 +425,10 @@ class Finder implements \IteratorAggregate
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param (\Closure(FileInfo): bool)[] $filters
|
||||
* @param array<int, bool> $cache
|
||||
*/
|
||||
private function proveFilters(array $filters, FileInfo $file, array &$cache): bool
|
||||
{
|
||||
foreach ($filters as $filter) {
|
||||
|
|
@ -468,6 +484,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
/**
|
||||
* Since glob() does not know ** wildcard, we divide the path into a part for glob and a part for manual traversal.
|
||||
* @return array{string, string, bool}
|
||||
*/
|
||||
private static function splitRecursivePart(string $path): array
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -14,6 +12,9 @@ use function array_unique, ini_get, levenshtein, max, min, ob_end_clean, ob_get_
|
|||
use const PHP_OS_FAMILY;
|
||||
|
||||
|
||||
/**
|
||||
* Miscellaneous utilities.
|
||||
*/
|
||||
class Helpers
|
||||
{
|
||||
public const IsWindows = PHP_OS_FAMILY === 'Windows';
|
||||
|
|
@ -21,6 +22,7 @@ class Helpers
|
|||
|
||||
/**
|
||||
* Executes a callback and returns the captured output as a string.
|
||||
* @param callable(): void $func
|
||||
*/
|
||||
public static function capture(callable $func): string
|
||||
{
|
||||
|
|
@ -37,7 +39,7 @@ class Helpers
|
|||
|
||||
/**
|
||||
* Returns the last occurred PHP error or an empty string if no error occurred. Unlike error_get_last(),
|
||||
* it is nit affected by the PHP directive html_errors and always returns text, not HTML.
|
||||
* it is not affected by the PHP directive html_errors and always returns text, not HTML.
|
||||
*/
|
||||
public static function getLastError(): string
|
||||
{
|
||||
|
|
@ -59,6 +61,7 @@ class Helpers
|
|||
|
||||
/**
|
||||
* Returns value clamped to the inclusive range of min and max.
|
||||
* @return ($value is float ? float : ($min is float ? float : ($max is float ? float : int)))
|
||||
*/
|
||||
public static function clamp(int|float $value, int|float $min, int|float $max): int|float
|
||||
{
|
||||
|
|
@ -71,7 +74,7 @@ class Helpers
|
|||
|
||||
|
||||
/**
|
||||
* Looks for a string from possibilities that is most similar to value, but not the same (for 8-bit encoding).
|
||||
* Finds the string from $possibilities most similar to $value using Levenshtein distance, or null if none is close enough.
|
||||
* @param string[] $possibilities
|
||||
*/
|
||||
public static function getSuggestion(array $possibilities, string $value): ?string
|
||||
|
|
@ -91,6 +94,7 @@ class Helpers
|
|||
|
||||
/**
|
||||
* Compares two values in the same way that PHP does. Recognizes operators: >, >=, <, <=, =, ==, ===, !=, !==, <>
|
||||
* @param '>'|'>='|'<'|'<='|'='|'=='|'==='|'!='|'!=='|'<>' $operator
|
||||
*/
|
||||
public static function compare(mixed $left, string $operator, mixed $right): bool
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette\HtmlStringable;
|
||||
|
|
@ -15,115 +13,115 @@ use const ENT_HTML5, ENT_NOQUOTES, ENT_QUOTES;
|
|||
|
||||
|
||||
/**
|
||||
* HTML helper.
|
||||
* Generates HTML elements with automatic attribute escaping.
|
||||
*
|
||||
* @property string|null $accept
|
||||
* @property string|null $accesskey
|
||||
* @property string|null $action
|
||||
* @property string|null $align
|
||||
* @property string|null $allow
|
||||
* @property string|null $alt
|
||||
* @property bool|null $async
|
||||
* @property string|null $autocapitalize
|
||||
* @property string|null $autocomplete
|
||||
* @property bool|null $autofocus
|
||||
* @property bool|null $autoplay
|
||||
* @property string|null $charset
|
||||
* @property bool|null $checked
|
||||
* @property string|null $cite
|
||||
* @property string|null $class
|
||||
* @property int|null $cols
|
||||
* @property int|null $colspan
|
||||
* @property string|null $content
|
||||
* @property bool|null $contenteditable
|
||||
* @property bool|null $controls
|
||||
* @property string|null $coords
|
||||
* @property string|null $crossorigin
|
||||
* @property string|null $data
|
||||
* @property string|null $datetime
|
||||
* @property string|null $decoding
|
||||
* @property bool|null $default
|
||||
* @property bool|null $defer
|
||||
* @property string|null $dir
|
||||
* @property string|null $dirname
|
||||
* @property bool|null $disabled
|
||||
* @property bool|null $download
|
||||
* @property string|null $draggable
|
||||
* @property string|null $dropzone
|
||||
* @property string|null $enctype
|
||||
* @property string|null $for
|
||||
* @property string|null $form
|
||||
* @property string|null $formaction
|
||||
* @property string|null $formenctype
|
||||
* @property string|null $formmethod
|
||||
* @property bool|null $formnovalidate
|
||||
* @property string|null $formtarget
|
||||
* @property string|null $headers
|
||||
* @property int|null $height
|
||||
* @property bool|null $hidden
|
||||
* @property float|null $high
|
||||
* @property string|null $href
|
||||
* @property string|null $hreflang
|
||||
* @property string|null $id
|
||||
* @property string|null $integrity
|
||||
* @property string|null $inputmode
|
||||
* @property bool|null $ismap
|
||||
* @property string|null $itemprop
|
||||
* @property string|null $kind
|
||||
* @property string|null $label
|
||||
* @property string|null $lang
|
||||
* @property string|null $list
|
||||
* @property bool|null $loop
|
||||
* @property float|null $low
|
||||
* @property float|null $max
|
||||
* @property int|null $maxlength
|
||||
* @property int|null $minlength
|
||||
* @property string|null $media
|
||||
* @property string|null $method
|
||||
* @property float|null $min
|
||||
* @property bool|null $multiple
|
||||
* @property bool|null $muted
|
||||
* @property string|null $name
|
||||
* @property bool|null $novalidate
|
||||
* @property bool|null $open
|
||||
* @property float|null $optimum
|
||||
* @property string|null $pattern
|
||||
* @property string|null $ping
|
||||
* @property string|null $placeholder
|
||||
* @property string|null $poster
|
||||
* @property string|null $preload
|
||||
* @property string|null $radiogroup
|
||||
* @property bool|null $readonly
|
||||
* @property string|null $rel
|
||||
* @property bool|null $required
|
||||
* @property bool|null $reversed
|
||||
* @property int|null $rows
|
||||
* @property int|null $rowspan
|
||||
* @property string|null $sandbox
|
||||
* @property string|null $scope
|
||||
* @property bool|null $selected
|
||||
* @property string|null $shape
|
||||
* @property int|null $size
|
||||
* @property string|null $sizes
|
||||
* @property string|null $slot
|
||||
* @property int|null $span
|
||||
* @property string|null $spellcheck
|
||||
* @property string|null $src
|
||||
* @property string|null $srcdoc
|
||||
* @property string|null $srclang
|
||||
* @property string|null $srcset
|
||||
* @property int|null $start
|
||||
* @property float|null $step
|
||||
* @property string|null $style
|
||||
* @property int|null $tabindex
|
||||
* @property string|null $target
|
||||
* @property string|null $title
|
||||
* @property string|null $translate
|
||||
* @property string|null $type
|
||||
* @property string|null $usemap
|
||||
* @property string|null $value
|
||||
* @property int|null $width
|
||||
* @property string|null $wrap
|
||||
* @property ?string $accept
|
||||
* @property ?string $accesskey
|
||||
* @property ?string $action
|
||||
* @property ?string $align
|
||||
* @property ?string $allow
|
||||
* @property ?string $alt
|
||||
* @property ?bool $async
|
||||
* @property ?string $autocapitalize
|
||||
* @property ?string $autocomplete
|
||||
* @property ?bool $autofocus
|
||||
* @property ?bool $autoplay
|
||||
* @property ?string $charset
|
||||
* @property ?bool $checked
|
||||
* @property ?string $cite
|
||||
* @property ?string $class
|
||||
* @property ?int $cols
|
||||
* @property ?int $colspan
|
||||
* @property ?string $content
|
||||
* @property ?bool $contenteditable
|
||||
* @property ?bool $controls
|
||||
* @property ?string $coords
|
||||
* @property ?string $crossorigin
|
||||
* @property ?string $data
|
||||
* @property ?string $datetime
|
||||
* @property ?string $decoding
|
||||
* @property ?bool $default
|
||||
* @property ?bool $defer
|
||||
* @property ?string $dir
|
||||
* @property ?string $dirname
|
||||
* @property ?bool $disabled
|
||||
* @property ?bool $download
|
||||
* @property ?string $draggable
|
||||
* @property ?string $dropzone
|
||||
* @property ?string $enctype
|
||||
* @property ?string $for
|
||||
* @property ?string $form
|
||||
* @property ?string $formaction
|
||||
* @property ?string $formenctype
|
||||
* @property ?string $formmethod
|
||||
* @property ?bool $formnovalidate
|
||||
* @property ?string $formtarget
|
||||
* @property ?string $headers
|
||||
* @property ?int $height
|
||||
* @property ?bool $hidden
|
||||
* @property ?float $high
|
||||
* @property ?string $href
|
||||
* @property ?string $hreflang
|
||||
* @property ?string $id
|
||||
* @property ?string $integrity
|
||||
* @property ?string $inputmode
|
||||
* @property ?bool $ismap
|
||||
* @property ?string $itemprop
|
||||
* @property ?string $kind
|
||||
* @property ?string $label
|
||||
* @property ?string $lang
|
||||
* @property ?string $list
|
||||
* @property ?bool $loop
|
||||
* @property ?float $low
|
||||
* @property ?float $max
|
||||
* @property ?int $maxlength
|
||||
* @property ?int $minlength
|
||||
* @property ?string $media
|
||||
* @property ?string $method
|
||||
* @property ?float $min
|
||||
* @property ?bool $multiple
|
||||
* @property ?bool $muted
|
||||
* @property ?string $name
|
||||
* @property ?bool $novalidate
|
||||
* @property ?bool $open
|
||||
* @property ?float $optimum
|
||||
* @property ?string $pattern
|
||||
* @property ?string $ping
|
||||
* @property ?string $placeholder
|
||||
* @property ?string $poster
|
||||
* @property ?string $preload
|
||||
* @property ?string $radiogroup
|
||||
* @property ?bool $readonly
|
||||
* @property ?string $rel
|
||||
* @property ?bool $required
|
||||
* @property ?bool $reversed
|
||||
* @property ?int $rows
|
||||
* @property ?int $rowspan
|
||||
* @property ?string $sandbox
|
||||
* @property ?string $scope
|
||||
* @property ?bool $selected
|
||||
* @property ?string $shape
|
||||
* @property ?int $size
|
||||
* @property ?string $sizes
|
||||
* @property ?string $slot
|
||||
* @property ?int $span
|
||||
* @property ?string $spellcheck
|
||||
* @property ?string $src
|
||||
* @property ?string $srcdoc
|
||||
* @property ?string $srclang
|
||||
* @property ?string $srcset
|
||||
* @property ?int $start
|
||||
* @property ?float $step
|
||||
* @property ?string $style
|
||||
* @property ?int $tabindex
|
||||
* @property ?string $target
|
||||
* @property ?string $title
|
||||
* @property ?string $translate
|
||||
* @property ?string $type
|
||||
* @property ?string $usemap
|
||||
* @property ?string $value
|
||||
* @property ?int $width
|
||||
* @property ?string $wrap
|
||||
*
|
||||
* @method self accept(?string $val)
|
||||
* @method self accesskey(?string $val, bool $state = null)
|
||||
|
|
@ -230,20 +228,23 @@ use const ENT_HTML5, ENT_NOQUOTES, ENT_QUOTES;
|
|||
* @method self value(?string $val)
|
||||
* @method self width(?int $val)
|
||||
* @method self wrap(?string $val)
|
||||
*
|
||||
* @implements \IteratorAggregate<int, self|string>
|
||||
* @implements \ArrayAccess<int, self|string>
|
||||
*/
|
||||
class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringable
|
||||
{
|
||||
/** @var array<string, mixed> element's attributes */
|
||||
public array $attrs = [];
|
||||
|
||||
/** void elements */
|
||||
/** @var array<string, int> void elements */
|
||||
public static array $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 */
|
||||
/** @var array<int, self|string> nodes */
|
||||
protected array $children = [];
|
||||
|
||||
/** element's name */
|
||||
|
|
@ -254,7 +255,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
/**
|
||||
* Constructs new HTML element.
|
||||
* @param array|string $attrs element's attributes or plain text content
|
||||
* @param array<string, mixed>|string|null $attrs element's attributes or plain text content
|
||||
*/
|
||||
public static function el(?string $name = null, array|string|null $attrs = null): static
|
||||
{
|
||||
|
|
@ -345,7 +346,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
|
||||
/**
|
||||
* Is element empty?
|
||||
* Checks whether the element is a void (self-closing) element.
|
||||
*/
|
||||
final public function isEmpty(): bool
|
||||
{
|
||||
|
|
@ -355,6 +356,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
/**
|
||||
* Sets multiple attributes.
|
||||
* @param array<string, mixed> $attrs
|
||||
*/
|
||||
public function addAttributes(array $attrs): static
|
||||
{
|
||||
|
|
@ -417,6 +419,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
/**
|
||||
* Unsets element's attributes.
|
||||
* @param list<string> $attributes
|
||||
*/
|
||||
public function removeAttributes(array $attributes): static
|
||||
{
|
||||
|
|
@ -429,7 +432,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
|
||||
/**
|
||||
* Overloaded setter for element's attribute.
|
||||
* Sets element's attribute via property assignment.
|
||||
*/
|
||||
final public function __set(string $name, mixed $value): void
|
||||
{
|
||||
|
|
@ -438,7 +441,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
|
||||
/**
|
||||
* Overloaded getter for element's attribute.
|
||||
* Returns element's attribute via property access.
|
||||
*/
|
||||
final public function &__get(string $name): mixed
|
||||
{
|
||||
|
|
@ -447,7 +450,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
|
||||
/**
|
||||
* Overloaded tester for element's attribute.
|
||||
* Checks if element's attribute is set.
|
||||
*/
|
||||
final public function __isset(string $name): bool
|
||||
{
|
||||
|
|
@ -456,7 +459,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
|
||||
/**
|
||||
* Overloaded unsetter for element's attribute.
|
||||
* Unsets element's attribute via property unset.
|
||||
*/
|
||||
final public function __unset(string $name): void
|
||||
{
|
||||
|
|
@ -465,7 +468,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
|
||||
/**
|
||||
* Overloaded setter for element's attribute.
|
||||
* Sets or returns element's attribute via method call.
|
||||
* @param mixed[] $args
|
||||
*/
|
||||
final public function __call(string $m, array $args): mixed
|
||||
{
|
||||
|
|
@ -496,6 +500,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
/**
|
||||
* Special setter for element's attribute.
|
||||
* @param array<string, mixed> $query
|
||||
*/
|
||||
final public function href(string $path, array $query = []): static
|
||||
{
|
||||
|
|
@ -594,6 +599,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
/**
|
||||
* Creates and adds a new Html child.
|
||||
* @param array<string, mixed>|string|null $attrs
|
||||
*/
|
||||
final public function create(string $name, array|string|null $attrs = null): static
|
||||
{
|
||||
|
|
@ -621,7 +627,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
/**
|
||||
* Inserts (replaces) child node (\ArrayAccess implementation).
|
||||
* @param int|null $index position or null for appending
|
||||
* @param ?int $index position or null for appending
|
||||
* @param Html|string $child Html node or raw HTML string
|
||||
*/
|
||||
final public function offsetSet($index, $child): void
|
||||
|
|
@ -634,7 +640,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
* Returns child node (\ArrayAccess implementation).
|
||||
* @param int $index
|
||||
*/
|
||||
final public function offsetGet($index): HtmlStringable|string
|
||||
final public function offsetGet($index): self|string
|
||||
{
|
||||
return $this->children[$index];
|
||||
}
|
||||
|
|
@ -682,7 +688,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
/**
|
||||
* Iterates over elements.
|
||||
* @return \ArrayIterator<int, HtmlStringable|string>
|
||||
* @return \ArrayIterator<int, self|string>
|
||||
*/
|
||||
final public function getIterator(): \ArrayIterator
|
||||
{
|
||||
|
|
@ -692,6 +698,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
/**
|
||||
* Returns all children.
|
||||
* @return array<int, self|string>
|
||||
*/
|
||||
final public function getChildren(): array
|
||||
{
|
||||
|
|
@ -700,7 +707,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
|
||||
/**
|
||||
* Renders element's start tag, content and end tag.
|
||||
* Renders element's start tag, content and end tag. Pass indent level to enable pretty-printing.
|
||||
*/
|
||||
final public function render(?int $indent = null): string
|
||||
{
|
||||
|
|
@ -764,10 +771,6 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
*/
|
||||
final public function attributes(): string
|
||||
{
|
||||
if (!is_array($this->attrs)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$s = '';
|
||||
$attrs = $this->attrs;
|
||||
foreach ($attrs as $key => $value) {
|
||||
|
|
@ -780,7 +783,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
continue;
|
||||
|
||||
} elseif (is_array($value)) {
|
||||
if (strncmp($key, 'data-', 5) === 0) {
|
||||
if (str_starts_with($key, 'data-')) {
|
||||
$value = Json::encode($value);
|
||||
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -24,7 +22,7 @@ use const IMG_BMP, IMG_FLIP_BOTH, IMG_FLIP_HORIZONTAL, IMG_FLIP_VERTICAL, IMG_GI
|
|||
* $image->send();
|
||||
* </code>
|
||||
*
|
||||
* @method Image affine(array $affine, ?array $clip = null)
|
||||
* @method Image affine(array<int, float|int> $affine, ?array{x: int, y: int, width: int, height: int} $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)
|
||||
|
|
@ -41,51 +39,51 @@ use const IMG_BMP, IMG_FLIP_BOTH, IMG_FLIP_HORIZONTAL, IMG_FLIP_VERTICAL, IMG_GI
|
|||
* @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 array{red: int, green: int, blue: int, alpha: int} colorsForIndex(int $color)
|
||||
* @method int colorsTotal()
|
||||
* @method int colorTransparent(?int $color = null)
|
||||
* @method void convolution(array $matrix, float $div, float $offset)
|
||||
* @method void convolution(array<int, array<int, float>> $matrix, float $div, float $offset)
|
||||
* @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 Image cropAuto(int $mode = 0, 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 filledPolygon(array<int, int> $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(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options = [])
|
||||
* @method array<int, int> ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array<string, mixed> $options = [])
|
||||
* @method void gammaCorrect(float $inputgamma, float $outputgamma)
|
||||
* @method array getClip()
|
||||
* @method array{int, int, int, int} getClip()
|
||||
* @method int getInterpolation()
|
||||
* @method int interlace(?bool $enable = null)
|
||||
* @method bool isTrueColor()
|
||||
* @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 openPolygon(array<int, int> $points, ImageColor $color)
|
||||
* @method void paletteCopy(Image $source)
|
||||
* @method void paletteToTrueColor()
|
||||
* @method void polygon(array $points, ImageColor $color)
|
||||
* @method void polygon(array<int, int> $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 Image scale(int $newWidth, int $newHeight = -1, int $mode = 3)
|
||||
* @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 setInterpolation(int $method = 3)
|
||||
* @method void setPixel(int $x, int $y, ImageColor $color)
|
||||
* @method void setStyle(array $style)
|
||||
* @method void setStyle(array<int, int> $style)
|
||||
* @method void setThickness(int $thickness)
|
||||
* @method void setTile(Image $tile)
|
||||
* @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 = [])
|
||||
* @method array<int, int> ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontfile, string $text, array<string, mixed> $options = [])
|
||||
* @property-read positive-int $width
|
||||
* @property-read positive-int $height
|
||||
* @property-read \GdImage $imageResource
|
||||
|
|
@ -139,6 +137,7 @@ class Image
|
|||
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'];
|
||||
private const Sentinel = "\0";
|
||||
|
||||
private \GdImage $image;
|
||||
|
||||
|
|
@ -146,6 +145,7 @@ class Image
|
|||
/**
|
||||
* Returns RGB color (0..255) and transparency (0..127).
|
||||
* @deprecated use ImageColor::rgb()
|
||||
* @return array{red: int, green: int, blue: int, alpha: int}
|
||||
*/
|
||||
public static function rgb(int $red, int $green, int $blue, int $transparency = 0): array
|
||||
{
|
||||
|
|
@ -159,11 +159,13 @@ class Image
|
|||
|
||||
|
||||
/**
|
||||
* Reads an image from a file and returns its type in $type.
|
||||
* Reads an image from a file and returns its type in $type. If $warnings is passed, recoverable decoder
|
||||
* warnings are returned in it instead of being raised as a PHP warning.
|
||||
* @param-out ?string $warnings
|
||||
* @throws Nette\NotSupportedException if gd extension is not loaded
|
||||
* @throws UnknownImageFileException if file not found or file type is not known
|
||||
*/
|
||||
public static function fromFile(string $file, ?int &$type = null): static
|
||||
public static function fromFile(string $file, ?int &$type = null, ?string &$warnings = self::Sentinel): static
|
||||
{
|
||||
self::ensureExtension();
|
||||
$type = self::detectTypeFromFile($file);
|
||||
|
|
@ -171,16 +173,18 @@ class Image
|
|||
throw new UnknownImageFileException(is_file($file) ? "Unknown type of file '$file'." : "File '$file' not found.");
|
||||
}
|
||||
|
||||
return self::invokeSafe('imagecreatefrom' . self::Formats[$type], $file, "Unable to open file '$file'.", __METHOD__);
|
||||
return self::invokeSafe('imagecreatefrom' . self::Formats[$type], $file, "Unable to open file '$file'.", __METHOD__, $warnings);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads an image from a string and returns its type in $type.
|
||||
* Reads an image from a string and returns its type in $type. If $warnings is passed, recoverable decoder
|
||||
* warnings are returned in it instead of being raised as a PHP warning.
|
||||
* @param-out ?string $warnings
|
||||
* @throws Nette\NotSupportedException if gd extension is not loaded
|
||||
* @throws ImageException
|
||||
*/
|
||||
public static function fromString(string $s, ?int &$type = null): static
|
||||
public static function fromString(string $s, ?int &$type = null, ?string &$warnings = self::Sentinel): static
|
||||
{
|
||||
self::ensureExtension();
|
||||
$type = self::detectTypeFromString($s);
|
||||
|
|
@ -188,21 +192,31 @@ class Image
|
|||
throw new UnknownImageFileException('Unknown type of image.');
|
||||
}
|
||||
|
||||
return self::invokeSafe('imagecreatefromstring', $s, 'Unable to open image from string.', __METHOD__);
|
||||
return self::invokeSafe('imagecreatefromstring', $s, 'Unable to open image from string.', __METHOD__, $warnings);
|
||||
}
|
||||
|
||||
|
||||
private static function invokeSafe(string $func, string $arg, string $message, string $callee): static
|
||||
/** @param callable-string $func */
|
||||
private static function invokeSafe(
|
||||
string $func,
|
||||
string $arg,
|
||||
string $message,
|
||||
string $callee,
|
||||
?string &$warnings = self::Sentinel,
|
||||
): static
|
||||
{
|
||||
$errors = [];
|
||||
$res = Callback::invokeSafe($func, [$arg], function (string $message) use (&$errors): void {
|
||||
$errors[] = $message;
|
||||
});
|
||||
|
||||
$raiseWarning = $warnings === self::Sentinel;
|
||||
$warnings = $errors ? implode(', ', $errors) : null;
|
||||
|
||||
if (!$res) {
|
||||
throw new ImageException($message . ' Errors: ' . implode(', ', $errors));
|
||||
} elseif ($errors) {
|
||||
trigger_error($callee . '(): ' . implode(', ', $errors), E_USER_WARNING);
|
||||
throw new ImageException($message . ' Errors: ' . $warnings);
|
||||
} elseif ($errors && $raiseWarning) {
|
||||
trigger_error($callee . '(): ' . $warnings, E_USER_WARNING);
|
||||
}
|
||||
|
||||
return new static($res);
|
||||
|
|
@ -213,6 +227,7 @@ 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
|
||||
* @param ImageColor|array{red: int, green: int, blue: int, alpha?: int}|null $color
|
||||
* @throws Nette\NotSupportedException if gd extension is not loaded
|
||||
*/
|
||||
public static function fromBlank(int $width, int $height, ImageColor|array|null $color = null): static
|
||||
|
|
@ -224,9 +239,9 @@ class Image
|
|||
|
||||
$image = new static(imagecreatetruecolor($width, $height));
|
||||
if ($color) {
|
||||
$image->alphablending(false);
|
||||
$image->filledrectangle(0, 0, $width - 1, $height - 1, $color);
|
||||
$image->alphablending(true);
|
||||
$image->alphaBlending(false);
|
||||
$image->filledRectangle(0, 0, $width - 1, $height - 1, self::normalizeColor($color));
|
||||
$image->alphaBlending(true);
|
||||
}
|
||||
|
||||
return $image;
|
||||
|
|
@ -235,9 +250,11 @@ class Image
|
|||
|
||||
/**
|
||||
* Returns the type of image from file.
|
||||
* @return ImageType::*|null
|
||||
* @param-out ?int $width
|
||||
* @param-out ?int $height
|
||||
* @return ?ImageType::*
|
||||
*/
|
||||
public static function detectTypeFromFile(string $file, &$width = null, &$height = null): ?int
|
||||
public static function detectTypeFromFile(string $file, mixed &$width = null, mixed &$height = null): ?int
|
||||
{
|
||||
[$width, $height, $type] = Helpers::falseToNull(@getimagesize($file)); // @ - files smaller than 12 bytes causes read error
|
||||
return $type && isset(self::Formats[$type]) ? $type : null;
|
||||
|
|
@ -246,9 +263,11 @@ class Image
|
|||
|
||||
/**
|
||||
* Returns the type of image from string.
|
||||
* @return ImageType::*|null
|
||||
* @param-out ?int $width
|
||||
* @param-out ?int $height
|
||||
* @return ?ImageType::*
|
||||
*/
|
||||
public static function detectTypeFromString(string $s, &$width = null, &$height = null): ?int
|
||||
public static function detectTypeFromString(string $s, mixed &$width = null, mixed &$height = null): ?int
|
||||
{
|
||||
[$width, $height, $type] = Helpers::falseToNull(@getimagesizefromstring($s)); // @ - strings smaller than 12 bytes causes read error
|
||||
return $type && isset(self::Formats[$type]) ? $type : null;
|
||||
|
|
@ -297,6 +316,7 @@ class Image
|
|||
|
||||
|
||||
/**
|
||||
* Checks whether the given image type is supported by the GD extension.
|
||||
* @param ImageType::* $type
|
||||
*/
|
||||
public static function isTypeSupported(int $type): bool
|
||||
|
|
@ -314,7 +334,10 @@ class Image
|
|||
}
|
||||
|
||||
|
||||
/** @return ImageType[] */
|
||||
/**
|
||||
* Returns list of image types supported by the GD extension.
|
||||
* @return ImageType::*[]
|
||||
*/
|
||||
public static function getSupportedTypes(): array
|
||||
{
|
||||
self::ensureExtension();
|
||||
|
|
@ -386,6 +409,10 @@ class Image
|
|||
public function resize(int|string|null $width, int|string|null $height, int $mode = self::OrSmaller): static
|
||||
{
|
||||
if ($mode & self::Cover) {
|
||||
if ($width === null || $height === null) {
|
||||
throw new Nette\InvalidArgumentException('Both width and height must be set for Cover mode.');
|
||||
}
|
||||
|
||||
return $this->resize($width, $height, self::OrBigger)->crop('50%', '50%', $width, $height);
|
||||
}
|
||||
|
||||
|
|
@ -419,12 +446,13 @@ class Image
|
|||
/**
|
||||
* 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
|
||||
* @return array{int<1, max>, int<1, max>}
|
||||
*/
|
||||
public static function calculateSize(
|
||||
int $srcWidth,
|
||||
int $srcHeight,
|
||||
$newWidth,
|
||||
$newHeight,
|
||||
int|string|null $newWidth,
|
||||
int|string|null $newHeight,
|
||||
int $mode = self::OrSmaller,
|
||||
): array
|
||||
{
|
||||
|
|
@ -468,19 +496,19 @@ class Image
|
|||
}
|
||||
|
||||
if ($mode & self::OrBigger) {
|
||||
$scale = [max($scale)];
|
||||
$scale = [max($scale ?: [1])];
|
||||
}
|
||||
|
||||
if ($mode & self::ShrinkOnly) {
|
||||
$scale[] = 1;
|
||||
}
|
||||
|
||||
$scale = min($scale);
|
||||
$scale = min($scale ?: [1]);
|
||||
$newWidth = (int) round($srcWidth * $scale);
|
||||
$newHeight = (int) round($srcHeight * $scale);
|
||||
}
|
||||
|
||||
return [max($newWidth, 1), max($newHeight, 1)];
|
||||
return [max((int) $newWidth, 1), max((int) $newHeight, 1)];
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -495,7 +523,7 @@ class Image
|
|||
$this->image = imagecrop($this->image, $r);
|
||||
imagesavealpha($this->image, true);
|
||||
} else {
|
||||
$newImage = static::fromBlank($r['width'], $r['height'], ImageColor::rgb(0, 0, 0, 0))->getImageResource();
|
||||
$newImage = static::fromBlank(max(1, $r['width']), max(1, $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;
|
||||
}
|
||||
|
|
@ -506,6 +534,7 @@ class Image
|
|||
|
||||
/**
|
||||
* Calculates dimensions of cutout in image. Arguments accepts pixels or percent.
|
||||
* @return array{int, int, int, int}
|
||||
*/
|
||||
public static function calculateCutout(
|
||||
int $srcWidth,
|
||||
|
|
@ -516,21 +545,10 @@ class Image
|
|||
int|string $newHeight,
|
||||
): array
|
||||
{
|
||||
if (self::isPercent($newWidth)) {
|
||||
$newWidth = (int) round($srcWidth / 100 * $newWidth);
|
||||
}
|
||||
|
||||
if (self::isPercent($newHeight)) {
|
||||
$newHeight = (int) round($srcHeight / 100 * $newHeight);
|
||||
}
|
||||
|
||||
if (self::isPercent($left)) {
|
||||
$left = (int) round(($srcWidth - $newWidth) / 100 * $left);
|
||||
}
|
||||
|
||||
if (self::isPercent($top)) {
|
||||
$top = (int) round(($srcHeight - $newHeight) / 100 * $top);
|
||||
}
|
||||
$newWidth = (int) (self::isPercent($newWidth) ? round($srcWidth / 100 * $newWidth) : $newWidth);
|
||||
$newHeight = (int) (self::isPercent($newHeight) ? round($srcHeight / 100 * $newHeight) : $newHeight);
|
||||
$left = (int) (self::isPercent($left) ? round(($srcWidth - $newWidth) / 100 * $left) : $left);
|
||||
$top = (int) (self::isPercent($top) ? round(($srcHeight - $newHeight) / 100 * $top) : $top);
|
||||
|
||||
if ($left < 0) {
|
||||
$newWidth += $left;
|
||||
|
|
@ -575,14 +593,8 @@ class Image
|
|||
|
||||
$width = $image->getWidth();
|
||||
$height = $image->getHeight();
|
||||
|
||||
if (self::isPercent($left)) {
|
||||
$left = (int) round(($this->getWidth() - $width) / 100 * $left);
|
||||
}
|
||||
|
||||
if (self::isPercent($top)) {
|
||||
$top = (int) round(($this->getHeight() - $height) / 100 * $top);
|
||||
}
|
||||
$left = (int) (self::isPercent($left) ? round(($this->getWidth() - $width) / 100 * $left) : $left);
|
||||
$top = (int) (self::isPercent($top) ? round(($this->getHeight() - $height) / 100 * $top) : $top);
|
||||
|
||||
$output = $input = $image->image;
|
||||
if ($opacity < 100) {
|
||||
|
|
@ -595,7 +607,7 @@ class Image
|
|||
imagealphablending($output, false);
|
||||
if (!$image->isTrueColor()) {
|
||||
$input = $output;
|
||||
imagefilledrectangle($output, 0, 0, $width, $height, imagecolorallocatealpha($output, 0, 0, 0, 127));
|
||||
imagefilledrectangle($output, 0, 0, $width, $height, (int) imagecolorallocatealpha($output, 0, 0, 0, 127));
|
||||
imagecopy($output, $image->image, 0, 0, 0, 0, $width, $height);
|
||||
}
|
||||
|
||||
|
|
@ -626,6 +638,8 @@ class Image
|
|||
|
||||
/**
|
||||
* Calculates the bounding box for a TrueType text. Returns keys left, top, width and height.
|
||||
* @param array<string, mixed> $options
|
||||
* @return array{left: int, top: int, width: int, height: int}
|
||||
*/
|
||||
public static function calculateTextBox(
|
||||
string $text,
|
||||
|
|
@ -647,7 +661,7 @@ class Image
|
|||
|
||||
|
||||
/**
|
||||
* Draw a rectangle.
|
||||
* Draws a rectangle using top-left coordinates and dimensions instead of two corner coordinates.
|
||||
*/
|
||||
public function rectangleWH(int $x, int $y, int $width, int $height, ImageColor $color): void
|
||||
{
|
||||
|
|
@ -658,7 +672,7 @@ class Image
|
|||
|
||||
|
||||
/**
|
||||
* Draw a filled rectangle.
|
||||
* Draws a filled rectangle using top-left coordinates and dimensions instead of two corner coordinates.
|
||||
*/
|
||||
public function filledRectangleWH(int $x, int $y, int $width, int $height, ImageColor $color): void
|
||||
{
|
||||
|
|
@ -670,7 +684,7 @@ class Image
|
|||
|
||||
/**
|
||||
* 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
|
||||
* @param ?ImageType::* $type
|
||||
* @throws ImageException
|
||||
*/
|
||||
public function save(string $file, ?int $quality = null, ?int $type = null): void
|
||||
|
|
@ -746,6 +760,7 @@ class Image
|
|||
|
||||
/**
|
||||
* Call to undefined method.
|
||||
* @param mixed[] $args
|
||||
* @throws Nette\MemberAccessException
|
||||
*/
|
||||
public function __call(string $name, array $args): mixed
|
||||
|
|
@ -760,6 +775,7 @@ class Image
|
|||
$args[$key] = $value->getImageResource();
|
||||
|
||||
} elseif ($value instanceof ImageColor || (is_array($value) && isset($value['red']))) {
|
||||
/** @var ImageColor|array{red: int, green: int, blue: int, alpha?: int} $value */
|
||||
$args[$key] = $this->resolveColor($value);
|
||||
}
|
||||
}
|
||||
|
|
@ -775,10 +791,11 @@ class Image
|
|||
{
|
||||
ob_start(fn() => '');
|
||||
imagepng($this->image, null, 0);
|
||||
$this->setImageResource(imagecreatefromstring(ob_get_clean()));
|
||||
$this->setImageResource(imagecreatefromstring(ob_get_clean()) ?: throw new Nette\ShouldNotHappenException);
|
||||
}
|
||||
|
||||
|
||||
/** @param-out int|float $num */
|
||||
private static function isPercent(int|string &$num): bool
|
||||
{
|
||||
if (is_string($num) && str_ends_with($num, '%')) {
|
||||
|
|
@ -802,13 +819,31 @@ class Image
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolves a color to a GD color index for the current image.
|
||||
* @param ImageColor|array{red: int, green: int, blue: int, alpha?: int} $color
|
||||
*/
|
||||
public function resolveColor(ImageColor|array $color): int
|
||||
{
|
||||
$color = $color instanceof ImageColor ? $color->toRGBA() : array_values($color);
|
||||
$color = self::normalizeColor($color)->toRGBA();
|
||||
return imagecolorallocatealpha($this->image, ...$color) ?: imagecolorresolvealpha($this->image, ...$color);
|
||||
}
|
||||
|
||||
|
||||
/** @param ImageColor|array{red: int, green: int, blue: int, alpha?: int} $color */
|
||||
private static function normalizeColor(ImageColor|array $color): ImageColor
|
||||
{
|
||||
return $color instanceof ImageColor
|
||||
? $color
|
||||
: ImageColor::rgb(
|
||||
$color['red'],
|
||||
$color['green'],
|
||||
$color['blue'],
|
||||
(127 - ($color['alpha'] ?? 0)) / 127,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private static function ensureExtension(): void
|
||||
{
|
||||
if (!extension_loaded('gd')) {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -18,6 +16,9 @@ use function hexdec, ltrim, max, min, round, strlen;
|
|||
*/
|
||||
class ImageColor
|
||||
{
|
||||
/**
|
||||
* Creates a color from RGB components (0..255) and opacity (0..1).
|
||||
*/
|
||||
public static function rgb(int $red, int $green, int $blue, float $opacity = 1): self
|
||||
{
|
||||
return new self($red, $green, $blue, $opacity);
|
||||
|
|
@ -64,6 +65,10 @@ class ImageColor
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns GD-compatible color array [R, G, B, alpha].
|
||||
* @return array{int<0, 255>, int<0, 255>, int<0, 255>, int<0, 127>}
|
||||
*/
|
||||
public function toRGBA(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use const IMAGETYPE_BMP, IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_WEBP;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -22,6 +20,7 @@ final class Iterables
|
|||
|
||||
/**
|
||||
* Tests for the presence of value.
|
||||
* @param iterable<mixed> $iterable
|
||||
*/
|
||||
public static function contains(iterable $iterable, mixed $value): bool
|
||||
{
|
||||
|
|
@ -36,6 +35,7 @@ final class Iterables
|
|||
|
||||
/**
|
||||
* Tests for the presence of key.
|
||||
* @param iterable<mixed> $iterable
|
||||
*/
|
||||
public static function containsKey(iterable $iterable, mixed $key): bool
|
||||
{
|
||||
|
|
@ -52,9 +52,11 @@ final class Iterables
|
|||
* Returns the first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null.
|
||||
* @template K
|
||||
* @template V
|
||||
* @template E
|
||||
* @param iterable<K, V> $iterable
|
||||
* @param ?callable(V, K, iterable<K, V>): bool $predicate
|
||||
* @return ?V
|
||||
* @param ?callable(): E $else
|
||||
* @return ($else is null ? ?V : V|E)
|
||||
*/
|
||||
public static function first(iterable $iterable, ?callable $predicate = null, ?callable $else = null): mixed
|
||||
{
|
||||
|
|
@ -71,9 +73,11 @@ final class Iterables
|
|||
* 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.
|
||||
* @template K
|
||||
* @template V
|
||||
* @template E
|
||||
* @param iterable<K, V> $iterable
|
||||
* @param ?callable(V, K, iterable<K, V>): bool $predicate
|
||||
* @return ?K
|
||||
* @param ?callable(): E $else
|
||||
* @return ($else is null ? ?K : K|E)
|
||||
*/
|
||||
public static function firstKey(iterable $iterable, ?callable $predicate = null, ?callable $else = null): mixed
|
||||
{
|
||||
|
|
@ -87,7 +91,7 @@ final class Iterables
|
|||
|
||||
|
||||
/**
|
||||
* Tests whether at least one element in the iterator passes the test implemented by the provided function.
|
||||
* Tests whether at least one element in the iterable passes the test implemented by the provided function.
|
||||
* @template K
|
||||
* @template V
|
||||
* @param iterable<K, V> $iterable
|
||||
|
|
@ -105,7 +109,7 @@ final class Iterables
|
|||
|
||||
|
||||
/**
|
||||
* Tests whether all elements in the iterator pass the test implemented by the provided function.
|
||||
* Tests whether all elements in the iterable pass the test implemented by the provided function.
|
||||
* @template K
|
||||
* @template V
|
||||
* @param iterable<K, V> $iterable
|
||||
|
|
@ -123,7 +127,7 @@ final class Iterables
|
|||
|
||||
|
||||
/**
|
||||
* Iterator that filters elements according to a given $predicate. Maintains original keys.
|
||||
* Returns a generator that yields only elements matching the given $predicate. Maintains original keys.
|
||||
* @template K
|
||||
* @template V
|
||||
* @param iterable<K, V> $iterable
|
||||
|
|
@ -141,7 +145,7 @@ final class Iterables
|
|||
|
||||
|
||||
/**
|
||||
* Iterator that transforms values by calling $transformer. Maintains original keys.
|
||||
* Returns a generator that transforms values by calling $transformer. Maintains original keys.
|
||||
* @template K
|
||||
* @template V
|
||||
* @template R
|
||||
|
|
@ -158,14 +162,14 @@ final class Iterables
|
|||
|
||||
|
||||
/**
|
||||
* Iterator that transforms keys and values by calling $transformer. If it returns null, the element is skipped.
|
||||
* Returns a generator that transforms keys and values by calling $transformer. If it returns null, the element is skipped.
|
||||
* @template K
|
||||
* @template V
|
||||
* @template ResV
|
||||
* @template ResK
|
||||
* @template ResV
|
||||
* @param iterable<K, V> $iterable
|
||||
* @param callable(V, K, iterable<K, V>): ?array{ResV, ResK} $transformer
|
||||
* @return \Generator<ResV, ResK>
|
||||
* @param callable(V, K, iterable<K, V>): ?array{ResK, ResV} $transformer
|
||||
* @return \Generator<ResK, ResV>
|
||||
*/
|
||||
public static function mapWithKeys(iterable $iterable, callable $transformer): \Generator
|
||||
{
|
||||
|
|
@ -188,9 +192,10 @@ final class Iterables
|
|||
*/
|
||||
public static function repeatable(callable $factory): \IteratorAggregate
|
||||
{
|
||||
return new class ($factory) implements \IteratorAggregate {
|
||||
return new class ($factory(...)) implements \IteratorAggregate {
|
||||
public function __construct(
|
||||
private $factory,
|
||||
/** @var \Closure(): iterable<mixed, mixed> */
|
||||
private \Closure $factory,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -215,7 +220,8 @@ final class Iterables
|
|||
{
|
||||
return new class (self::toIterator($iterable)) implements \IteratorAggregate {
|
||||
public function __construct(
|
||||
private \Iterator $iterator,
|
||||
private readonly \Iterator $iterator,
|
||||
/** @var array<array{mixed, mixed}> */
|
||||
private array $cache = [],
|
||||
) {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -32,8 +30,8 @@ final class Json
|
|||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Converts value to JSON format. Use $pretty for formatted output, $asciiSafe for ASCII-only output,
|
||||
* $htmlSafe for HTML-safe output, and $forceObjects to encode non-associative arrays as objects.
|
||||
* @throws JsonException
|
||||
*/
|
||||
public static function encode(
|
||||
|
|
@ -66,7 +64,7 @@ final class Json
|
|||
|
||||
|
||||
/**
|
||||
* Parses JSON to PHP value. The $forceArrays enforces the decoding of objects as arrays.
|
||||
* Decodes a JSON string to a PHP value. Use $forceArrays to decode objects as arrays.
|
||||
* @throws JsonException
|
||||
*/
|
||||
public static function decode(string $json, bool|int $forceArrays = false): mixed
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -17,17 +15,17 @@ use Nette;
|
|||
*
|
||||
* @property int $page
|
||||
* @property-read int $firstPage
|
||||
* @property-read int|null $lastPage
|
||||
* @property-read ?int $lastPage
|
||||
* @property-read int<0,max> $firstItemOnPage
|
||||
* @property-read int<0,max> $lastItemOnPage
|
||||
* @property int $base
|
||||
* @property-read bool $first
|
||||
* @property-read bool $last
|
||||
* @property-read int<0,max>|null $pageCount
|
||||
* @property-read ?int<0,max> $pageCount
|
||||
* @property positive-int $itemsPerPage
|
||||
* @property int<0,max>|null $itemCount
|
||||
* @property ?int<0,max> $itemCount
|
||||
* @property-read int<0,max> $offset
|
||||
* @property-read int<0,max>|null $countdownOffset
|
||||
* @property-read ?int<0,max> $countdownOffset
|
||||
* @property-read int<0,max> $length
|
||||
*/
|
||||
class Paginator
|
||||
|
|
@ -41,13 +39,10 @@ class Paginator
|
|||
|
||||
private int $page = 1;
|
||||
|
||||
/** @var int<0, max>|null */
|
||||
/** @var ?int<0, max> */
|
||||
private ?int $itemCount = null;
|
||||
|
||||
|
||||
/**
|
||||
* Sets current page number.
|
||||
*/
|
||||
public function setPage(int $page): static
|
||||
{
|
||||
$this->page = $page;
|
||||
|
|
@ -55,27 +50,18 @@ class Paginator
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns current page number.
|
||||
*/
|
||||
public function getPage(): int
|
||||
{
|
||||
return $this->base + $this->getPageIndex();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns first page number.
|
||||
*/
|
||||
public function getFirstPage(): int
|
||||
{
|
||||
return $this->base;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns last page number.
|
||||
*/
|
||||
public function getLastPage(): ?int
|
||||
{
|
||||
return $this->itemCount === null
|
||||
|
|
@ -85,7 +71,7 @@ class Paginator
|
|||
|
||||
|
||||
/**
|
||||
* Returns the sequence number of the first element on the page
|
||||
* Returns the 1-based index of the first item on the current page, or 0 if the page is empty.
|
||||
* @return int<0, max>
|
||||
*/
|
||||
public function getFirstItemOnPage(): int
|
||||
|
|
@ -97,7 +83,7 @@ class Paginator
|
|||
|
||||
|
||||
/**
|
||||
* Returns the sequence number of the last element on the page
|
||||
* Returns the 1-based index of the last item on the current page.
|
||||
* @return int<0, max>
|
||||
*/
|
||||
public function getLastItemOnPage(): int
|
||||
|
|
@ -106,9 +92,6 @@ class Paginator
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets first page (base) number.
|
||||
*/
|
||||
public function setBase(int $base): static
|
||||
{
|
||||
$this->base = $base;
|
||||
|
|
@ -116,9 +99,6 @@ class Paginator
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns first page (base) number.
|
||||
*/
|
||||
public function getBase(): int
|
||||
{
|
||||
return $this->base;
|
||||
|
|
@ -138,18 +118,12 @@ class Paginator
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the current page the first one?
|
||||
*/
|
||||
public function isFirst(): bool
|
||||
{
|
||||
return $this->getPageIndex() === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the current page the last one?
|
||||
*/
|
||||
public function isLast(): bool
|
||||
{
|
||||
return $this->itemCount === null
|
||||
|
|
@ -159,20 +133,16 @@ class Paginator
|
|||
|
||||
|
||||
/**
|
||||
* Returns the total number of pages.
|
||||
* @return int<0, max>|null
|
||||
* @return ?int<0, max>
|
||||
*/
|
||||
public function getPageCount(): ?int
|
||||
{
|
||||
return $this->itemCount === null
|
||||
? null
|
||||
: (int) ceil($this->itemCount / $this->itemsPerPage);
|
||||
: max(0, (int) ceil($this->itemCount / $this->itemsPerPage));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the number of items to display on a single page.
|
||||
*/
|
||||
public function setItemsPerPage(int $itemsPerPage): static
|
||||
{
|
||||
$this->itemsPerPage = max(1, $itemsPerPage);
|
||||
|
|
@ -181,7 +151,6 @@ class Paginator
|
|||
|
||||
|
||||
/**
|
||||
* Returns the number of items to display on a single page.
|
||||
* @return positive-int
|
||||
*/
|
||||
public function getItemsPerPage(): int
|
||||
|
|
@ -190,9 +159,6 @@ class Paginator
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the total number of items.
|
||||
*/
|
||||
public function setItemCount(?int $itemCount = null): static
|
||||
{
|
||||
$this->itemCount = $itemCount === null ? null : max(0, $itemCount);
|
||||
|
|
@ -201,8 +167,7 @@ class Paginator
|
|||
|
||||
|
||||
/**
|
||||
* Returns the total number of items.
|
||||
* @return int<0, max>|null
|
||||
* @return ?int<0, max>
|
||||
*/
|
||||
public function getItemCount(): ?int
|
||||
{
|
||||
|
|
@ -222,7 +187,7 @@ class Paginator
|
|||
|
||||
/**
|
||||
* Returns the absolute index of the first item on current page in countdown paging.
|
||||
* @return int<0, max>|null
|
||||
* @return ?int<0, max>
|
||||
*/
|
||||
public function getCountdownOffset(): ?int
|
||||
{
|
||||
|
|
@ -240,6 +205,6 @@ class Paginator
|
|||
{
|
||||
return $this->itemCount === null
|
||||
? $this->itemsPerPage
|
||||
: min($this->itemsPerPage, $this->itemCount - $this->getPageIndex() * $this->itemsPerPage);
|
||||
: max(0, min($this->itemsPerPage, $this->itemCount - $this->getPageIndex() * $this->itemsPerPage));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,507 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Represents a process, which can be started and controlled (reading output, writing input, waiting for completion).
|
||||
*/
|
||||
final class Process
|
||||
{
|
||||
private const PollInterval = 10_000;
|
||||
private const DefaultTimeout = 60;
|
||||
private const StdIn = 0;
|
||||
private const StdOut = 1;
|
||||
private const StdErr = 2;
|
||||
|
||||
/** @var resource */
|
||||
private mixed $process;
|
||||
|
||||
/** @var array<string, mixed> result of proc_get_status() */
|
||||
private array $status = ['running' => true];
|
||||
|
||||
/** @var resource */
|
||||
private mixed $inputPipe;
|
||||
|
||||
/** @var array<int, resource|string[]> pipe resources, or ['file', path, mode] descriptors for discarded output */
|
||||
private array $outputPipes = [];
|
||||
|
||||
/** @var string[] */
|
||||
private array $outputBuffers = [];
|
||||
|
||||
/** @var int[] Number of bytes already read from buffers. */
|
||||
private array $outputBufferOffsets = [];
|
||||
|
||||
/** @var array<int, true> Output IDs whose target resource was supplied by the caller and must not be closed here. */
|
||||
private array $callerOutputs = [];
|
||||
private float $startTime;
|
||||
|
||||
|
||||
/**
|
||||
* Starts an executable with given arguments. Because the arguments are passed as an array, the shell is
|
||||
* never involved, so they need no escaping and there is no risk of shell injection.
|
||||
* @param string $executable Path to the executable binary.
|
||||
* @param list<string> $arguments Arguments passed to the executable.
|
||||
* @param string[]|null $env Environment variables or null to use the same environment as the current process.
|
||||
* @param array<string, mixed> $options Additional options for proc_open(). On Windows the executable is launched directly, without cmd.exe.
|
||||
* @param mixed $stdin Input: string, a readable resource (its content is copied to STDIN), a Process (its STDOUT is piped in), or null (STDIN stays open for writeStdInput()).
|
||||
* @param mixed $stdout Output target: string filename, a writable resource backed by a real OS file descriptor (not php://memory etc.), false to discard, or null to capture into memory.
|
||||
* @param mixed $stderr Error output target (same options as $stdout).
|
||||
* @param string|null $directory Working directory.
|
||||
* @param float|null $timeout Time limit in seconds, checked while waiting for or reading the process; null disables it.
|
||||
*/
|
||||
public static function runExecutable(
|
||||
string $executable,
|
||||
array $arguments = [],
|
||||
?array $env = null,
|
||||
array $options = [],
|
||||
mixed $stdin = '',
|
||||
mixed $stdout = null,
|
||||
mixed $stderr = null,
|
||||
?string $directory = null,
|
||||
?float $timeout = self::DefaultTimeout,
|
||||
): self
|
||||
{
|
||||
return new self([$executable, ...$arguments], $env, $options, $directory, $stdin, $stdout, $stderr, $timeout);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Starts a process from a command string interpreted by the shell (/bin/sh on POSIX, cmd.exe on Windows).
|
||||
* Because the shell parses the string, NEVER pass unescaped user input here - use runExecutable() for that.
|
||||
* @param string $command Shell command to run.
|
||||
* @param string[]|null $env Environment variables or null to use the same environment as the current process.
|
||||
* @param array<string, mixed> $options Options for proc_open(), e.g. ['bypass_shell' => true] on Windows to skip cmd.exe.
|
||||
* @param mixed $stdin Input: string, a readable resource (its content is copied to STDIN), a Process (its STDOUT is piped in), or null (STDIN stays open for writeStdInput()).
|
||||
* @param mixed $stdout Output target: string filename, a writable resource backed by a real OS file descriptor (not php://memory etc.), false to discard, or null to capture into memory.
|
||||
* @param mixed $stderr Error output target (same options as $stdout).
|
||||
* @param string|null $directory Working directory.
|
||||
* @param float|null $timeout Time limit in seconds, checked while waiting for or reading the process; null disables it.
|
||||
*/
|
||||
public static function runCommand(
|
||||
string $command,
|
||||
?array $env = null,
|
||||
array $options = [],
|
||||
mixed $stdin = '',
|
||||
mixed $stdout = null,
|
||||
mixed $stderr = null,
|
||||
?string $directory = null,
|
||||
?float $timeout = self::DefaultTimeout,
|
||||
): self
|
||||
{
|
||||
return new self($command, $env, $options, $directory, $stdin, $stdout, $stderr, $timeout);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param list<string>|string $command
|
||||
* @param array<string, string>|null $env
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
private function __construct(
|
||||
string|array $command,
|
||||
?array $env,
|
||||
array $options,
|
||||
?string $directory,
|
||||
mixed $stdin,
|
||||
mixed $stdout,
|
||||
mixed $stderr,
|
||||
private ?float $timeout,
|
||||
) {
|
||||
$descriptors = [
|
||||
self::StdIn => $this->createInputDescriptor($stdin),
|
||||
self::StdOut => $this->createOutputDescriptor(self::StdOut, $stdout),
|
||||
self::StdErr => $this->createOutputDescriptor(self::StdErr, $stderr),
|
||||
];
|
||||
|
||||
$process = @proc_open($command, $descriptors, $pipes, $directory, $env, $options);
|
||||
if (!is_resource($process)) {
|
||||
throw new ProcessFailedException('Failed to start process: ' . Helpers::getLastError());
|
||||
}
|
||||
|
||||
$this->process = $process;
|
||||
[$this->inputPipe, $this->outputPipes[self::StdOut], $this->outputPipes[self::StdErr]] = $pipes + $descriptors;
|
||||
|
||||
if ($stdin instanceof self) {
|
||||
// the source process hands over its STDOUT pipe; from now on this process owns it
|
||||
unset(
|
||||
$stdin->outputBuffers[self::StdOut],
|
||||
$stdin->outputBufferOffsets[self::StdOut],
|
||||
$stdin->outputPipes[self::StdOut],
|
||||
);
|
||||
}
|
||||
|
||||
$this->writeInitialInput($stdin);
|
||||
$this->startTime = microtime(true);
|
||||
}
|
||||
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->outputBuffers = [];
|
||||
$this->terminate();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the process is currently running.
|
||||
*/
|
||||
public function isRunning(): bool
|
||||
{
|
||||
if (!$this->status['running']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->status = proc_get_status($this->process);
|
||||
if (!$this->status['running']) {
|
||||
$this->close();
|
||||
}
|
||||
|
||||
return $this->status['running'];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finishes the process by waiting for its completion. While waiting, the captured output is read
|
||||
* continuously and kept in memory; an optional callback is invoked with each new output/error chunk.
|
||||
*
|
||||
* @param (\Closure(string, string): void)|null $callback
|
||||
*/
|
||||
public function wait(?\Closure $callback = null): void
|
||||
{
|
||||
while ($this->isRunning()) {
|
||||
$this->enforceTimeout();
|
||||
$this->drainPipes();
|
||||
$this->dispatchCallback($callback);
|
||||
usleep(self::PollInterval);
|
||||
}
|
||||
|
||||
$this->dispatchCallback($callback);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads any new data from the captured pipes into the buffers, so a process producing more output
|
||||
* than the OS pipe buffer holds does not block. (On Windows the captured output is a file and never blocks.)
|
||||
*/
|
||||
private function drainPipes(): void
|
||||
{
|
||||
foreach ([self::StdOut, self::StdErr] as $id) {
|
||||
$this->readFromPipe($id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Terminates the running process if it is still running.
|
||||
*/
|
||||
public function terminate(): void
|
||||
{
|
||||
if (!$this->isRunning()) {
|
||||
return;
|
||||
} elseif (Helpers::IsWindows) {
|
||||
exec("taskkill /F /T /PID {$this->getPid()} 2>&1");
|
||||
} else {
|
||||
proc_terminate($this->process, 9); // 9 = SIGKILL: cannot be trapped, so the following proc_close() won't hang
|
||||
}
|
||||
$this->status['running'] = false;
|
||||
$this->close();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the process exit code. If the process is still running, waits until it finishes.
|
||||
*/
|
||||
public function getExitCode(): int
|
||||
{
|
||||
$this->wait();
|
||||
return $this->status['exitcode'] ?? -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the process terminated with exit code 0.
|
||||
*/
|
||||
public function isSuccess(): bool
|
||||
{
|
||||
return $this->getExitCode() === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Waits for the process to finish and throws ProcessFailedException if exit code is not zero.
|
||||
*/
|
||||
public function ensureSuccess(): void
|
||||
{
|
||||
$code = $this->getExitCode();
|
||||
if ($code !== 0) {
|
||||
throw new ProcessFailedException("Process failed with non-zero exit code: $code");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the PID of the running process, or null if it is not running.
|
||||
*/
|
||||
public function getPid(): ?int
|
||||
{
|
||||
return $this->isRunning() ? $this->status['pid'] : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Waits for the process to finish and returns everything it wrote to STDOUT.
|
||||
*/
|
||||
public function getStdOutput(): string
|
||||
{
|
||||
$this->wait();
|
||||
return $this->outputBuffers[self::StdOut] ?? throw new Nette\InvalidStateException('Cannot read output: it is not captured (it was redirected, discarded or piped).');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Waits for the process to finish and returns everything it wrote to STDERR.
|
||||
*/
|
||||
public function getStdError(): string
|
||||
{
|
||||
$this->wait();
|
||||
return $this->outputBuffers[self::StdErr] ?? throw new Nette\InvalidStateException('Cannot read output: it is not captured (it was redirected, discarded or piped).');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the STDOUT data produced since the previous consumeStdOutput() call.
|
||||
* To read everything incrementally, poll `while ($p->isRunning())` calling this, then call it once more
|
||||
* after the loop; that last call returns whatever the process wrote just before the loop noticed it had exited.
|
||||
*/
|
||||
public function consumeStdOutput(): string
|
||||
{
|
||||
return $this->consumeBuffer(self::StdOut);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the STDERR data produced since the previous consumeStdError() call. See consumeStdOutput().
|
||||
*/
|
||||
public function consumeStdError(): string
|
||||
{
|
||||
return $this->consumeBuffer(self::StdErr);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns newly available data from the specified buffer and advances the read pointer.
|
||||
*/
|
||||
private function consumeBuffer(int $id): string
|
||||
{
|
||||
if (!isset($this->outputBuffers[$id])) {
|
||||
throw new Nette\InvalidStateException('Cannot read output: it is not captured (it was redirected, discarded or piped).');
|
||||
} elseif ($this->isRunning()) {
|
||||
$this->enforceTimeout();
|
||||
$this->readFromPipe($id);
|
||||
}
|
||||
return $this->extractNewData($id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the buffered data not returned yet and advances the read pointer.
|
||||
*/
|
||||
private function extractNewData(int $id): string
|
||||
{
|
||||
if (!isset($this->outputBuffers[$id])) {
|
||||
return '';
|
||||
}
|
||||
$res = substr($this->outputBuffers[$id], $this->outputBufferOffsets[$id]);
|
||||
$this->outputBufferOffsets[$id] = strlen($this->outputBuffers[$id]);
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes data into the process' STDIN. If STDIN is closed, throws exception.
|
||||
*/
|
||||
public function writeStdInput(string $string): void
|
||||
{
|
||||
if (!is_resource($this->inputPipe)) {
|
||||
throw new Nette\InvalidStateException('Cannot write to process: STDIN pipe is closed');
|
||||
}
|
||||
$this->writeToPipe($string);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes the whole string to STDIN, handling partial writes. Stops (and lets fwrite() warn) on a broken pipe,
|
||||
* i.e. when the process stopped reading its STDIN.
|
||||
*/
|
||||
private function writeToPipe(string $string): void
|
||||
{
|
||||
$length = strlen($string);
|
||||
for ($written = 0; $written < $length; $written += $bytes) {
|
||||
$bytes = fwrite($this->inputPipe, substr($string, $written));
|
||||
if (!$bytes) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Closes the STDIN pipe, indicating no more data will be sent.
|
||||
*/
|
||||
public function closeStdInput(): void
|
||||
{
|
||||
if (is_resource($this->inputPipe)) {
|
||||
fclose($this->inputPipe);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If a callback is given, invokes it with the output/error produced since the previous call.
|
||||
* @param (\Closure(string, string): void)|null $callback
|
||||
*/
|
||||
private function dispatchCallback(?\Closure $callback): void
|
||||
{
|
||||
if (!$callback) {
|
||||
return;
|
||||
}
|
||||
$output = $this->extractNewData(self::StdOut);
|
||||
$error = $this->extractNewData(self::StdErr);
|
||||
if ($output !== '' || $error !== '') {
|
||||
$callback($output, $error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the timeout has expired. If yes, terminates the process.
|
||||
*/
|
||||
private function enforceTimeout(): void
|
||||
{
|
||||
if ($this->timeout !== null && (microtime(true) - $this->startTime) >= $this->timeout) {
|
||||
$this->terminate();
|
||||
throw new ProcessTimeoutException('Process exceeded the time limit of ' . $this->timeout . ' seconds');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads any new data from the specified pipe and appends it to the buffer. Does nothing if the output
|
||||
* is not captured or the pipe is already closed (or handed over to another process).
|
||||
*/
|
||||
private function readFromPipe(int $id): void
|
||||
{
|
||||
if (!isset($this->outputBuffers[$id]) || !is_resource($this->outputPipes[$id] ?? null)) {
|
||||
return;
|
||||
} elseif (Helpers::IsWindows) {
|
||||
fseek($this->outputPipes[$id], strlen($this->outputBuffers[$id]));
|
||||
} else {
|
||||
stream_set_blocking($this->outputPipes[$id], false);
|
||||
}
|
||||
$this->outputBuffers[$id] .= stream_get_contents($this->outputPipes[$id]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends the initial input to the process: writes and closes a string or stream input,
|
||||
* or leaves STDIN open when input is null (until closeStdInput()) or another Process (fed by that process).
|
||||
* The input type was already validated by createInputDescriptor().
|
||||
*
|
||||
* Note: a string or stream input is written upfront, so if it is large and the process does not read it
|
||||
* while filling its own output, this can block; in that case pass null and feed STDIN via writeStdInput().
|
||||
*/
|
||||
private function writeInitialInput(mixed $input): void
|
||||
{
|
||||
if ($input === null || $input instanceof self) {
|
||||
// STDIN stays open
|
||||
|
||||
} elseif (is_string($input)) {
|
||||
$this->writeToPipe($input);
|
||||
$this->closeStdInput();
|
||||
|
||||
} elseif (is_resource($input)) {
|
||||
stream_copy_to_stream($input, $this->inputPipe);
|
||||
$this->closeStdInput();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates the input and determines the STDIN descriptor based on its type.
|
||||
*/
|
||||
private function createInputDescriptor(mixed $input): mixed
|
||||
{
|
||||
if ($input === null || is_string($input) || is_resource($input)) {
|
||||
return ['pipe', 'r'];
|
||||
} elseif (!$input instanceof self) {
|
||||
throw new Nette\InvalidArgumentException('Input must be string, resource, Process or null, ' . get_debug_type($input) . ' given.');
|
||||
} elseif (Helpers::IsWindows) {
|
||||
throw new Nette\NotSupportedException('Process piping is not supported on Windows.');
|
||||
} elseif (!isset($input->outputBuffers[self::StdOut]) || !is_resource($input->outputPipes[self::StdOut] ?? null)) {
|
||||
throw new Nette\InvalidStateException('Cannot pipe from the given process: its STDOUT must be captured (it must not be redirected elsewhere).');
|
||||
}
|
||||
return $input->outputPipes[self::StdOut];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines the descriptor for STDOUT or STDERR based on the specified output target.
|
||||
*/
|
||||
private function createOutputDescriptor(int $id, mixed $output): mixed
|
||||
{
|
||||
if (is_resource($output)) {
|
||||
$this->callerOutputs[$id] = true;
|
||||
return $output;
|
||||
|
||||
} elseif (is_string($output)) {
|
||||
return FileSystem::open($output, 'w');
|
||||
|
||||
} elseif ($output === false) {
|
||||
return ['file', Helpers::IsWindows ? 'NUL' : '/dev/null', 'w'];
|
||||
|
||||
} elseif ($output === null) {
|
||||
$this->outputBuffers[$id] = '';
|
||||
$this->outputBufferOffsets[$id] = 0;
|
||||
// On Windows anonymous pipes are blocking and cannot be polled without freezing the process,
|
||||
// so captured output is backed by a temporary file that can be read non-blockingly (needed for timeouts).
|
||||
return Helpers::IsWindows ? tmpfile() : ['pipe', 'w'];
|
||||
|
||||
} else {
|
||||
throw new Nette\InvalidArgumentException('Output must be string, resource, bool or null, ' . get_debug_type($output) . ' given.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Closes all pipes and the process resource.
|
||||
*/
|
||||
private function close(): void
|
||||
{
|
||||
$this->drainPipes();
|
||||
$this->closeStdInput();
|
||||
$this->closeOutputPipes();
|
||||
proc_close($this->process);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Closes the output pipes that this class opened; resources supplied by the caller are left untouched.
|
||||
* (The temporary file backing captured output on Windows is removed by fclose() itself.)
|
||||
*/
|
||||
private function closeOutputPipes(): void
|
||||
{
|
||||
foreach ($this->outputPipes as $id => $pipe) {
|
||||
if (is_resource($pipe) && !isset($this->callerOutputs[$id])) {
|
||||
fclose($pipe);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -35,10 +33,14 @@ final class Reflection
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the default value of a parameter. Resolves constants and class constants used as default values.
|
||||
* @throws \ReflectionException if the constant cannot be resolved
|
||||
*/
|
||||
public static function getParameterDefaultValue(\ReflectionParameter $param): mixed
|
||||
{
|
||||
if ($param->isDefaultValueConstant()) {
|
||||
$const = $orig = $param->getDefaultValueConstantName();
|
||||
$const = $orig = $param->getDefaultValueConstantName() ?? throw new Nette\ShouldNotHappenException;
|
||||
$pair = explode('::', $const);
|
||||
if (isset($pair[1])) {
|
||||
$pair[0] = Type::resolve($pair[0], $param);
|
||||
|
|
@ -68,6 +70,7 @@ final class Reflection
|
|||
|
||||
/**
|
||||
* Returns a reflection of a class or trait that contains a declaration of given property. Property can also be declared in the trait.
|
||||
* @return \ReflectionClass<object>
|
||||
*/
|
||||
public static function getPropertyDeclaringClass(\ReflectionProperty $prop): \ReflectionClass
|
||||
{
|
||||
|
|
@ -130,6 +133,9 @@ final class Reflection
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a human-readable string representation of a reflection object.
|
||||
*/
|
||||
public static function toString(\Reflector $ref): string
|
||||
{
|
||||
if ($ref instanceof \ReflectionClass) {
|
||||
|
|
@ -151,6 +157,7 @@ final class Reflection
|
|||
/**
|
||||
* Expands the name of the class to full name in the given context of given class.
|
||||
* Thus, it returns how the PHP parser would understand $name if it were written in the body of the class $context.
|
||||
* @param \ReflectionClass<object> $context
|
||||
* @throws Nette\InvalidArgumentException
|
||||
*/
|
||||
public static function expandClassName(string $name, \ReflectionClass $context): string
|
||||
|
|
@ -189,7 +196,11 @@ final class Reflection
|
|||
}
|
||||
|
||||
|
||||
/** @return array<string, class-string> of [alias => class] */
|
||||
/**
|
||||
* Returns the use statements from the file where the class is defined.
|
||||
* @param \ReflectionClass<object> $class
|
||||
* @return array<string, class-string> Map of alias to fully qualified class name
|
||||
*/
|
||||
public static function getUseStatements(\ReflectionClass $class): array
|
||||
{
|
||||
if ($class->isAnonymous()) {
|
||||
|
|
@ -201,7 +212,7 @@ final class Reflection
|
|||
if ($class->isInternal()) {
|
||||
$cache[$name] = [];
|
||||
} else {
|
||||
$code = file_get_contents($class->getFileName());
|
||||
$code = (string) file_get_contents((string) $class->getFileName());
|
||||
$cache = self::parseUseStatements($code, $name) + $cache;
|
||||
}
|
||||
}
|
||||
|
|
@ -212,6 +223,7 @@ final class Reflection
|
|||
|
||||
/**
|
||||
* Parses PHP code to [class => [alias => class, ...]]
|
||||
* @return array<string, array<string, string>>
|
||||
*/
|
||||
private static function parseUseStatements(string $code, ?string $forClass = null): array
|
||||
{
|
||||
|
|
@ -256,8 +268,8 @@ final class Reflection
|
|||
$name = ltrim($name, '\\');
|
||||
if (self::fetch($tokens, '{')) {
|
||||
while ($suffix = self::fetch($tokens, $nameTokens)) {
|
||||
if (self::fetch($tokens, T_AS)) {
|
||||
$uses[self::fetch($tokens, T_STRING)] = $name . $suffix;
|
||||
if (self::fetch($tokens, T_AS) && ($alias = self::fetch($tokens, T_STRING))) {
|
||||
$uses[$alias] = $name . $suffix;
|
||||
} else {
|
||||
$tmp = explode('\\', $suffix);
|
||||
$uses[end($tmp)] = $name . $suffix;
|
||||
|
|
@ -267,8 +279,8 @@ final class Reflection
|
|||
break;
|
||||
}
|
||||
}
|
||||
} elseif (self::fetch($tokens, T_AS)) {
|
||||
$uses[self::fetch($tokens, T_STRING)] = $name;
|
||||
} elseif (self::fetch($tokens, T_AS) && ($alias = self::fetch($tokens, T_STRING))) {
|
||||
$uses[$alias] = $name;
|
||||
|
||||
} else {
|
||||
$tmp = explode('\\', $name);
|
||||
|
|
@ -301,6 +313,10 @@ final class Reflection
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \PhpToken[] $tokens
|
||||
* @param string|int|int[] $take
|
||||
*/
|
||||
private static function fetch(array &$tokens, string|int|array $take): ?string
|
||||
{
|
||||
$res = null;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use function explode, is_string, str_contains;
|
||||
|
|
@ -18,9 +16,11 @@ use function explode, is_string, str_contains;
|
|||
*/
|
||||
final class ReflectionMethod extends \ReflectionMethod
|
||||
{
|
||||
private \ReflectionClass $originalClass;
|
||||
/** @var \ReflectionClass<object> */
|
||||
private readonly \ReflectionClass $originalClass;
|
||||
|
||||
|
||||
/** @param class-string|object $objectOrMethod */
|
||||
public function __construct(object|string $objectOrMethod, ?string $method = null)
|
||||
{
|
||||
if (is_string($objectOrMethod) && str_contains($objectOrMethod, '::')) {
|
||||
|
|
@ -31,6 +31,7 @@ final class ReflectionMethod extends \ReflectionMethod
|
|||
}
|
||||
|
||||
|
||||
/** @return \ReflectionClass<object> */
|
||||
public function getOriginalClass(): \ReflectionClass
|
||||
{
|
||||
return $this->originalClass;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use JetBrains\PhpStorm\Language;
|
||||
|
|
@ -59,7 +57,8 @@ class Strings
|
|||
throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.');
|
||||
}
|
||||
|
||||
return iconv('UTF-32BE', 'UTF-8//IGNORE', pack('N', $code));
|
||||
$res = iconv('UTF-32BE', 'UTF-8//IGNORE', pack('N', $code));
|
||||
return $res === false ? throw new Nette\ShouldNotHappenException : $res;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -73,11 +72,11 @@ class Strings
|
|||
}
|
||||
|
||||
$tmp = iconv('UTF-8', 'UTF-32BE//IGNORE', $c);
|
||||
if (!$tmp) {
|
||||
if ($tmp === false || $tmp === '') {
|
||||
throw new Nette\InvalidArgumentException('Invalid UTF-8 character "' . ($c === '' ? '' : '\x' . strtoupper(bin2hex($c))) . '".');
|
||||
}
|
||||
|
||||
return unpack('N', $tmp)[1];
|
||||
return unpack('N', $tmp)[1] ?? throw new Nette\ShouldNotHappenException;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -124,7 +123,8 @@ class Strings
|
|||
$start += self::length($s); // unifies iconv_substr behavior with mb_substr
|
||||
}
|
||||
|
||||
return iconv_substr($s, $start, $length, 'UTF-8');
|
||||
$res = iconv_substr($s, $start, $length, 'UTF-8');
|
||||
return $res === false ? throw new Nette\InvalidStateException('iconv_substr() failed.') : $res;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -135,7 +135,7 @@ class Strings
|
|||
public static function normalize(string $s): string
|
||||
{
|
||||
// convert to compressed normal form (NFC)
|
||||
if (class_exists('Normalizer', false) && ($n = \Normalizer::normalize($s, \Normalizer::FORM_C)) !== false) {
|
||||
if (class_exists('Normalizer', autoload: false) && ($n = \Normalizer::normalize($s, \Normalizer::FORM_C)) !== false) {
|
||||
$s = $n;
|
||||
}
|
||||
|
||||
|
|
@ -201,14 +201,23 @@ class Strings
|
|||
$s = strtr($s, ["\u{AE}" => '(R)', "\u{A9}" => '(c)', "\u{2026}" => '...', "\u{AB}" => '<<', "\u{BB}" => '>>', "\u{A3}" => 'lb', "\u{A5}" => 'yen', "\u{B2}" => '^2', "\u{B3}" => '^3', "\u{B5}" => 'u', "\u{B9}" => '^1', "\u{BA}" => 'o', "\u{BF}" => '?', "\u{2CA}" => "'", "\u{2CD}" => '_', "\u{2DD}" => '"', "\u{1FEF}" => '', "\u{20AC}" => 'EUR', "\u{2122}" => 'TM', "\u{212E}" => 'e', "\u{2190}" => '<-', "\u{2191}" => '^', "\u{2192}" => '->', "\u{2193}" => 'V', "\u{2194}" => '<->']); // ® © … « » £ ¥ ² ³ µ ¹ º ¿ ˊ ˍ ˝ ` € ™ ℮ ← ↑ → ↓ ↔
|
||||
}
|
||||
|
||||
$s = \Transliterator::create('Any-Latin; Latin-ASCII')->transliterate($s);
|
||||
$s = \Transliterator::create('Any-Latin; Latin-ASCII')?->transliterate($s)
|
||||
?? throw new Nette\InvalidStateException('Transliterator::transliterate() failed.');
|
||||
|
||||
// use iconv because The transliterator leaves some characters out of ASCII, eg → ʾ
|
||||
if ($iconv === 'glibc') {
|
||||
$s = strtr($s, '?', "\x01"); // temporarily hide ? to distinguish them from the garbage that iconv creates
|
||||
$s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
|
||||
if ($s === false) {
|
||||
throw new Nette\InvalidStateException('iconv() failed.');
|
||||
}
|
||||
|
||||
$s = str_replace(['?', "\x01"], ['', '?'], $s); // remove garbage and restore ? characters
|
||||
} elseif ($iconv === 'libiconv') {
|
||||
$s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
|
||||
if ($s === false) {
|
||||
throw new Nette\InvalidStateException('iconv() failed.');
|
||||
}
|
||||
} else { // null or 'unknown' (#216)
|
||||
$s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); // remove non-ascii chars
|
||||
}
|
||||
|
|
@ -323,7 +332,7 @@ class Strings
|
|||
*/
|
||||
public static function compare(string $left, string $right, ?int $length = null): bool
|
||||
{
|
||||
if (class_exists('Normalizer', false)) {
|
||||
if (class_exists('Normalizer', autoload: false)) {
|
||||
$left = \Normalizer::normalize($left, \Normalizer::FORM_D); // form NFD is faster
|
||||
$right = \Normalizer::normalize($right, \Normalizer::FORM_D); // form NFD is faster
|
||||
}
|
||||
|
|
@ -347,6 +356,10 @@ class Strings
|
|||
public static function findPrefix(array $strings): string
|
||||
{
|
||||
$first = array_shift($strings);
|
||||
if ($first === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
for ($i = 0; $i < strlen($first); $i++) {
|
||||
foreach ($strings as $s) {
|
||||
if (!isset($s[$i]) || $first[$i] !== $s[$i]) {
|
||||
|
|
@ -370,8 +383,8 @@ class Strings
|
|||
public static function length(string $s): int
|
||||
{
|
||||
return match (true) {
|
||||
extension_loaded('mbstring') => mb_strlen($s, 'UTF-8'),
|
||||
extension_loaded('iconv') => iconv_strlen($s, 'UTF-8'),
|
||||
extension_loaded('mbstring') => (int) mb_strlen($s, 'UTF-8'),
|
||||
extension_loaded('iconv') => (int) iconv_strlen($s, 'UTF-8'),
|
||||
default => strlen(@utf8_decode($s)), // deprecated
|
||||
};
|
||||
}
|
||||
|
|
@ -420,7 +433,10 @@ class Strings
|
|||
throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.');
|
||||
}
|
||||
|
||||
return iconv('UTF-32LE', 'UTF-8', strrev(iconv('UTF-8', 'UTF-32BE', $s)));
|
||||
$tmp = iconv('UTF-8', 'UTF-32BE', $s);
|
||||
return $tmp === false
|
||||
? throw new Nette\InvalidStateException('iconv() failed.')
|
||||
: (string) iconv('UTF-32LE', 'UTF-8', strrev($tmp));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -498,7 +514,8 @@ class Strings
|
|||
|
||||
|
||||
/**
|
||||
* Divides the string into arrays according to the regular expression. Expressions in parentheses will be captured and returned as well.
|
||||
* Splits the string by a regular expression. Expressions in parentheses will be captured and returned as well.
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function split(
|
||||
string $subject,
|
||||
|
|
@ -523,8 +540,9 @@ class Strings
|
|||
|
||||
|
||||
/**
|
||||
* Searches the string for the part matching the regular expression and returns
|
||||
* an array with the found expression and individual subexpressions, or `null`.
|
||||
* Searches the string for the first match of the regular expression and returns
|
||||
* an array with the found expression and individual subexpressions, or null.
|
||||
* @return ?array<string>
|
||||
*/
|
||||
public static function match(
|
||||
string $subject,
|
||||
|
|
@ -545,6 +563,7 @@ class Strings
|
|||
$pattern .= 'u';
|
||||
}
|
||||
|
||||
$m = [];
|
||||
if ($offset > strlen($subject)) {
|
||||
return null;
|
||||
} elseif (!self::pcre('preg_match', [$pattern, $subject, &$m, $flags, $offset])) {
|
||||
|
|
@ -558,9 +577,9 @@ class Strings
|
|||
|
||||
|
||||
/**
|
||||
* Searches the string for all occurrences matching the regular expression and
|
||||
* returns an array of arrays containing the found expression and each subexpression.
|
||||
* @return ($lazy is true ? \Generator<int, array> : array[])
|
||||
* Searches the string for all occurrences matching the regular expression and returns
|
||||
* an array of arrays containing the found expression and each subexpression.
|
||||
* @return ($lazy is true ? \Generator<int, array<string>> : list<array<string>>)
|
||||
*/
|
||||
public static function matchAll(
|
||||
string $subject,
|
||||
|
|
@ -583,10 +602,12 @@ class Strings
|
|||
$flags = PREG_OFFSET_CAPTURE | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0);
|
||||
return (function () use ($utf8, $captureOffset, $flags, $subject, $pattern, $offset) {
|
||||
$counter = 0;
|
||||
$m = [];
|
||||
while (
|
||||
$offset <= strlen($subject) - ($counter ? 1 : 0)
|
||||
&& self::pcre('preg_match', [$pattern, $subject, &$m, $flags, $offset])
|
||||
) {
|
||||
/** @var list<array{string, int}> $m */
|
||||
$offset = $m[0][1] + max(1, strlen($m[0][0]));
|
||||
if (!$captureOffset) {
|
||||
$m = array_map(fn($item) => $item[0], $m);
|
||||
|
|
@ -606,6 +627,7 @@ class Strings
|
|||
? $captureOffset
|
||||
: ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0) | ($patternOrder ? PREG_PATTERN_ORDER : 0);
|
||||
|
||||
$m = [];
|
||||
self::pcre('preg_match_all', [
|
||||
$pattern, $subject, &$m,
|
||||
($flags & PREG_PATTERN_ORDER) ? $flags : ($flags | PREG_SET_ORDER),
|
||||
|
|
@ -618,7 +640,8 @@ class Strings
|
|||
|
||||
|
||||
/**
|
||||
* Replaces all occurrences matching regular expression $pattern which can be string or array in the form `pattern => replacement`.
|
||||
* Replaces all occurrences matching the regular expression $pattern, which can be a string or array in the form `pattern => replacement`.
|
||||
* @param string|array<string, string> $pattern
|
||||
*/
|
||||
public static function replace(
|
||||
string $subject,
|
||||
|
|
@ -638,7 +661,7 @@ class Strings
|
|||
|
||||
$flags = ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0);
|
||||
if ($utf8) {
|
||||
$pattern .= 'u';
|
||||
$pattern = is_array($pattern) ? array_map(fn($item) => $item . 'u', $pattern) : $pattern . 'u';
|
||||
if ($captureOffset) {
|
||||
$replacement = fn($m) => $replacement(self::bytesToChars($subject, [$m])[0]);
|
||||
}
|
||||
|
|
@ -659,6 +682,10 @@ class Strings
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param list<array<array{string, int}>> $groups
|
||||
* @return list<array<array{string, int}>>
|
||||
*/
|
||||
private static function bytesToChars(string $s, array $groups): array
|
||||
{
|
||||
$lastBytes = $lastChars = 0;
|
||||
|
|
@ -679,8 +706,12 @@ class Strings
|
|||
}
|
||||
|
||||
|
||||
/** @internal */
|
||||
public static function pcre(string $func, array $args)
|
||||
/**
|
||||
* @param callable-string $func
|
||||
* @param list<mixed> $args
|
||||
* @internal
|
||||
*/
|
||||
public static function pcre(string $func, array $args): mixed
|
||||
{
|
||||
$res = Callback::invokeSafe($func, $args, function (string $message) use ($args): void {
|
||||
// compile-time error, not detectable by preg_last_error
|
||||
|
|
@ -688,7 +719,7 @@ class Strings
|
|||
});
|
||||
|
||||
if (($code = preg_last_error()) // run-time error, but preg_last_error & return code are liars
|
||||
&& ($res === null || !in_array($func, ['preg_filter', 'preg_replace_callback', 'preg_replace'], true))
|
||||
&& ($res === null || !in_array($func, ['preg_filter', 'preg_replace_callback', 'preg_replace'], strict: true))
|
||||
) {
|
||||
throw new RegexpException(preg_last_error_msg()
|
||||
. ' (pattern: ' . implode(' or ', (array) $args[0]) . ')', $code);
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
use function array_map, array_search, array_splice, count, explode, implode, is_a, is_resource, is_string, strcasecmp, strtolower, substr, trim;
|
||||
use function array_map, array_search, array_splice, array_values, count, explode, implode, is_a, is_resource, is_string, strcasecmp, strtolower, substr, trim;
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -18,9 +16,9 @@ use function array_map, array_search, array_splice, count, explode, implode, is_
|
|||
*/
|
||||
final readonly class Type
|
||||
{
|
||||
/** @var array<int, string|self> */
|
||||
/** @var list<string|self> */
|
||||
private array $types;
|
||||
private bool $simple;
|
||||
private ?string $singleName;
|
||||
private string $kind; // | &
|
||||
|
||||
|
||||
|
|
@ -40,7 +38,12 @@ final readonly class Type
|
|||
}
|
||||
|
||||
|
||||
private static function fromReflectionType(\ReflectionType $type, $of, bool $asObject): self|string
|
||||
/** @return ($asObject is true ? self : self|string) */
|
||||
private static function fromReflectionType(
|
||||
\ReflectionType $type,
|
||||
\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $of,
|
||||
bool $asObject,
|
||||
): self|string
|
||||
{
|
||||
if ($type instanceof \ReflectionNamedType) {
|
||||
$name = self::resolve($type->getName(), $of);
|
||||
|
|
@ -107,34 +110,40 @@ final readonly class Type
|
|||
*/
|
||||
public static function resolve(
|
||||
string $type,
|
||||
\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $of,
|
||||
\ReflectionFunction|\ReflectionMethod|\ReflectionParameter|\ReflectionProperty $of,
|
||||
): string
|
||||
{
|
||||
$lower = strtolower($type);
|
||||
if ($of instanceof \ReflectionFunction) {
|
||||
return $type;
|
||||
}
|
||||
|
||||
$class = $of->getDeclaringClass();
|
||||
if ($class === null) {
|
||||
return $type;
|
||||
} elseif ($lower === 'self') {
|
||||
return $of->getDeclaringClass()->name;
|
||||
return $class->name;
|
||||
} elseif ($lower === 'static') {
|
||||
return ($of instanceof ReflectionMethod ? $of->getOriginalClass() : $of->getDeclaringClass())->name;
|
||||
} elseif ($lower === 'parent' && $of->getDeclaringClass()->getParentClass()) {
|
||||
return $of->getDeclaringClass()->getParentClass()->name;
|
||||
return ($of instanceof ReflectionMethod ? $of->getOriginalClass() : $class)->name;
|
||||
} elseif ($lower === 'parent' && $class->getParentClass()) {
|
||||
return $class->getParentClass()->name;
|
||||
} else {
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** @param array<string|self> $types */
|
||||
private function __construct(array $types, string $kind = '|')
|
||||
{
|
||||
$o = array_search('null', $types, strict: true);
|
||||
if ($o !== false) { // null as last
|
||||
array_splice($types, $o, 1);
|
||||
array_splice($types, (int) $o, 1);
|
||||
$types[] = 'null';
|
||||
}
|
||||
|
||||
$this->types = $types;
|
||||
$this->simple = is_string($types[0]) && ($types[1] ?? 'null') === 'null';
|
||||
$this->types = array_values($types);
|
||||
$this->singleName = is_string($types[0]) && ($types[1] ?? 'null') === 'null' ? $types[0] : null;
|
||||
$this->kind = count($types) > 1 ? $kind : '';
|
||||
}
|
||||
|
||||
|
|
@ -142,8 +151,8 @@ final readonly class Type
|
|||
public function __toString(): string
|
||||
{
|
||||
$multi = count($this->types) > 1;
|
||||
if ($this->simple) {
|
||||
return ($multi ? '?' : '') . $this->types[0];
|
||||
if ($this->singleName !== null) {
|
||||
return ($multi ? '?' : '') . $this->singleName;
|
||||
}
|
||||
|
||||
$res = [];
|
||||
|
|
@ -155,7 +164,7 @@ final readonly class Type
|
|||
|
||||
|
||||
/**
|
||||
* Returns a type that accepts both the current type and the given type.
|
||||
* Returns a union type that accepts both the current type and the given type.
|
||||
*/
|
||||
public function with(string|self $type): self
|
||||
{
|
||||
|
|
@ -173,7 +182,7 @@ final readonly class Type
|
|||
|
||||
/**
|
||||
* Returns the array of subtypes that make up the compound type as strings.
|
||||
* @return array<int, string|string[]>
|
||||
* @return list<string|array<string|array<mixed>>>
|
||||
*/
|
||||
public function getNames(): array
|
||||
{
|
||||
|
|
@ -182,8 +191,8 @@ final readonly class Type
|
|||
|
||||
|
||||
/**
|
||||
* Returns the array of subtypes that make up the compound type as Type objects:
|
||||
* @return self[]
|
||||
* Returns the array of subtypes that make up the compound type as Type objects.
|
||||
* @return list<self>
|
||||
*/
|
||||
public function getTypes(): array
|
||||
{
|
||||
|
|
@ -196,9 +205,7 @@ final readonly class Type
|
|||
*/
|
||||
public function getSingleName(): ?string
|
||||
{
|
||||
return $this->simple
|
||||
? $this->types[0]
|
||||
: null;
|
||||
return $this->singleName;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -221,36 +228,36 @@ final readonly class Type
|
|||
|
||||
|
||||
/**
|
||||
* Returns true whether it is a simple type. Single nullable types are also considered to be simple types.
|
||||
* Checks whether it is a simple (non-compound) type. Single nullable types such as ?int are also considered simple.
|
||||
*/
|
||||
public function isSimple(): bool
|
||||
{
|
||||
return $this->simple;
|
||||
return $this->singleName !== null;
|
||||
}
|
||||
|
||||
|
||||
#[\Deprecated('use isSimple()')]
|
||||
public function isSingle(): bool
|
||||
{
|
||||
return $this->simple;
|
||||
return $this->singleName !== null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true whether the type is both a simple and a PHP built-in type.
|
||||
* Checks whether it is a simple PHP built-in type (int, string, bool, etc.).
|
||||
*/
|
||||
public function isBuiltin(): bool
|
||||
{
|
||||
return $this->simple && Validators::isBuiltinType($this->types[0]);
|
||||
return $this->singleName !== null && Validators::isBuiltinType($this->singleName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true whether the type is both a simple and a class name.
|
||||
* Checks whether it is a simple class or interface name (not a built-in type).
|
||||
*/
|
||||
public function isClass(): bool
|
||||
{
|
||||
return $this->simple && !Validators::isBuiltinType($this->types[0]);
|
||||
return $this->singleName !== null && !Validators::isBuiltinType($this->singleName);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -259,12 +266,12 @@ final readonly class Type
|
|||
*/
|
||||
public function isClassKeyword(): bool
|
||||
{
|
||||
return $this->simple && Validators::isClassKeyword($this->types[0]);
|
||||
return $this->singleName !== null && Validators::isClassKeyword($this->singleName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies type compatibility. For example, it checks if a value of a certain type could be passed as a parameter.
|
||||
* Checks whether a value of the given type could be assigned to this type.
|
||||
*/
|
||||
public function allows(string|self $type): bool
|
||||
{
|
||||
|
|
@ -279,6 +286,7 @@ final readonly class Type
|
|||
}
|
||||
|
||||
|
||||
/** @param array<string> $givenTypes */
|
||||
private function allowsAny(array $givenTypes): bool
|
||||
{
|
||||
return $this->isUnion()
|
||||
|
|
@ -287,13 +295,17 @@ final readonly class Type
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array<string> $ourTypes
|
||||
* @param array<string> $givenTypes
|
||||
*/
|
||||
private function allowsAll(array $ourTypes, array $givenTypes): bool
|
||||
{
|
||||
return Arrays::every(
|
||||
$ourTypes,
|
||||
fn($ourType) => Arrays::some(
|
||||
fn(string $ourType) => Arrays::some(
|
||||
$givenTypes,
|
||||
fn($givenType) => Validators::isBuiltinType($ourType)
|
||||
fn(string $givenType) => Validators::isBuiltinType($ourType)
|
||||
? strcasecmp($ourType, $givenType) === 0
|
||||
: is_a($givenType, $ourType, allow_string: true),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -26,7 +24,7 @@ class Validators
|
|||
'never' => 1, 'true' => 1,
|
||||
];
|
||||
|
||||
/** @var array<string,?callable> */
|
||||
/** @var array<string, ?(callable(mixed): bool)> */
|
||||
protected static $validators = [
|
||||
// PHP types
|
||||
'array' => 'is_array',
|
||||
|
|
@ -76,7 +74,7 @@ class Validators
|
|||
'type' => [self::class, 'isType'],
|
||||
];
|
||||
|
||||
/** @var array<string,callable> */
|
||||
/** @var array<string, callable(mixed): int> */
|
||||
protected static $counters = [
|
||||
'string' => 'strlen',
|
||||
'unicode' => [Strings::class, 'length'],
|
||||
|
|
@ -114,22 +112,22 @@ class Validators
|
|||
|
||||
|
||||
/**
|
||||
* Verifies that element $key in array is of expected types separated by pipe.
|
||||
* Verifies that item $key in array exists and is of expected types separated by pipe.
|
||||
* @param mixed[] $array
|
||||
* @throws AssertionException
|
||||
*/
|
||||
public static function assertField(
|
||||
array $array,
|
||||
$key,
|
||||
int|string $key,
|
||||
?string $expected = null,
|
||||
string $label = "item '%' in array",
|
||||
): void
|
||||
{
|
||||
if (!array_key_exists($key, $array)) {
|
||||
throw new AssertionException('Missing ' . str_replace('%', $key, $label) . '.');
|
||||
throw new AssertionException('Missing ' . str_replace('%', (string) $key, $label) . '.');
|
||||
|
||||
} elseif ($expected) {
|
||||
static::assert($array[$key], $expected, str_replace('%', $key, $label));
|
||||
static::assert($array[$key], $expected, str_replace('%', (string) $key, $label));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -159,7 +157,7 @@ class Validators
|
|||
if (!static::$validators[$type]($value)) {
|
||||
continue;
|
||||
}
|
||||
} catch (\TypeError $e) {
|
||||
} catch (\TypeError) {
|
||||
continue;
|
||||
}
|
||||
} elseif ($type === 'pattern') {
|
||||
|
|
@ -197,7 +195,7 @@ class Validators
|
|||
|
||||
/**
|
||||
* Finds whether all values are of expected types separated by pipe.
|
||||
* @param mixed[] $values
|
||||
* @param iterable<mixed> $values
|
||||
*/
|
||||
public static function everyIs(iterable $values, string $expected): bool
|
||||
{
|
||||
|
|
@ -261,7 +259,7 @@ class Validators
|
|||
|
||||
/**
|
||||
* Checks if the value is 0, '', false or null.
|
||||
* @return ($value is 0|''|false|null ? true : false)
|
||||
* @return ($value is 0|0.0|''|false|null ? true : false)
|
||||
*/
|
||||
public static function isNone(mixed $value): bool
|
||||
{
|
||||
|
|
@ -290,6 +288,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 array{int|float|string|\DateTimeInterface|null, int|float|string|\DateTimeInterface|null} $range
|
||||
*/
|
||||
public static function isInRange(mixed $value, array $range): bool
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
|
||||
|
|
@ -48,3 +46,19 @@ class RegexpException extends \Exception
|
|||
class AssertionException extends \Exception
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The process failed to run successfully.
|
||||
*/
|
||||
class ProcessFailedException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The process execution exceeded its timeout limit.
|
||||
*/
|
||||
class ProcessTimeoutException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ final class GeneratedConfig
|
|||
0 => 'rules.neon',
|
||||
),
|
||||
),
|
||||
'version' => '2.12.0',
|
||||
'phpstanVersionConstraint' => '>=2.1.8.0-dev, <3.0.0.0-dev',
|
||||
'version' => '2.13.1',
|
||||
'phpstanVersionConstraint' => '>=2.1.35.0-dev, <3.0.0.0-dev',
|
||||
),
|
||||
'phpstan/phpstan-strict-rules' =>
|
||||
array (
|
||||
|
|
@ -35,8 +35,8 @@ final class GeneratedConfig
|
|||
0 => 'rules.neon',
|
||||
),
|
||||
),
|
||||
'version' => '2.0.7',
|
||||
'phpstanVersionConstraint' => '>=2.1.29.0-dev, <3.0.0.0-dev',
|
||||
'version' => '2.0.11',
|
||||
'phpstanVersionConstraint' => '>=2.1.39.0-dev, <3.0.0.0-dev',
|
||||
),
|
||||
'tomasvotruba/type-coverage' =>
|
||||
array (
|
||||
|
|
@ -58,7 +58,7 @@ final class GeneratedConfig
|
|||
);
|
||||
|
||||
/** @var string|null */
|
||||
public const PHPSTAN_VERSION_CONSTRAINT = '>=2.1.29.0-dev, <3.0.0.0-dev';
|
||||
public const PHPSTAN_VERSION_CONSTRAINT = '>=2.1.39.0-dev, <3.0.0.0-dev';
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{php,phpt}]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
[*.xml]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
[*.neon]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
[*.{yaml,yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[composer.json]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
|
@ -10,13 +10,14 @@
|
|||
|:---------------------------------------|:--------------------------------------------------------------------------------------------------------|
|
||||
| `booleansInConditions` | Require booleans in `if`, `elseif`, ternary operator, after `!`, and on both sides of `&&` and `\|\|`. |
|
||||
| `booleansInLoopConditions` | Require booleans in `while` and `do while` loop conditions. |
|
||||
| `numericOperandsInArithmeticOperators` | Require numeric operand in `+$var`, `-$var`, `$var++`, `$var--`, `++$var` and `--$var`. |
|
||||
| `numericOperandsInArithmeticOperators` | Require numeric operand in `$var++`, `$var--`, `++$var`and `--$var`. |
|
||||
| `uselessCast` | Detect useless casts — e.g. casting to `int` when the expression is already `int`. |
|
||||
| `numericOperandsInArithmeticOperators` | Require numeric operands in `+`, `-`, `*`, `/`, `%`, `**`, `+$var`, `-$var`, `$var++`, `$var--`, `++$var` and `--$var`. |
|
||||
| `strictFunctionCalls` | These functions contain a `$strict` parameter for better type safety, it must be set to `true`:<br>* `in_array` (3rd parameter)<br>* `array_search` (3rd parameter)<br>* `array_keys` (3rd parameter; only if the 2nd parameter `$search_value` is provided)<br>* `base64_decode` (2nd parameter). |
|
||||
| `overwriteVariablesWithLoop` | * Disallow overwriting variables with `foreach` key and value variables.<br>* Disallow overwriting variables with `for` loop initial assignment. |
|
||||
| `switchConditionsMatchingType` | Types in `switch` condition and `case` value must match. PHP compares them loosely by default and that can lead to unexpected results. |
|
||||
| `dynamicCallOnStaticMethod` | Check that statically declared methods are called statically. |
|
||||
| `disallowedEmpty` | Disallow `empty()` - it's a very loose comparison (see [manual](https://php.net/empty)), it's recommended to use more strict one. |
|
||||
| `disallowedLooseComparison` | Disallow loose comparison via `==` and `!=`. |
|
||||
| `disallowedShortTernary` | Disallow short ternary operator (`?:`) - implies weak comparison, it's recommended to use null coalesce operator (`??`) or ternary operator with strict condition. |
|
||||
| `noVariableVariables` | Disallow variable variables (`$$foo`, `$this->$method()` etc.). |
|
||||
| `checkAlwaysTrueInstanceof`, `checkAlwaysTrueCheckTypeFunctionCall`, `checkAlwaysTrueStrictComparison` | Always true `instanceof`, type-checking `is_*` functions and strict comparisons `===`/`!==`. These checks can be turned off by setting `checkAlwaysTrueInstanceof`, `checkAlwaysTrueCheckTypeFunctionCall` and `checkAlwaysTrueStrictComparison` to false. |
|
||||
|
|
@ -26,6 +27,9 @@
|
|||
| | Check LSP even for static methods. |
|
||||
| `requireParentConstructorCall` | Require calling parent constructor. |
|
||||
| `disallowedBacktick` | Disallow usage of backtick operator (`` $ls = `ls -la` ``). |
|
||||
| `disallowedImplicitArrayCreation` | Disallow implicit array creation through `$var[] =` when the variable does not exist yet. |
|
||||
| `strictArrayFilter` | Require `array_filter()` to have a callback parameter to avoid loose comparison semantics. |
|
||||
| `illegalConstructorMethodCall` | Disallow calling `__construct()` on an existing object or as a static call outside of parent constructor. |
|
||||
| `closureUsesThis` | Closure should use `$this` directly instead of using `$this` variable indirectly. |
|
||||
|
||||
Additional rules are coming in subsequent releases!
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@
|
|||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"keywords": ["static analysis"],
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0",
|
||||
"phpstan/phpstan": "^2.1.29"
|
||||
"phpstan/phpstan": "^2.1.39"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-parallel-lint/php-parallel-lint": "^1.2",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ parameters:
|
|||
reportMaybesInPropertyPhpDocTypes: true
|
||||
reportWrongPhpDocTypeInVarTag: true
|
||||
checkStrictPrintfPlaceholderTypes: true
|
||||
reportNonIntStringArrayKey: %featureToggles.bleedingEdge%
|
||||
strictRules:
|
||||
allRules: true
|
||||
disallowedLooseComparison: %strictRules.allRules%
|
||||
|
|
@ -164,6 +165,8 @@ services:
|
|||
|
||||
-
|
||||
class: PHPStan\Rules\DisallowedConstructs\DisallowedLooseComparisonRule
|
||||
arguments:
|
||||
includeOperandTypesInErrorMessage: %featureToggles.bleedingEdge%
|
||||
|
||||
-
|
||||
class: PHPStan\Rules\BooleansInConditions\BooleanInBooleanAndRule
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ namespace PHPStan\Rules\Cast;
|
|||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\Cast;
|
||||
use PhpParser\Node\Expr\Cast\Void_;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Rules\Rule;
|
||||
use PHPStan\Rules\RuleErrorBuilder;
|
||||
|
|
@ -38,6 +39,10 @@ class UselessCastRule implements Rule
|
|||
|
||||
public function processNode(Node $node, Scope $scope): array
|
||||
{
|
||||
if ($node instanceof Void_) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$castType = $scope->getType($node);
|
||||
if ($castType instanceof ErrorType) {
|
||||
return [];
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ use PhpParser\Node\Expr\BinaryOp\NotEqual;
|
|||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Rules\Rule;
|
||||
use PHPStan\Rules\RuleErrorBuilder;
|
||||
use PHPStan\Type\VerbosityLevel;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* @implements Rule<BinaryOp>
|
||||
|
|
@ -16,6 +18,13 @@ use PHPStan\Rules\RuleErrorBuilder;
|
|||
class DisallowedLooseComparisonRule implements Rule
|
||||
{
|
||||
|
||||
private bool $includeOperandTypesInErrorMessage;
|
||||
|
||||
public function __construct(bool $includeOperandTypesInErrorMessage)
|
||||
{
|
||||
$this->includeOperandTypesInErrorMessage = $includeOperandTypesInErrorMessage;
|
||||
}
|
||||
|
||||
public function getNodeType(): string
|
||||
{
|
||||
return BinaryOp::class;
|
||||
|
|
@ -23,26 +32,34 @@ class DisallowedLooseComparisonRule implements Rule
|
|||
|
||||
public function processNode(Node $node, Scope $scope): array
|
||||
{
|
||||
if (!$node instanceof Equal && !$node instanceof NotEqual) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$left = $scope->getType($node->left)->describe(VerbosityLevel::typeOnly());
|
||||
$right = $scope->getType($node->right)->describe(VerbosityLevel::typeOnly());
|
||||
|
||||
if ($node instanceof Equal) {
|
||||
return [
|
||||
RuleErrorBuilder::message(
|
||||
'Loose comparison via "==" is not allowed.',
|
||||
$this->includeOperandTypesInErrorMessage
|
||||
? sprintf('Loose comparison via "==" between %s and %s is not allowed.', $left, $right)
|
||||
: 'Loose comparison via "==" is not allowed.',
|
||||
)->tip('Use strict comparison via "===" instead.')
|
||||
->identifier('equal.notAllowed')
|
||||
->build(),
|
||||
];
|
||||
}
|
||||
if ($node instanceof NotEqual) {
|
||||
|
||||
return [
|
||||
RuleErrorBuilder::message(
|
||||
'Loose comparison via "!=" is not allowed.',
|
||||
$this->includeOperandTypesInErrorMessage
|
||||
? sprintf('Loose comparison via "!=" between %s and %s is not allowed.', $left, $right)
|
||||
: 'Loose comparison via "!=" is not allowed.',
|
||||
)->tip('Use strict comparison via "!==" instead.')
|
||||
->identifier('notEqual.notAllowed')
|
||||
->build(),
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,10 @@ class VariablePropertyFetchRule implements Rule
|
|||
return [];
|
||||
}
|
||||
|
||||
if ($scope->getType($node->name)->isLiteralString()->yes()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$fetchedOnType = $scope->getType($node->var);
|
||||
foreach ($fetchedOnType->getObjectClassNames() as $referencedClass) {
|
||||
if (!$this->reflectionProvider->hasClass($referencedClass)) {
|
||||
|
|
|
|||
|
|
@ -27,8 +27,9 @@ final class PharAutoloader
|
|||
if (self::$composerAutoloader === null) {
|
||||
self::$composerAutoloader = require 'phar://' . __DIR__ . '/phpstan.phar/vendor/autoload.php';
|
||||
require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/jetbrains/phpstorm-stubs/PhpStormStubsMap.php';
|
||||
require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/react/async/src/functions_include.php';
|
||||
require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/react/promise/src/functions_include.php';
|
||||
require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/ralouphie/getallheaders/src/getallheaders.php';
|
||||
require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/guzzlehttp/guzzle/src/functions_include.php';
|
||||
}
|
||||
self::$composerAutoloader->loadClass($class);
|
||||
|
||||
|
|
@ -106,7 +107,7 @@ final class PharAutoloader
|
|||
if (
|
||||
PHP_VERSION_ID < 80400
|
||||
&& empty ($GLOBALS['__composer_autoload_files']['9d2b9fc6db0f153a0a149fefb182415e'])
|
||||
&& !class_exists(\Symfony\Polyfill\Php83\Php84::class, false)
|
||||
&& !class_exists(\Symfony\Polyfill\Php84\Php84::class, false)
|
||||
) {
|
||||
$GLOBALS['__composer_autoload_files']['9d2b9fc6db0f153a0a149fefb182415e'] = true;
|
||||
require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php84/Php84.php';
|
||||
|
|
@ -116,7 +117,7 @@ final class PharAutoloader
|
|||
if (
|
||||
PHP_VERSION_ID < 80500
|
||||
&& empty ($GLOBALS['__composer_autoload_files']['606a39d89246991a373564698c2d8383'])
|
||||
&& !class_exists(\Symfony\Polyfill\Php83\Php85::class, false)
|
||||
&& !class_exists(\Symfony\Polyfill\Php85\Php85::class, false)
|
||||
) {
|
||||
$GLOBALS['__composer_autoload_files']['606a39d89246991a373564698c2d8383'] = true;
|
||||
require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php85/Php85.php';
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,16 +1,16 @@
|
|||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQIzBAABCgAdFiEEynwsejDI6OEnSoR2UcZzBf/C5cAFAmkysr4ACgkQUcZzBf/C
|
||||
5cDE+RAAlbDueQxsbyo+wqm1sURcXT4fPbwdFAv1RXSYfZl7XraoagxYHssIHt9n
|
||||
TcKuBSOPOmlKgZ4FmVW05TRr0yoLZtckpRDCWuJoPzI9ilW2tiVi6AbEVhV0EqwL
|
||||
xVToGIzZXmti+X5Mm2OwQMCdpAbqpAy0mwmv/TudFiKdgZBzl5zHWDUSNCwP8s2m
|
||||
pLGBupQis6sA45+u28vLpyR7vn3mIq3F3hJgb5nrZlWt33jVJ7MDFnpGpeJ6+jBn
|
||||
/1BmefK0sXwN4EqaJbrRkHdRePISt2+jQXa/V/L3oJ0a3hVFufcGze41ARecSQ/0
|
||||
203aB0XcBtIFlshE5OIpXW+ibmgBabd+ggZEkRoS3OXtV5cJizLh49B+16A97QOr
|
||||
H0NWgLfY/W1u2t0Iu+WDT37Na5LPsuucW9vv9PwS6WY9eCr4Jbbmq1PtF0T17aqx
|
||||
STyPWNjs5A8NNjVUQ+Ufj/AAdH3hmNj4B5H6psxHD5UsFHoOLVFtwih4GdHrKXIZ
|
||||
N2lznVo8HDCcvKo6C4F8ejG1t4f5qBHNpYeEC9aVQVvXrBockQQz4lUclibW7MIe
|
||||
g33fXgO/GIm0dn2u0NCzjYof+TAVOfvtbK4GY0Z9BOlJcQnzu7V1LxqFamG5ef+u
|
||||
A2ABQa0HVEeKTomB3jzNQQVKw9QzWyfI21xSskfpFs4FXW3A5xc=
|
||||
=edJd
|
||||
iQIzBAABCgAdFiEEynwsejDI6OEnSoR2UcZzBf/C5cAFAmnyB+sACgkQUcZzBf/C
|
||||
5cCc/w//RkO2T6MfMe/Fh1MeWAtdvqVCBDG0x1SwP0Z4UmhW2/EyxPlgP7LtyZOr
|
||||
IiivLmTQlv9VgNsyKrVJjvo6WH4mQ39s6yyGHsRJxtQqeZRHQWFkCz/SykwCFJ8e
|
||||
X7vhJsW6UzvboA1/zvjkuRnOr1JVt2DWRVN2P5STK+hk/hizBE/ec404Boc/iD7A
|
||||
0WGEnhnDGsyKfwB1NHhiWiqUrZ3Wo8x17wyeZsvmH/SL2oimfEjPe3oGm37CKbjr
|
||||
ztSGnCXfO5EWHxFOIs5O1xbW7Lz13EboQNzpsJyQVpjVwEdLq8xt2LWxnRN7gQTd
|
||||
5JV5pygmAFhrf44xoFa20HLK2ia14izA+dMTXvTBRHDPJzPaVo2wW3Nt4qg+WCdT
|
||||
TrN7IAWx88OjZ/4rAKIp3SDzNj3TsN9KxzriCokzZmExipkZL4C+rDLkqhwEhrwE
|
||||
W+kSGTz0egQeNwBA1FBWKmiBzMYr+rF2uwy7Oud2BHwnq1N0n5tpS5Djsti0fFcp
|
||||
YQdNt27e7yjV4LO6O7h7uK0r7u0QZ7Pw0En9sU9FC5PQsEmzP22aUoFpqEnzNI2g
|
||||
7MbHRO5qKmYI7f991RFOj/utbuk5d17ByAOLroJ8Ub8B2qIVEczTPwtYJ06Wf59I
|
||||
ZRQDRoWjv4q8JosQ9xclS2nt6S//SMmeXrJLMh5t3u65vQN9OcI=
|
||||
=H59Q
|
||||
-----END PGP SIGNATURE-----
|
||||
|
|
|
|||
BIN
tools/composer
BIN
tools/composer
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue