@@ -21,7 +21,7 @@ The package provides:
- `NumericHelper` that has static methods to work with numeric strings;
- `Inflector` provides methods such as `toPlural()` or `toSlug()` that derive a new string based on the string given;
- `WildcardPattern` is a shell wildcard pattern to match strings against;
-- `CombinedRegexp` is a wrapper that optimizes multiple regular expressions matching and
+- `CombinedRegexp` is a wrapper that optimizes multiple regular expressions matching and
`MemoizedCombinedRegexp` is a decorator that caches results of `CombinedRegexp` to speed up matching.
## Requirements
@@ -157,7 +157,7 @@ Overall the inflector has the following method groups.
## WildcardPattern usage
-`WildcardPattern` allows a simple POSIX-style string matching.
+`WildcardPattern` allows a simple POSIX-style string matching.
```php
use \Yiisoft\Strings\WildcardPattern;
diff --git a/composer.json b/composer.json
index 8f3564d..1a29832 100644
--- a/composer.json
+++ b/composer.json
@@ -10,13 +10,23 @@
"homepage": "https://www.yiiframework.com/",
"license": "BSD-3-Clause",
"support": {
+ "issues": "https://github.com/yiisoft/strings/issues?state=open",
"source": "https://github.com/yiisoft/strings",
- "issues": "https://github.com/yiisoft/strings/issues",
"forum": "https://www.yiiframework.com/forum/",
"wiki": "https://www.yiiframework.com/wiki/",
"irc": "ircs://irc.libera.chat:6697/yii",
"chat": "https://t.me/yii3en"
},
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/yiisoft"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/yiisoft"
+ }
+ ],
"require": {
"php": "^8.0",
"ext-mbstring": "*"
From 47a3675d817d98612aeec5083d73a184ecd5239c Mon Sep 17 00:00:00 2001
From: Luiz Marin <67489841+luizcmarin@users.noreply.github.com>
Date: Fri, 24 May 2024 10:48:48 -0300
Subject: [PATCH 07/60] Update UPGRADE.md (#132)
---
UPGRADE.md | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/UPGRADE.md b/UPGRADE.md
index 07fc897..e7cb567 100644
--- a/UPGRADE.md
+++ b/UPGRADE.md
@@ -1,12 +1,11 @@
-# Yii Strings Upgrading Instructions
+# Upgrading Instructions for Yii Strings
-These notes highlight changes that could break your application when you upgrade package from one version to another.
+This file contains the upgrade notes. These notes highlight changes that could break your
+application when you upgrade the package from one version to another.
-Upgrading in general is as simple as updating your dependency in your `composer.json` and running `composer update`.
-In a big application however there may be more things to consider, which are explained in the following.
-
-> Note: The following upgrading instructions are cumulative. That is, if you want to upgrade from a version A to version
-> C and there is a version B between A and C, you need to follow the instructions for both A and B.
+> **Important!** The following upgrading instructions are cumulative. That is, if you want
+> to upgrade from version A to version C and there is version B between A and C, you need
+> to following the instructions for both A and B.
## Upgrade from 1.2.0
From a7217c82ba4a2e39d4773b4fa6ef040fcb0f55b0 Mon Sep 17 00:00:00 2001
From: Sergei Predvoditelev
Date: Mon, 26 Aug 2024 21:21:10 +0300
Subject: [PATCH 08/60] Fix readme badges (#133)
---
README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 2888d73..5f05113 100644
--- a/README.md
+++ b/README.md
@@ -6,9 +6,9 @@
-[](https://packagist.org/packages/yiisoft/strings)
-[](https://packagist.org/packages/yiisoft/strings)
-[](https://github.com/yiisoft/strings/actions)
+[](https://packagist.org/packages/yiisoft/strings)
+[](https://packagist.org/packages/yiisoft/strings)
+[](https://github.com/yiisoft/strings/actions/workflows/build.yml)
[](https://scrutinizer-ci.com/g/yiisoft/strings/?branch=master)
[](https://scrutinizer-ci.com/g/yiisoft/strings/?branch=master)
[](https://dashboard.stryker-mutator.io/reports/github.com/yiisoft/strings/master)
From e7a06ae40a8bb9499384b3fd9c2496562fe1d3b1 Mon Sep 17 00:00:00 2001
From: Sergei Predvoditelev
Date: Mon, 26 Aug 2024 21:27:14 +0300
Subject: [PATCH 09/60] Remove scrutinizer (#134)
---
.gitattributes | 1 -
.scrutinizer.yml | 35 -----------------------------------
README.md | 3 +--
3 files changed, 1 insertion(+), 38 deletions(-)
delete mode 100644 .scrutinizer.yml
diff --git a/.gitattributes b/.gitattributes
index 5583f36..9f9e9af 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -26,7 +26,6 @@
/.editorconfig export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
-/.scrutinizer.yml export-ignore
/phpunit.xml.dist export-ignore
/tests export-ignore
/docs export-ignore
diff --git a/.scrutinizer.yml b/.scrutinizer.yml
deleted file mode 100644
index 61fb502..0000000
--- a/.scrutinizer.yml
+++ /dev/null
@@ -1,35 +0,0 @@
-checks:
- php: true
-
-filter:
- paths:
- - src/
-
-build:
- image: default-bionic
-
- environment:
- php:
- version: 8.0.18
- ini:
- xdebug.mode: coverage
-
- nodes:
- analysis:
- tests:
- override:
- - php-scrutinizer-run
-
- phpunit:
- dependencies:
- override:
- - composer self-update
- - composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi
-
- tests:
- override:
- - command: ./vendor/bin/phpunit --coverage-clover ./coverage.xml
- on_node: 1
- coverage:
- file: coverage.xml
- format: php-clover
diff --git a/README.md b/README.md
index 5f05113..aafb961 100644
--- a/README.md
+++ b/README.md
@@ -9,8 +9,7 @@
[](https://packagist.org/packages/yiisoft/strings)
[](https://packagist.org/packages/yiisoft/strings)
[](https://github.com/yiisoft/strings/actions/workflows/build.yml)
-[](https://scrutinizer-ci.com/g/yiisoft/strings/?branch=master)
-[](https://scrutinizer-ci.com/g/yiisoft/strings/?branch=master)
+[](https://codecov.io/gh/yiisoft/strings)
[](https://dashboard.stryker-mutator.io/reports/github.com/yiisoft/strings/master)
[](https://github.com/yiisoft/strings/actions?query=workflow%3A%22static+analysis%22)
[](https://shepherd.dev/github/yiisoft/strings)
From fccb0ef70340630546af7f6156eaffad1c3a5d5b Mon Sep 17 00:00:00 2001
From: Sergei Predvoditelev
Date: Mon, 26 Aug 2024 21:30:23 +0300
Subject: [PATCH 10/60] Fix psalm errors (#135)
---
composer.json | 2 +-
src/AbstractCombinedRegexp.php | 7 +++++++
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/composer.json b/composer.json
index 1a29832..a53433c 100644
--- a/composer.json
+++ b/composer.json
@@ -37,7 +37,7 @@
"rector/rector": "^1.0.0",
"roave/infection-static-analysis-plugin": "^1.16",
"spatie/phpunit-watcher": "^1.23",
- "vimeo/psalm": "^4.30|^5.8"
+ "vimeo/psalm": "^4.30|^5.25"
},
"autoload": {
"psr-4": {
diff --git a/src/AbstractCombinedRegexp.php b/src/AbstractCombinedRegexp.php
index e40c91f..169172c 100644
--- a/src/AbstractCombinedRegexp.php
+++ b/src/AbstractCombinedRegexp.php
@@ -15,7 +15,14 @@
*/
abstract class AbstractCombinedRegexp
{
+ /**
+ * @psalm-suppress MissingClassConstType
+ */
public const REGEXP_DELIMITER = '/';
+
+ /**
+ * @psalm-suppress MissingClassConstType
+ */
public const QUOTE_REPLACER = '\\/';
/**
From 0e09941fe1de00c7521a9e04e689018c64d4cfdb Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 13 Dec 2024 12:51:00 +0300
Subject: [PATCH 11/60] Update rector/rector requirement from ^1.0.0 to ^2.0.3
(#136)
Co-authored-by: Sergei Predvoditelev
---
composer.json | 2 +-
rector.php | 2 --
2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/composer.json b/composer.json
index a53433c..e16642e 100644
--- a/composer.json
+++ b/composer.json
@@ -34,7 +34,7 @@
"require-dev": {
"maglnet/composer-require-checker": "^4.2",
"phpunit/phpunit": "^9.5",
- "rector/rector": "^1.0.0",
+ "rector/rector": "^2.0.3",
"roave/infection-static-analysis-plugin": "^1.16",
"spatie/phpunit-watcher": "^1.23",
"vimeo/psalm": "^4.30|^5.25"
diff --git a/rector.php b/rector.php
index c80d86e..f55daae 100644
--- a/rector.php
+++ b/rector.php
@@ -4,7 +4,6 @@
use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector;
use Rector\Config\RectorConfig;
-use Rector\Php73\Rector\FuncCall\JsonThrowOnErrorRector;
use Rector\Php74\Rector\Closure\ClosureToArrowFunctionRector;
use Rector\Set\ValueObject\LevelSetList;
@@ -24,6 +23,5 @@
$rectorConfig->skip([
ClosureToArrowFunctionRector::class,
- JsonThrowOnErrorRector::class,
]);
};
From 3230b19fb2c95777ee82b31088c64c6c23dd4a6f Mon Sep 17 00:00:00 2001
From: Sergei Predvoditelev
Date: Sun, 19 Jan 2025 11:22:39 +0300
Subject: [PATCH 12/60] Add `StringHelper::matchAnyRegex()` (#137)
---
CHANGELOG.md | 1 +
README.md | 1 +
src/StringHelper.php | 21 +++++++++++
tests/StringHelper/MatchAnyRegexTest.php | 46 ++++++++++++++++++++++++
4 files changed, 69 insertions(+)
create mode 100644 tests/StringHelper/MatchAnyRegexTest.php
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4bdbaf3..7f78b7b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
## 2.4.1 under development
+- New #137: Add `StringHelper::matchAnyRegex()` method as a facade for `CombinedRegexp` (@vjik)
- Enh #128: Add more specific psalm type for result of `StringHelper::base64UrlEncode()` method (@vjik)
## 2.4.0 December 22, 2023
diff --git a/README.md b/README.md
index aafb961..df04325 100644
--- a/README.md
+++ b/README.md
@@ -97,6 +97,7 @@ Overall the helper has the following method groups.
### Other
+- matchAnyRegex
- parsePath
- split
diff --git a/src/StringHelper.php b/src/StringHelper.php
index f395156..0bf4497 100644
--- a/src/StringHelper.php
+++ b/src/StringHelper.php
@@ -729,6 +729,27 @@ public static function findBetweenLast(string $string, string $start, ?string $e
return mb_substr($string, $startPos, $endPos - $startPos);
}
+ /**
+ * Checks if a given string matches any of the provided patterns.
+ *
+ * Note that patterns should be provided without delimiters on both sides. For example, `te(s|x)t`.
+ *
+ * @see https://www.php.net/manual/reference.pcre.pattern.syntax.php
+ * @see https://www.php.net/manual/reference.pcre.pattern.modifiers.php
+ *
+ * @param string $string The string to match against the patterns.
+ * @param string[] $patterns Regular expressions without delimiters on both sides.
+ * @param string $flags Flags to apply to all regular expressions.
+ */
+ public static function matchAnyRegex(string $string, array $patterns, string $flags = ''): bool
+ {
+ if (empty($patterns)) {
+ return false;
+ }
+
+ return (new CombinedRegexp($patterns, $flags))->matches($string);
+ }
+
/**
* Ensure the input string is a valid UTF-8 string.
*
diff --git a/tests/StringHelper/MatchAnyRegexTest.php b/tests/StringHelper/MatchAnyRegexTest.php
new file mode 100644
index 0000000..a7df6a3
--- /dev/null
+++ b/tests/StringHelper/MatchAnyRegexTest.php
@@ -0,0 +1,46 @@
+assertSame($expected, $result);
+ }
+
+ /**
+ * @testWith [true, "test"]
+ * [false, "hello"]
+ */
+ public function testWithoutFlags(bool $expected, string $string): void
+ {
+ $result = StringHelper::matchAnyRegex($string, ['te[sx]t', 'm(o|a)n']);
+ $this->assertSame($expected, $result);
+ }
+
+ public function testWithoutPatterns(): void
+ {
+ $result = StringHelper::matchAnyRegex('test', []);
+ $this->assertFalse($result);
+ }
+}
From 5063d70af8b59907261c66a57349ef2c20f5cf2e Mon Sep 17 00:00:00 2001
From: Sergei Predvoditelev
Date: Sun, 19 Jan 2025 13:44:53 +0000
Subject: [PATCH 13/60] Release version 2.5.0
---
CHANGELOG.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7f78b7b..2158561 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,9 @@
# Yii Strings Change Log
-## 2.4.1 under development
+## 2.5.0 January 19, 2025
- New #137: Add `StringHelper::matchAnyRegex()` method as a facade for `CombinedRegexp` (@vjik)
-- Enh #128: Add more specific psalm type for result of `StringHelper::base64UrlEncode()` method (@vjik)
+- Enh #128: Add more specific psalm type for result of `StringHelper::base64UrlEncode()` method (@vjik)
## 2.4.0 December 22, 2023
From ae45a5cf5c63d3c864d028456d4e3190fd1e9f71 Mon Sep 17 00:00:00 2001
From: Sergei Predvoditelev
Date: Sun, 19 Jan 2025 13:44:56 +0000
Subject: [PATCH 14/60] Prepare for next release
---
CHANGELOG.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2158561..d780445 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
# Yii Strings Change Log
+## 2.5.1 under development
+
+- no changes in this release.
+
## 2.5.0 January 19, 2025
- New #137: Add `StringHelper::matchAnyRegex()` method as a facade for `CombinedRegexp` (@vjik)
From 2bf12f1bd5eb8b67cd9cdddac1ba438808261d16 Mon Sep 17 00:00:00 2001
From: Sergei Predvoditelev
Date: Fri, 31 Jan 2025 15:41:46 +0300
Subject: [PATCH 15/60] Improve Rector CI action (#139)
---
.github/workflows/rector.yml | 3 ++-
composer.json | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml
index 35411d0..457772a 100644
--- a/.github/workflows/rector.yml
+++ b/.github/workflows/rector.yml
@@ -1,5 +1,5 @@
on:
- pull_request:
+ pull_request_target:
paths-ignore:
- 'docs/**'
- 'README.md'
@@ -17,6 +17,7 @@ jobs:
secrets:
token: ${{ secrets.YIISOFT_GITHUB_TOKEN }}
with:
+ repository: ${{ github.event.pull_request.head.repo.full_name }}
os: >-
['ubuntu-latest']
php: >-
diff --git a/composer.json b/composer.json
index e16642e..dbb37b5 100644
--- a/composer.json
+++ b/composer.json
@@ -34,7 +34,7 @@
"require-dev": {
"maglnet/composer-require-checker": "^4.2",
"phpunit/phpunit": "^9.5",
- "rector/rector": "^2.0.3",
+ "rector/rector": "^2.0.7",
"roave/infection-static-analysis-plugin": "^1.16",
"spatie/phpunit-watcher": "^1.23",
"vimeo/psalm": "^4.30|^5.25"
From eff507ea0c76d7e4fc08f547ec78df421e86dbc4 Mon Sep 17 00:00:00 2001
From: ferrum_fist
Date: Sun, 2 Feb 2025 20:55:30 +0300
Subject: [PATCH 16/60] Explicitly mark nullable parameters
---
CHANGELOG.md | 2 +-
src/StringHelper.php | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d780445..d3537f1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,7 @@
## 2.5.1 under development
-- no changes in this release.
+- Bug #138: Explicitly mark nullable parameters (@ferrumfist)
## 2.5.0 January 19, 2025
diff --git a/src/StringHelper.php b/src/StringHelper.php
index 0bf4497..fc1a1b2 100644
--- a/src/StringHelper.php
+++ b/src/StringHelper.php
@@ -67,7 +67,7 @@ public static function byteLength(string|null $input): int
*
* @see https://www.php.net/manual/en/function.substr.php
*/
- public static function byteSubstring(string $input, int $start, int $length = null): string
+ public static function byteSubstring(string $input, int $start, ?int $length = null): string
{
return mb_substr($input, $start, $length ?? mb_strlen($input, '8bit'), '8bit');
}
@@ -133,7 +133,7 @@ public static function directoryName(string $path): string
*
* @see https://php.net/manual/en/function.mb-substr.php
*/
- public static function substring(string $string, int $start, int $length = null, string $encoding = 'UTF-8'): string
+ public static function substring(string $string, int $start, ?int $length = null, string $encoding = 'UTF-8'): string
{
return mb_substr($string, $start, $length, $encoding);
}
From 4cdcc85b2f94b29a11da8bde579d645ef1cf4f46 Mon Sep 17 00:00:00 2001
From: Sergei Predvoditelev
Date: Thu, 6 Feb 2025 20:46:04 +0300
Subject: [PATCH 17/60] Bump minimal required PHP version to 8.1 (#140)
---
.github/workflows/build.yml | 2 +-
.../workflows/composer-require-checker.yml | 2 +-
.github/workflows/static.yml | 2 +-
.gitignore | 8 +-
CHANGELOG.md | 1 +
README.md | 2 +-
composer.json | 13 +-
phpunit.xml.dist | 28 ++--
rector.php | 25 ++--
src/CombinedRegexp.php | 4 +-
src/MemoizedCombinedRegexp.php | 2 +-
...php => AbstractCombinedRegexpTestCase.php} | 19 +--
tests/CombinedRegexpTest.php | 2 +-
tests/InflectorTest.php | 19 +--
tests/IsOneOfAssert.php | 2 +-
tests/MemoizedCombinedRegexpTest.php | 2 +-
tests/NumericHelperTest.php | 13 +-
tests/StringHelper/MatchAnyRegexTest.php | 5 +-
tests/StringHelperTest.php | 120 ++++++------------
tests/Support/StringableObject.php | 2 +-
tests/WildcardPatternTest.php | 25 ++--
21 files changed, 115 insertions(+), 183 deletions(-)
rename tests/{AbstractCombinedRegexpTest.php => AbstractCombinedRegexpTestCase.php} (95%)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index f980c7f..3feaeef 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -29,4 +29,4 @@ jobs:
os: >-
['ubuntu-latest', 'windows-latest']
php: >-
- ['8.0', '8.1', '8.2', '8.3']
+ ['8.1', '8.2', '8.3']
diff --git a/.github/workflows/composer-require-checker.yml b/.github/workflows/composer-require-checker.yml
index 5473ec9..a857bce 100644
--- a/.github/workflows/composer-require-checker.yml
+++ b/.github/workflows/composer-require-checker.yml
@@ -31,4 +31,4 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
- ['8.0', '8.1', '8.2', '8.3']
+ ['8.1', '8.2', '8.3']
diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml
index 8694d2d..e33eca8 100644
--- a/.github/workflows/static.yml
+++ b/.github/workflows/static.yml
@@ -29,4 +29,4 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
- ['8.0', '8.1', '8.2', '8.3']
+ ['8.1', '8.2', '8.3']
diff --git a/.gitignore b/.gitignore
index 56da114..5d023ef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,9 +23,7 @@ composer.phar
# Mac DS_Store Files
.DS_Store
-# phpunit itself is not needed
-phpunit.phar
-# local phpunit config
+# PHPUnit
+/phpunit.phar
/phpunit.xml
-# phpunit cache
-.phpunit.result.cache
+/.phpunit.cache
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d3537f1..4f201fc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
## 2.5.1 under development
- Bug #138: Explicitly mark nullable parameters (@ferrumfist)
+- Chg #140: Bump minimal required PHP version to 8.1 and minor refactoring (@vjik)
## 2.5.0 January 19, 2025
diff --git a/README.md b/README.md
index df04325..f9dad8f 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ The package provides:
## Requirements
-- PHP 8.0 or higher.
+- PHP 8.1 or higher.
- `mbstring` PHP extension.
## Installation
diff --git a/composer.json b/composer.json
index dbb37b5..a0d8b45 100644
--- a/composer.json
+++ b/composer.json
@@ -28,16 +28,16 @@
}
],
"require": {
- "php": "^8.0",
+ "php": "^8.1",
"ext-mbstring": "*"
},
"require-dev": {
- "maglnet/composer-require-checker": "^4.2",
- "phpunit/phpunit": "^9.5",
+ "maglnet/composer-require-checker": "^4.7.1",
+ "phpunit/phpunit": "^10.5.44",
"rector/rector": "^2.0.7",
- "roave/infection-static-analysis-plugin": "^1.16",
- "spatie/phpunit-watcher": "^1.23",
- "vimeo/psalm": "^4.30|^5.25"
+ "roave/infection-static-analysis-plugin": "^1.35",
+ "spatie/phpunit-watcher": "^1.24",
+ "vimeo/psalm": "^5.26.1"
},
"autoload": {
"psr-4": {
@@ -51,6 +51,7 @@
},
"config": {
"sort-packages": true,
+ "bump-after-update": "dev",
"allow-plugins": {
"infection/extension-installer": true,
"composer/package-versions-deprecated": true
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 14acb8c..98b0b95 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,18 +1,20 @@
-
-
+ colors="true"
+ displayDetailsOnPhpunitDeprecations="true"
+>
-
+
@@ -21,9 +23,9 @@
-
+
- ./src
+ ./src
-
+
diff --git a/rector.php b/rector.php
index f55daae..1f8a952 100644
--- a/rector.php
+++ b/rector.php
@@ -5,23 +5,18 @@
use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector;
use Rector\Config\RectorConfig;
use Rector\Php74\Rector\Closure\ClosureToArrowFunctionRector;
-use Rector\Set\ValueObject\LevelSetList;
+use Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector;
-return static function (RectorConfig $rectorConfig): void {
- $rectorConfig->paths([
+return RectorConfig::configure()
+ ->withPaths([
__DIR__ . '/src',
__DIR__ . '/tests',
- ]);
-
- // register a single rule
- $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class);
-
- // define sets of rules
- $rectorConfig->sets([
- LevelSetList::UP_TO_PHP_80,
- ]);
-
- $rectorConfig->skip([
+ ])
+ ->withPhpSets(php81: true)
+ ->withRules([
+ InlineConstructorDefaultToPropertyRector::class,
+ ])
+ ->withSkip([
ClosureToArrowFunctionRector::class,
+ NullToStrictStringFuncCallArgRector::class,
]);
-};
diff --git a/src/CombinedRegexp.php b/src/CombinedRegexp.php
index cba76d2..81fc72c 100644
--- a/src/CombinedRegexp.php
+++ b/src/CombinedRegexp.php
@@ -28,7 +28,7 @@ final class CombinedRegexp extends AbstractCombinedRegexp
/**
* @psalm-var non-empty-string
*/
- private string $compiledPattern;
+ private readonly string $compiledPattern;
/**
* @param string[] $patterns Regular expressions to combine.
@@ -36,7 +36,7 @@ final class CombinedRegexp extends AbstractCombinedRegexp
*/
public function __construct(
array $patterns,
- private string $flags = ''
+ private readonly string $flags = ''
) {
if (empty($patterns)) {
throw new InvalidArgumentException('At least one pattern should be specified.');
diff --git a/src/MemoizedCombinedRegexp.php b/src/MemoizedCombinedRegexp.php
index 1e49245..f366462 100644
--- a/src/MemoizedCombinedRegexp.php
+++ b/src/MemoizedCombinedRegexp.php
@@ -18,7 +18,7 @@ final class MemoizedCombinedRegexp extends AbstractCombinedRegexp
private array $results = [];
public function __construct(
- private AbstractCombinedRegexp $decorated,
+ private readonly AbstractCombinedRegexp $decorated,
) {
}
diff --git a/tests/AbstractCombinedRegexpTest.php b/tests/AbstractCombinedRegexpTestCase.php
similarity index 95%
rename from tests/AbstractCombinedRegexpTest.php
rename to tests/AbstractCombinedRegexpTestCase.php
index 178d075..bf06246 100644
--- a/tests/AbstractCombinedRegexpTest.php
+++ b/tests/AbstractCombinedRegexpTestCase.php
@@ -4,14 +4,13 @@
namespace Yiisoft\Strings\Tests;
+use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Yiisoft\Strings\AbstractCombinedRegexp;
-abstract class AbstractCombinedRegexpTest extends TestCase
+abstract class AbstractCombinedRegexpTestCase extends TestCase
{
- /**
- * @dataProvider dataMatchAny
- */
+ #[DataProvider('dataMatchAny')]
public function testMatchAny(array $patterns, string $string, bool $expectedResult): void
{
$regexp = $this->createCombinedRegexp($patterns);
@@ -106,9 +105,7 @@ public static function dataMatchAny(): iterable
];
}
- /**
- * @dataProvider dataMatchingPattern
- */
+ #[DataProvider('dataMatchingPattern')]
public function testMatchingPattern(array $patterns, string $string, string $expectedResult): void
{
$regexp = $this->createCombinedRegexp($patterns);
@@ -153,9 +150,7 @@ public static function dataMatchingPattern(): iterable
];
}
- /**
- * @dataProvider dataMatchingPatternPosition
- */
+ #[DataProvider('dataMatchingPatternPosition')]
public function testMatchingPatternPosition(array $patterns, string $string, int $expectedResult): void
{
$regexp = $this->createCombinedRegexp($patterns);
@@ -200,9 +195,7 @@ public static function dataMatchingPatternPosition(): iterable
];
}
- /**
- * @dataProvider dataMatchDifferentDelimiters
- */
+ #[DataProvider('dataMatchDifferentDelimiters')]
public function testMatchDifferentDelimiters(
array $patterns,
string $flags,
diff --git a/tests/CombinedRegexpTest.php b/tests/CombinedRegexpTest.php
index 7a59dd2..aa9a4ed 100644
--- a/tests/CombinedRegexpTest.php
+++ b/tests/CombinedRegexpTest.php
@@ -7,7 +7,7 @@
use Yiisoft\Strings\AbstractCombinedRegexp;
use Yiisoft\Strings\CombinedRegexp;
-final class CombinedRegexpTest extends AbstractCombinedRegexpTest
+final class CombinedRegexpTest extends AbstractCombinedRegexpTestCase
{
protected function createCombinedRegexp(array $patterns, string $flags = ''): AbstractCombinedRegexp
{
diff --git a/tests/InflectorTest.php b/tests/InflectorTest.php
index d068126..08b6e4e 100644
--- a/tests/InflectorTest.php
+++ b/tests/InflectorTest.php
@@ -4,6 +4,7 @@
namespace Yiisoft\Strings\Tests;
+use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Yiisoft\Strings\Inflector;
@@ -135,9 +136,7 @@ public function testToWords(): void
$this->assertEquals('і це дійсно так!', $inflector->toWords('ІЦеДійсноТак!'));
}
- /**
- * @dataProvider pascalCaseToIdProvider()
- */
+ #[DataProvider('pascalCaseToIdProvider')]
public function testPascalCaseToId(string $expectedResult, array $arguments): void
{
$inflector = new Inflector();
@@ -162,7 +161,7 @@ public function testToCamelCase(): void
$this->assertEquals('ひらがなHepimiz', $inflector->toCamelCase('ひらがな_hepimiz'));
}
- public function dataToSnakeCase(): array
+ public static function dataToSnakeCase(): array
{
return [
[['input' => 'userName'], 'user_name'],
@@ -176,9 +175,7 @@ public function dataToSnakeCase(): array
];
}
- /**
- * @dataProvider dataToSnakeCase
- */
+ #[DataProvider('dataToSnakeCase')]
public function testToSnakeCase(array $arguments, string $expectedOutput): void
{
$inflector = new Inflector();
@@ -193,7 +190,7 @@ public function testToTable(): void
$this->assertEquals('customer_tables', $inflector->classToTable('customerTable'));
}
- public function toSlugCommonsDataProvider(): array
+ public static function toSlugCommonsDataProvider(): array
{
return [
['', ''],
@@ -216,9 +213,7 @@ public function toSlugCommonsDataProvider(): array
];
}
- /**
- * @dataProvider toSlugCommonsDataProvider
- */
+ #[DataProvider('toSlugCommonsDataProvider')]
public function testToSlugCommons(string $input, string $expected, string $replacement = '-'): void
{
$inflector = new Inflector();
@@ -489,7 +484,7 @@ private function assertIsOneOf(mixed $actual, array $expected, $message = ''): v
self::assertThat($actual, new IsOneOfAssert($expected), $message);
}
- public function pascalCaseToIdProvider(): array
+ public static function pascalCaseToIdProvider(): array
{
return [
['photo\\album-controller', ['Photo\\AlbumController', '-', false]],
diff --git a/tests/IsOneOfAssert.php b/tests/IsOneOfAssert.php
index 700c598..429245a 100644
--- a/tests/IsOneOfAssert.php
+++ b/tests/IsOneOfAssert.php
@@ -11,7 +11,7 @@
*/
final class IsOneOfAssert extends Constraint
{
- public function __construct(private array $allowedValues)
+ public function __construct(private readonly array $allowedValues)
{
}
diff --git a/tests/MemoizedCombinedRegexpTest.php b/tests/MemoizedCombinedRegexpTest.php
index 138e385..e406f0e 100644
--- a/tests/MemoizedCombinedRegexpTest.php
+++ b/tests/MemoizedCombinedRegexpTest.php
@@ -8,7 +8,7 @@
use Yiisoft\Strings\CombinedRegexp;
use Yiisoft\Strings\MemoizedCombinedRegexp;
-final class MemoizedCombinedRegexpTest extends AbstractCombinedRegexpTest
+final class MemoizedCombinedRegexpTest extends AbstractCombinedRegexpTestCase
{
protected function createCombinedRegexp(array $patterns, string $flags = ''): AbstractCombinedRegexp
{
diff --git a/tests/NumericHelperTest.php b/tests/NumericHelperTest.php
index 2cc509e..5291a4e 100644
--- a/tests/NumericHelperTest.php
+++ b/tests/NumericHelperTest.php
@@ -4,6 +4,7 @@
namespace Yiisoft\Strings\Tests;
+use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Yiisoft\Strings\NumericHelper;
use Yiisoft\Strings\Tests\Support\StringableObject;
@@ -38,7 +39,7 @@ public function testToOrdinalWithIncorrectType(): void
NumericHelper::toOrdinal('bla-bla');
}
- public function dataNormalize(): array
+ public static function dataNormalize(): array
{
return [
'French' => ['4 294 967 295,000', '4294967295.000'],
@@ -54,9 +55,7 @@ public function dataNormalize(): array
];
}
- /**
- * @dataProvider dataNormalize
- */
+ #[DataProvider('dataNormalize')]
public function testNormalize(mixed $input, string $expected): void
{
$this->assertSame($expected, NumericHelper::normalize($input));
@@ -68,7 +67,7 @@ public function testNormalizeWithIncorrectType(): void
NumericHelper::normalize([]);
}
- public function dataIsInteger(): array
+ public static function dataIsInteger(): array
{
return [
[new \stdClass(), false],
@@ -83,9 +82,7 @@ public function dataIsInteger(): array
];
}
- /**
- * @dataProvider dataIsInteger
- */
+ #[DataProvider('dataIsInteger')]
public function testIsInteger(mixed $value, bool $expected): void
{
$this->assertSame($expected, NumericHelper::isInteger($value));
diff --git a/tests/StringHelper/MatchAnyRegexTest.php b/tests/StringHelper/MatchAnyRegexTest.php
index a7df6a3..5be92a3 100644
--- a/tests/StringHelper/MatchAnyRegexTest.php
+++ b/tests/StringHelper/MatchAnyRegexTest.php
@@ -4,6 +4,7 @@
namespace Yiisoft\Strings\Tests\StringHelper;
+use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Yiisoft\Strings\StringHelper;
@@ -19,9 +20,7 @@ public static function dataBase(): array
];
}
- /**
- * @dataProvider dataBase
- */
+ #[DataProvider('dataBase')]
public function testBase(bool $expected, string $string, array $patterns, string $flags = ''): void
{
$result = StringHelper::matchAnyRegex($string, $patterns, $flags);
diff --git a/tests/StringHelperTest.php b/tests/StringHelperTest.php
index 619a615..f6f65b1 100644
--- a/tests/StringHelperTest.php
+++ b/tests/StringHelperTest.php
@@ -5,6 +5,7 @@
namespace Yiisoft\Strings\Tests;
use InvalidArgumentException;
+use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Yiisoft\Strings\StringHelper;
@@ -118,11 +119,7 @@ public function testTruncateWords(): void
$this->assertEquals('это строка с неожиданными…', StringHelper::truncateWords(' это строка с неожиданными пробелами ', 4));
}
- /**
- * @dataProvider providerStartsWith
- *
- * @param string|null $with
- */
+ #[DataProvider('providerStartsWith')]
public function testStartsWith(bool $result, string $string, ?string $with): void
{
$this->assertSame($result, StringHelper::startsWith($string, $with));
@@ -131,7 +128,7 @@ public function testStartsWith(bool $result, string $string, ?string $with): voi
/**
* Rules that should work the same for case-sensitive and case-insensitive `startsWith()`.
*/
- public function providerStartsWith(): array
+ public static function providerStartsWith(): array
{
return [
// positive check
@@ -170,11 +167,7 @@ public function testStartsWithIgnoringCase(): void
$this->assertTrue(StringHelper::startsWithIgnoringCase('anything', null));
}
- /**
- * @dataProvider providerEndsWith
- *
- * @param string|null $with
- */
+ #[DataProvider('providerEndsWith')]
public function testEndsWith(bool $result, string $string, ?string $with): void
{
// case sensitive version check
@@ -184,7 +177,7 @@ public function testEndsWith(bool $result, string $string, ?string $with): void
/**
* Rules that should work the same for case-sensitive and case-insensitive `endsWith()`.
*/
- public function providerEndsWith(): array
+ public static function providerEndsWith(): array
{
return [
// positive check
@@ -231,21 +224,14 @@ public function testCountWords(): void
$this->assertEquals(1, StringHelper::countWords(' слово '));
}
- /**
- * @dataProvider base64UrlEncodedStringsProvider
- */
+ #[DataProvider('base64UrlEncodedStringsProvider')]
public function testBase64UrlEncode(string $input, string $base64UrlEncoded): void
{
$encoded = StringHelper::base64UrlEncode($input);
$this->assertEquals($base64UrlEncoded, $encoded);
}
- /**
- * @dataProvider base64UrlEncodedStringsProvider
- *
- * @param $output
- * @param $base64UrlEncoded
- */
+ #[DataProvider('base64UrlEncodedStringsProvider')]
public function testBase64UrlDecode($output, $base64UrlEncoded): void
{
$decoded = StringHelper::base64UrlDecode($base64UrlEncoded);
@@ -263,7 +249,7 @@ public static function base64UrlEncodedStringsProvider(): array
];
}
- public function uppercaseFirstCharacterProvider(): array
+ public static function uppercaseFirstCharacterProvider(): array
{
return [
['foo', 'Foo'],
@@ -274,15 +260,13 @@ public function uppercaseFirstCharacterProvider(): array
];
}
- /**
- * @dataProvider uppercaseFirstCharacterProvider
- */
+ #[DataProvider('uppercaseFirstCharacterProvider')]
public function testUppercaseFirstCharacter(string $string, string $expectedResult): void
{
$this->assertSame($expectedResult, StringHelper::uppercaseFirstCharacter($string));
}
- public function uppercaseFirstCharacterInEachWordProvider(): array
+ public static function uppercaseFirstCharacterInEachWordProvider(): array
{
return [
'Single word' => ['foo', 'Foo'],
@@ -293,9 +277,7 @@ public function uppercaseFirstCharacterInEachWordProvider(): array
];
}
- /**
- * @dataProvider uppercaseFirstCharacterInEachWordProvider
- */
+ #[DataProvider('uppercaseFirstCharacterInEachWordProvider')]
public function testUppercaseFirstCharacterInEachWord(string $string, string $expectedResult): void
{
$this->assertSame($expectedResult, StringHelper::uppercaseFirstCharacterInEachWord($string));
@@ -348,7 +330,7 @@ public function testLength(): void
/**
* @see https://github.com/php/php-src/blob/master/ext/standard/tests/strings/substr_replace.phpt
*/
- public function replaceSubstringProvider(): array
+ public static function replaceSubstringProvider(): array
{
return [
['trbala ', ['try this', 'bala ', 2]],
@@ -361,15 +343,13 @@ public function replaceSubstringProvider(): array
];
}
- /**
- * @dataProvider replaceSubstringProvider
- */
+ #[DataProvider('replaceSubstringProvider')]
public function testReplaceSubstring(string $expected, array $arguments): void
{
$this->assertSame($expected, StringHelper::replaceSubstring(...$arguments));
}
- public function dataSplit(): array
+ public static function dataSplit(): array
{
return [
['', []],
@@ -411,9 +391,7 @@ public function dataSplit(): array
];
}
- /**
- * @dataProvider dataSplit
- */
+ #[DataProvider('dataSplit')]
public function testSplit(string $string, array $expected): void
{
$this->assertSame($expected, StringHelper::split($string));
@@ -424,7 +402,7 @@ public function testSplitWithSeparator(): void
$this->assertSame(['A', 'B', 'C'], StringHelper::split(' A 2 B3C', '\d'));
}
- public function dataParsePath(): array
+ public static function dataParsePath(): array
{
return [
['key1.key2.key3', '.', '\\', false, ['key1', 'key2', 'key3']],
@@ -470,9 +448,7 @@ public function dataParsePath(): array
];
}
- /**
- * @dataProvider dataParsePath
- */
+ #[DataProvider('dataParsePath')]
public function testParsePath(
string $path,
string $delimiter,
@@ -508,7 +484,7 @@ public function testParsePathWithDelimiterEqualsEscapeCharacter(): void
StringHelper::parsePath('key1.key2.key3', '.', '.');
}
- public function dataInvariantTrim(): iterable
+ public static function dataInvariantTrim(): iterable
{
$base = 'Здесь我' . self::WS['nbsp'] . '-' . self::WS['thsp'] . 'Multibyte我' . self::WS['lsep'] . 'Строка 👍🏻';
@@ -526,7 +502,7 @@ public function dataInvariantTrim(): iterable
];
}
- public function dataTrim(): iterable
+ public static function dataTrim(): iterable
{
$base = 'Здесь我' . self::WS['nbsp'] . '-' . self::WS['thsp'] . 'Multibyte我' . self::WS['lsep'] . 'Строка 👍🏻';
@@ -552,7 +528,7 @@ public function dataTrim(): iterable
];
}
- public function dataLtrim(): iterable
+ public static function dataLtrim(): iterable
{
$base = 'Здесь我' . self::WS['nbsp'] . '-' . self::WS['thsp'] . 'Multibyte我' . self::WS['lsep'] . 'Строка 👍🏻';
@@ -578,7 +554,7 @@ public function dataLtrim(): iterable
];
}
- public function dataRtrim(): iterable
+ public static function dataRtrim(): iterable
{
$base = 'Здесь我' . self::WS['nbsp'] . '-' . self::WS['thsp'] . 'Multibyte我' . self::WS['lsep'] . 'Строка 👍🏻';
@@ -600,7 +576,7 @@ public function dataRtrim(): iterable
];
}
- public function dataTrimPattern(): iterable
+ public static function dataTrimPattern(): iterable
{
$base = 'Здесь我' . self::WS['nbsp'] . '-' . self::WS['thsp'] . 'Multibyte我' . self::WS['lsep'] . 'Строка 👍🏻';
@@ -641,7 +617,7 @@ public function dataTrimPattern(): iterable
];
}
- public function dataLtrimPattern(): iterable
+ public static function dataLtrimPattern(): iterable
{
$base = 'Здесь我' . self::WS['nbsp'] . '-' . self::WS['thsp'] . 'Multibyte我' . self::WS['lsep'] . 'Строка 👍🏻';
@@ -667,7 +643,7 @@ public function dataLtrimPattern(): iterable
];
}
- public function dataRtrimPattern(): iterable
+ public static function dataRtrimPattern(): iterable
{
$base = 'Здесь我' . self::WS['nbsp'] . '-' . self::WS['thsp'] . 'Multibyte我' . self::WS['lsep'] . 'Строка 👍🏻';
@@ -708,52 +684,40 @@ public function dataRtrimPattern(): iterable
];
}
- /**
- * @dataProvider dataInvariantTrim
- * @dataProvider dataTrim
- */
+ #[DataProvider('dataTrim')]
+ #[DataProvider('dataInvariantTrim')]
public function testTrim(string|array $string, string|array $expected): void
{
$this->assertSame($expected, StringHelper::trim($string));
}
- /**
- * @dataProvider dataInvariantTrim
- * @dataProvider dataLtrim
- */
+ #[DataProvider('dataLtrim')]
+ #[DataProvider('dataInvariantTrim')]
public function testLtrim(string|array $string, string|array $expected): void
{
$this->assertSame($expected, StringHelper::ltrim($string));
}
- /**
- * @dataProvider dataInvariantTrim
- * @dataProvider dataRtrim
- */
+ #[DataProvider('dataRtrim')]
+ #[DataProvider('dataInvariantTrim')]
public function testRtrim(string|array $string, string|array $expected): void
{
$this->assertSame($expected, StringHelper::rtrim($string));
}
- /**
- * @dataProvider dataTrimPattern
- */
+ #[DataProvider('dataTrimPattern')]
public function testTrimPattern(string|array $string, string $pattern, string|array $expected): void
{
$this->assertSame($expected, StringHelper::trim($string, $pattern));
}
- /**
- * @dataProvider dataLtrimPattern
- */
+ #[DataProvider('dataLtrimPattern')]
public function testLtrimPattern(string|array $string, string $pattern, string|array $expected): void
{
$this->assertSame($expected, StringHelper::ltrim($string, $pattern));
}
- /**
- * @dataProvider dataRtrimPattern
- */
+ #[DataProvider('dataRtrimPattern')]
public function testRtrimPattern(string|array $string, string $pattern, string|array $expected): void
{
$this->assertSame($expected, StringHelper::rtrim($string, $pattern));
@@ -766,15 +730,13 @@ public function testInvalidTrimPattern(): void
StringHelper::trim('string', "\xC3\x28");
}
- /**
- * @dataProvider dataProviderFindBetween
- */
+ #[DataProvider('dataProviderFindBetween')]
public function testFindBetween(string $string, string $start, ?string $end, ?string $expectedResult): void
{
$this->assertSame($expectedResult, StringHelper::findBetween($string, $start, $end));
}
- public function dataProviderFindBetween(): array
+ public static function dataProviderFindBetween(): array
{
return [
['hello world hello', ' hello', ' world', null], // end before start
@@ -794,15 +756,13 @@ public function dataProviderFindBetween(): array
];
}
- /**
- * @dataProvider dataProviderFindBetweenFirst
- */
+ #[DataProvider('dataProviderFindBetweenFirst')]
public function testFindBetweenFirst(string $string, string $start, ?string $end, ?string $expectedResult): void
{
$this->assertSame($expectedResult, StringHelper::findBetweenFirst($string, $start, $end));
}
- public function dataProviderFindBetweenFirst(): array
+ public static function dataProviderFindBetweenFirst(): array
{
return [
['[a][b][c]', '[', ']', 'a'], // normal case
@@ -824,15 +784,13 @@ public function dataProviderFindBetweenFirst(): array
];
}
- /**
- * @dataProvider dataProviderFindBetweenLast
- */
+ #[DataProvider('dataProviderFindBetweenLast')]
public function testFindBetweenLast(string $string, string $start, ?string $end, ?string $expectedResult): void
{
$this->assertSame($expectedResult, StringHelper::findBetweenLast($string, $start, $end));
}
- public function dataProviderFindBetweenLast(): array
+ public static function dataProviderFindBetweenLast(): array
{
return [
['[a][b][c]', '[', ']', 'c'], // normal case
diff --git a/tests/Support/StringableObject.php b/tests/Support/StringableObject.php
index e4d1614..5c83bb0 100644
--- a/tests/Support/StringableObject.php
+++ b/tests/Support/StringableObject.php
@@ -9,7 +9,7 @@
final class StringableObject implements Stringable
{
public function __construct(
- private string $string
+ private readonly string $string
) {
}
diff --git a/tests/WildcardPatternTest.php b/tests/WildcardPatternTest.php
index acee2ac..99b9aee 100644
--- a/tests/WildcardPatternTest.php
+++ b/tests/WildcardPatternTest.php
@@ -4,6 +4,7 @@
namespace Yiisoft\Strings\Tests;
+use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Yiisoft\Strings\WildcardPattern;
@@ -14,7 +15,7 @@ final class WildcardPatternTest extends TestCase
*
* @return array test data.
*/
- public function dataProviderMatchWildcard(): array
+ public static function dataProviderMatchWildcard(): array
{
return [
// *
@@ -93,9 +94,7 @@ public function dataProviderMatchWildcard(): array
];
}
- /**
- * @dataProvider dataProviderMatchWildcard
- */
+ #[DataProvider('dataProviderMatchWildcard')]
public function testMatchWildcard(string $pattern, string $string, bool $expectedResult, array $options = []): void
{
$wildcardPattern = $this->getWildcardPattern($pattern, $options);
@@ -126,15 +125,13 @@ public function testImmutability(): void
$this->assertNotSame($original, $original->ignoreCase());
}
- /**
- * @dataProvider isDynamicDataProvider
- */
+ #[DataProvider('isDynamicDataProvider')]
public function testIsDynamic(string $pattern, bool $expected): void
{
$this->assertSame($expected, WildcardPattern::isDynamic($pattern));
}
- public function isDynamicDataProvider(): array
+ public static function isDynamicDataProvider(): array
{
return [
'not-dynamic' => ['just-some-string', false],
@@ -145,7 +142,7 @@ public function isDynamicDataProvider(): array
];
}
- public function customDelimitersProvider(): array
+ public static function customDelimitersProvider(): array
{
return [
'empty' => ['begin*end', 'begin/end', [], true],
@@ -154,16 +151,14 @@ public function customDelimitersProvider(): array
];
}
- /**
- * @dataProvider customDelimitersProvider
- */
+ #[DataProvider('customDelimitersProvider')]
public function testCustomDelimiters(string $pattern, string $string, array $delimiters, bool $expected): void
{
$wildcardPattern = $wildcardPattern = new WildcardPattern($pattern, $delimiters);
$this->assertSame($expected, $wildcardPattern->match($string));
}
- public function quoteDataProvider(): array
+ public static function quoteDataProvider(): array
{
return [
'no-pattern' => ['test', 'test'],
@@ -171,9 +166,7 @@ public function quoteDataProvider(): array
];
}
- /**
- * @dataProvider quoteDataProvider
- */
+ #[DataProvider('quoteDataProvider')]
public function testQuote(string $string, string $expected): void
{
$this->assertSame($expected, WildcardPattern::quote($string));
From 4a1623d9d2789ae82162fa915a9ae13fd10f91ed Mon Sep 17 00:00:00 2001
From: Sergei Predvoditelev
Date: Fri, 7 Feb 2025 14:06:56 +0300
Subject: [PATCH 18/60] Fix `StringHelper::parsePath()` for empty string (#141)
---
CHANGELOG.md | 1 +
src/StringHelper.php | 4 ++--
tests/StringHelperTest.php | 4 ++--
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4f201fc..ebb3d80 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
- Bug #138: Explicitly mark nullable parameters (@ferrumfist)
- Chg #140: Bump minimal required PHP version to 8.1 and minor refactoring (@vjik)
+- Bug #141: `StringHelper::parsePath()` for empty string returns path `['']` instead of `[]` before (@vjik)
## 2.5.0 January 19, 2025
diff --git a/src/StringHelper.php b/src/StringHelper.php
index fc1a1b2..b9b2293 100644
--- a/src/StringHelper.php
+++ b/src/StringHelper.php
@@ -489,7 +489,7 @@ public static function split(string $string, string $separator = '\R'): array
*
* @return string[]
*
- * @psalm-return list
+ * @psalm-return non-empty-list
*/
public static function parsePath(
string $path,
@@ -510,7 +510,7 @@ public static function parsePath(
}
if ($path === '') {
- return [];
+ return [''];
}
if (!str_contains($path, $delimiter)) {
diff --git a/tests/StringHelperTest.php b/tests/StringHelperTest.php
index f6f65b1..303392d 100644
--- a/tests/StringHelperTest.php
+++ b/tests/StringHelperTest.php
@@ -443,8 +443,8 @@ public static function dataParsePath(): array
['.key1.key2', '.', '\\', false, ['', 'key1', 'key2']],
['~key1~key2', '~', '\\', false, ['', 'key1', 'key2']],
- ['', '.', '\\', false, []],
- ['', '.', '\\', true, []],
+ ['', '.', '\\', false, ['']],
+ ['', '.', '\\', true, ['']],
];
}
From 59deb2e3003341f97110d5d52bd6fb470adf0db5 Mon Sep 17 00:00:00 2001
From: Sergei Predvoditelev
Date: Fri, 7 Feb 2025 16:59:08 +0300
Subject: [PATCH 19/60] Check string on a valid UTF-8 in `StringHelper` trim
methods (#142)
---
CHANGELOG.md | 1 +
src/StringHelper.php | 37 ++++++++++++++++++++++++++++++--
tests/StringHelperTest.php | 44 +++++++++++++++++++++++++++++++++++++-
3 files changed, 79 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ebb3d80..05992ee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
- Bug #138: Explicitly mark nullable parameters (@ferrumfist)
- Chg #140: Bump minimal required PHP version to 8.1 and minor refactoring (@vjik)
+- Bug #142: Check string on a valid UTF-8 in `StringHelper` methods: `trim()`, `ltrim()` and `rtrim()` (@vjik)
- Bug #141: `StringHelper::parsePath()` for empty string returns path `['']` instead of `[]` before (@vjik)
## 2.5.0 January 19, 2025
diff --git a/src/StringHelper.php b/src/StringHelper.php
index b9b2293..70c5f8b 100644
--- a/src/StringHelper.php
+++ b/src/StringHelper.php
@@ -580,8 +580,13 @@ public static function parsePath(
*/
public static function trim(string|array $string, string $pattern = self::DEFAULT_WHITESPACE_PATTERN): string|array
{
+ self::ensureUtf8String($string);
self::ensureUtf8Pattern($pattern);
+ /**
+ * @var string|string[] `$string` is correct UTF-8 string and `$pattern` is correct (it should be passed
+ * already prepared), so `preg_replace` never returns `null`.
+ */
return preg_replace("#^[$pattern]+|[$pattern]+$#uD", '', $string);
}
@@ -603,8 +608,13 @@ public static function trim(string|array $string, string $pattern = self::DEFAUL
*/
public static function ltrim(string|array $string, string $pattern = self::DEFAULT_WHITESPACE_PATTERN): string|array
{
+ self::ensureUtf8String($string);
self::ensureUtf8Pattern($pattern);
+ /**
+ * @var string|string[] `$string` is correct UTF-8 string and `$pattern` is correct (it should be passed
+ * already prepared), so `preg_replace` never returns `null`.
+ */
return preg_replace("#^[$pattern]+#u", '', $string);
}
@@ -626,8 +636,13 @@ public static function ltrim(string|array $string, string $pattern = self::DEFAU
*/
public static function rtrim(string|array $string, string $pattern = self::DEFAULT_WHITESPACE_PATTERN): string|array
{
+ self::ensureUtf8String($string);
self::ensureUtf8Pattern($pattern);
+ /**
+ * @var string|string[] `$string` is correct UTF-8 string and `$pattern` is correct (it should be passed
+ * already prepared), so `preg_replace` never returns `null`.
+ */
return preg_replace("#[$pattern]+$#uD", '', $string);
}
@@ -751,9 +766,9 @@ public static function matchAnyRegex(string $string, array $patterns, string $fl
}
/**
- * Ensure the input string is a valid UTF-8 string.
+ * Ensure the pattern is a valid UTF-8 string.
*
- * @param string $pattern The input string.
+ * @param string $pattern The pattern.
*
* @throws InvalidArgumentException
*/
@@ -763,4 +778,22 @@ private static function ensureUtf8Pattern(string $pattern): void
throw new InvalidArgumentException('Pattern is not a valid UTF-8 string.');
}
}
+
+ /**
+ * Ensure the string is a valid UTF-8 string.
+ *
+ * @param array|string $string The string.
+ *
+ * @throws InvalidArgumentException
+ *
+ * @psalm-param string|string[] $string
+ */
+ private static function ensureUtf8String(string|array $string): void
+ {
+ foreach ((array) $string as $s) {
+ if (!preg_match('##u', $s)) {
+ throw new InvalidArgumentException('String is not a valid UTF-8 string.');
+ }
+ }
+ }
}
diff --git a/tests/StringHelperTest.php b/tests/StringHelperTest.php
index 303392d..89bd206 100644
--- a/tests/StringHelperTest.php
+++ b/tests/StringHelperTest.php
@@ -6,6 +6,7 @@
use InvalidArgumentException;
use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\TestWith;
use PHPUnit\Framework\TestCase;
use Yiisoft\Strings\StringHelper;
@@ -726,10 +727,51 @@ public function testRtrimPattern(string|array $string, string $pattern, string|a
public function testInvalidTrimPattern(): void
{
$this->expectException(InvalidArgumentException::class);
-
+ $this->expectExceptionMessage('Pattern is not a valid UTF-8 string.');
StringHelper::trim('string', "\xC3\x28");
}
+ #[TestWith(["abc\xFF"])]
+ #[TestWith([['hello', "abc\xFF"]])]
+ public function testInvalidTrimString(string|array $string): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('String is not a valid UTF-8 string.');
+ StringHelper::trim($string);
+ }
+
+ public function testInvalidLtrimPattern(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Pattern is not a valid UTF-8 string.');
+ StringHelper::ltrim('string', "\xC3\x28");
+ }
+
+ #[TestWith(["abc\xFF"])]
+ #[TestWith([['hello', "abc\xFF"]])]
+ public function testInvalidLtrimString(string|array $string): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('String is not a valid UTF-8 string.');
+ StringHelper::ltrim($string);
+ }
+
+ public function testInvalidRtrimPattern(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Pattern is not a valid UTF-8 string.');
+ StringHelper::ltrim('string', "\xC3\x28");
+ }
+
+ #[TestWith(["abc\xFF"])]
+ #[TestWith([['hello', "abc\xFF"]])]
+ public function testInvalidRtrimString(string|array $string): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('String is not a valid UTF-8 string.');
+ StringHelper::rtrim($string);
+ }
+
#[DataProvider('dataProviderFindBetween')]
public function testFindBetween(string $string, string $start, ?string $end, ?string $expectedResult): void
{
From 8accf0820371dfd0a92dd4bfb054ef63ca6ad695 Mon Sep 17 00:00:00 2001
From: Sergei Predvoditelev
Date: Sun, 9 Feb 2025 18:26:28 +0300
Subject: [PATCH 20/60] Add PHP 8.4 (#143)
Co-authored-by: Sergei Tigrov
---
.github/workflows/build.yml | 2 +-
.../workflows/composer-require-checker.yml | 2 +-
.github/workflows/mutation.yml | 2 +-
.github/workflows/rector.yml | 2 +-
.github/workflows/static.yml | 2 +-
CHANGELOG.md | 1 +
composer.json | 8 +-
src/Inflector.php | 80 +++++++++++++------
src/NumericHelper.php | 7 +-
src/StringHelper.php | 18 ++++-
src/WildcardPattern.php | 2 +
11 files changed, 89 insertions(+), 37 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 3feaeef..54dfa76 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -29,4 +29,4 @@ jobs:
os: >-
['ubuntu-latest', 'windows-latest']
php: >-
- ['8.1', '8.2', '8.3']
+ ['8.1', '8.2', '8.3', '8.4']
diff --git a/.github/workflows/composer-require-checker.yml b/.github/workflows/composer-require-checker.yml
index a857bce..a93390b 100644
--- a/.github/workflows/composer-require-checker.yml
+++ b/.github/workflows/composer-require-checker.yml
@@ -31,4 +31,4 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
- ['8.1', '8.2', '8.3']
+ ['8.1', '8.2', '8.3', '8.4']
diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml
index 8150499..a56769e 100644
--- a/.github/workflows/mutation.yml
+++ b/.github/workflows/mutation.yml
@@ -27,6 +27,6 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
- ['8.2']
+ ['8.4']
secrets:
STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }}
diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml
index 457772a..5d6931d 100644
--- a/.github/workflows/rector.yml
+++ b/.github/workflows/rector.yml
@@ -21,4 +21,4 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
- ['8.3']
+ ['8.4']
diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml
index e33eca8..d03874d 100644
--- a/.github/workflows/static.yml
+++ b/.github/workflows/static.yml
@@ -29,4 +29,4 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
- ['8.1', '8.2', '8.3']
+ ['8.1', '8.2', '8.3', '8.4']
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 05992ee..9664018 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
- Bug #138: Explicitly mark nullable parameters (@ferrumfist)
- Chg #140: Bump minimal required PHP version to 8.1 and minor refactoring (@vjik)
+- Chg #143: Change PHP constraint in `composer.json` to `~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0` (@vjik)
- Bug #142: Check string on a valid UTF-8 in `StringHelper` methods: `trim()`, `ltrim()` and `rtrim()` (@vjik)
- Bug #141: `StringHelper::parsePath()` for empty string returns path `['']` instead of `[]` before (@vjik)
diff --git a/composer.json b/composer.json
index a0d8b45..8141195 100644
--- a/composer.json
+++ b/composer.json
@@ -28,16 +28,16 @@
}
],
"require": {
- "php": "^8.1",
+ "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0",
"ext-mbstring": "*"
},
"require-dev": {
"maglnet/composer-require-checker": "^4.7.1",
- "phpunit/phpunit": "^10.5.44",
- "rector/rector": "^2.0.7",
+ "phpunit/phpunit": "^10.5.45",
+ "rector/rector": "^2.0.8",
"roave/infection-static-analysis-plugin": "^1.35",
"spatie/phpunit-watcher": "^1.24",
- "vimeo/psalm": "^5.26.1"
+ "vimeo/psalm": "^5.26.1|^6.4.1"
},
"autoload": {
"psr-4": {
diff --git a/src/Inflector.php b/src/Inflector.php
index 521fc26..ef15aea 100644
--- a/src/Inflector.php
+++ b/src/Inflector.php
@@ -435,6 +435,7 @@ public function toPlural(string $input): string
}
foreach ($this->pluralizeRules as $rule => $replacement) {
if (preg_match($rule, $input)) {
+ /** @var string `$rule` and `$replacement` always correct, so `preg_replace` always returns string */
return preg_replace($rule, $replacement, $input);
}
}
@@ -459,6 +460,7 @@ public function toSingular(string $input): string
foreach ($this->singularizeRules as $rule => $replacement) {
if (preg_match($rule, $input)) {
+ /** @var string `$rule` and `$replacement` always correct, so `preg_replace` always returns string */
return preg_replace($rule, $replacement, $input);
}
}
@@ -488,17 +490,21 @@ public function toSentence(string $input, bool $uppercaseAll = false): string
* Converts a string into space-separated words.
* For example, 'PostTag' will be converted to 'Post Tag'.
*
- * @param string $input The string to be converted.
+ * @param string $input The valid UTF-8 string to be converted.
*
* @return string The resulting words.
*/
public function toWords(string $input): string
{
- return mb_strtolower(trim(str_replace([
- '-',
- '_',
- '.',
- ], ' ', preg_replace('/(?pascalCaseToId(preg_replace('/[^\pL\pN]+/u', '_', $input), '_', $strict);
+ /**
+ * @var string $input We assume that `$input` is valid UTF-8 string, so `preg_replace()` never returns `false`.
+ */
+ $input = preg_replace('/[^\pL\pN]+/u', '_', $input);
+ return $this->pascalCaseToId($input, '_', $strict);
}
/**
@@ -635,8 +655,8 @@ public function tableToClass(string $tableName): string
* and removes the rest. You may customize characters map via $transliteration property
* of the helper.
*
- * @param string $input An arbitrary string to convert.
- * @param string $replacement The replacement to use for spaces.
+ * @param string $input An arbitrary valid UTF-8 string to convert.
+ * @param string $replacement The replacement to use for spaces. It must be valid UTF-8 string.
* @param bool $lowercase whether to return the string in lowercase or not. Defaults to `true`.
*
* @return string The converted string.
@@ -644,9 +664,21 @@ public function tableToClass(string $tableName): string
public function toSlug(string $input, string $replacement = '-', bool $lowercase = true): string
{
$quotedReplacement = preg_quote($replacement, '/');
- // replace all non words character
+
+ /**
+ * Replace all non-words character
+ *
+ * @var string $input We assume that `$input` and `$replacement` are valid UTF-8 strings, so `preg_replace()`
+ * never returns `false`.
+ */
$input = preg_replace('/[^a-zA-Z0-9]+/u', $replacement, $this->toTransliterated($input));
- // remove first and last replacements
+
+ /**
+ * Remove first and last replacements
+ *
+ * @var string $input We assume that `$input` and `$quotedReplacement` are valid UTF-8 strings, so
+ * `preg_replace()` never returns `false`.
+ */
$input = preg_replace(
"/^(?:$quotedReplacement)+|(?:$quotedReplacement)+$/u" . ($lowercase ? 'i' : ''),
'',
@@ -665,12 +697,10 @@ public function toSlug(string $input, string $replacement = '-', bool $lowercase
*
* @noinspection PhpComposerExtensionStubsInspection
*
- * @param string $input Input string.
+ * @param string $input Input string. It must be valid UTF-8 string.
* @param string|Transliterator|null $transliterator either a {@see \Transliterator} or a string
* from which a {@see \Transliterator} can be built. If null, value set with {@see withTransliterator()}
* or {@see TRANSLITERATE_LOOSE} is used.
- *
- * @return string
*/
public function toTransliterated(string $input, $transliterator = null): string
{
@@ -679,7 +709,11 @@ public function toTransliterated(string $input, $transliterator = null): string
$transliterator = $this->transliterator;
}
- /* @noinspection PhpComposerExtensionStubsInspection */
+ /**
+ * @noinspection PhpComposerExtensionStubsInspection
+ * @var string We assume that `$input` are valid UTF-8 strings and `$transliterator` is valid, so
+ * `preg_replace()` never returns `false`.
+ */
return transliterator_transliterate($transliterator, $input);
}
diff --git a/src/NumericHelper.php b/src/NumericHelper.php
index 9a665cd..5f99ef3 100644
--- a/src/NumericHelper.php
+++ b/src/NumericHelper.php
@@ -53,7 +53,7 @@ public static function toOrdinal(mixed $value): string
/**
* Returns string representation of a number value without thousands separators and with dot as decimal separator.
*
- * @param bool|float|int|string|Stringable $value
+ * @param bool|float|int|string|Stringable $value String in `string` or `Stringable` must be valid UTF-8 string.
*
* @throws InvalidArgumentException if value is not scalar.
*/
@@ -69,8 +69,11 @@ public static function normalize(mixed $value): string
return $value ? '1' : '0';
}
- $value = str_replace([' ', ','], ['', '.'], (string)$value);
+ $value = str_replace([' ', ','], ['', '.'], (string) $value);
+ /**
+ * @var string We assume that `$value` is valid UTF-8 string, so `preg_replace()` never returns `false`.
+ */
return preg_replace('/\.(?=.*\.)/', '', $value);
}
diff --git a/src/StringHelper.php b/src/StringHelper.php
index 70c5f8b..75b7c92 100644
--- a/src/StringHelper.php
+++ b/src/StringHelper.php
@@ -410,13 +410,16 @@ public static function uppercaseFirstCharacter(string $string, string $encoding
/**
* Uppercase the first character of each word in a string.
*
- * @param string $string The string to be processed.
+ * @param string $string The valid UTF-8 string to be processed.
* @param string $encoding The encoding to use, defaults to "UTF-8".
*
* @see https://php.net/manual/en/function.ucwords.php
*/
public static function uppercaseFirstCharacterInEachWord(string $string, string $encoding = 'UTF-8'): string
{
+ /**
+ * @var array $words We assume that `$string` is valid UTF-8 string, so `preg_split()` never returns `false`.
+ */
$words = preg_split('/\s/u', $string, -1, PREG_SPLIT_NO_EMPTY);
$wordsWithUppercaseFirstCharacter = array_map(
@@ -466,13 +469,22 @@ public static function base64UrlDecode(string $input): string
* Split a string to array with non-empty lines.
* Whitespace from the beginning and end of a each line will be stripped.
*
- * @param string $string The input string.
+ * @param string $string The input string. It must be valid UTF-8 string.
* @param string $separator The boundary string. It is a part of regular expression
- * so should be taken into account or properly escaped with {@see preg_quote()}.
+ * so should be taken into account or properly escaped with {@see preg_quote()}. It must be valid UTF-8 string.
*/
public static function split(string $string, string $separator = '\R'): array
{
+ /**
+ * @var string $string We assume that `$string` is valid UTF-8 string, so `preg_replace()` never returns
+ * `false`.
+ */
$string = preg_replace('(^\s*|\s*$)', '', $string);
+
+ /**
+ * @var array We assume that $separator is prepared by `preg_quote()` and $string is valid UTF-8 string,
+ * so `preg_split()` never returns `false`.
+ */
return preg_split('~\s*' . $separator . '\s*~u', $string, -1, PREG_SPLIT_NO_EMPTY);
}
diff --git a/src/WildcardPattern.php b/src/WildcardPattern.php
index 95e5206..f5b0e27 100644
--- a/src/WildcardPattern.php
+++ b/src/WildcardPattern.php
@@ -80,6 +80,7 @@ public function ignoreCase(bool $flag = true): self
*/
public static function isDynamic(string $pattern): bool
{
+ /** @var string $pattern `$rule` and `$replacement` always correct, so `preg_replace` always returns string */
$pattern = preg_replace('/\\\\./', '', $pattern);
return preg_match('/[*{?\[]/', $pattern) === 1;
}
@@ -93,6 +94,7 @@ public static function isDynamic(string $pattern): bool
*/
public static function quote(string $string): string
{
+ /** @var string `$rule` and `$replacement` always correct, so `preg_replace` always returns string */
return preg_replace('#([\\\\?*\\[\\]])#', '\\\\$1', $string);
}
From eeb087d96ab8ae35c5b17daafe5d8ad9522880ab Mon Sep 17 00:00:00 2001
From: Sergei Predvoditelev
Date: Sun, 9 Feb 2025 16:30:52 +0000
Subject: [PATCH 21/60] Release version 2.6.0
---
CHANGELOG.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9664018..f4d74ff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,12 +1,12 @@
# Yii Strings Change Log
-## 2.5.1 under development
+## 2.6.0 February 09, 2025
-- Bug #138: Explicitly mark nullable parameters (@ferrumfist)
- Chg #140: Bump minimal required PHP version to 8.1 and minor refactoring (@vjik)
- Chg #143: Change PHP constraint in `composer.json` to `~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0` (@vjik)
-- Bug #142: Check string on a valid UTF-8 in `StringHelper` methods: `trim()`, `ltrim()` and `rtrim()` (@vjik)
+- Bug #138: Explicitly mark nullable parameters (@ferrumfist)
- Bug #141: `StringHelper::parsePath()` for empty string returns path `['']` instead of `[]` before (@vjik)
+- Bug #142: Check string on a valid UTF-8 in `StringHelper` methods: `trim()`, `ltrim()` and `rtrim()` (@vjik)
## 2.5.0 January 19, 2025
From 220caa78ad2da6a79a5b9710cb5a02b7ded358bc Mon Sep 17 00:00:00 2001
From: Sergei Predvoditelev
Date: Sun, 9 Feb 2025 16:30:54 +0000
Subject: [PATCH 22/60] Prepare for next release
---
CHANGELOG.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f4d74ff..bb63c4d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
# Yii Strings Change Log
+## 2.6.1 under development
+
+- no changes in this release.
+
## 2.6.0 February 09, 2025
- Chg #140: Bump minimal required PHP version to 8.1 and minor refactoring (@vjik)
From 284f7a7e8372fb2157c9e201bf25adb28836ac2d Mon Sep 17 00:00:00 2001
From: Sergei Predvoditelev
Date: Fri, 25 Jul 2025 15:47:50 +0300
Subject: [PATCH 23/60] Fix psalm errors + Update dev deps (#145)
---
composer.json | 9 ++++-----
psalm.xml | 2 ++
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/composer.json b/composer.json
index 8141195..80e222e 100644
--- a/composer.json
+++ b/composer.json
@@ -28,16 +28,16 @@
}
],
"require": {
- "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0",
+ "php": "8.1 - 8.4",
"ext-mbstring": "*"
},
"require-dev": {
"maglnet/composer-require-checker": "^4.7.1",
- "phpunit/phpunit": "^10.5.45",
- "rector/rector": "^2.0.8",
+ "phpunit/phpunit": "^10.5.48",
+ "rector/rector": "^2.1.2",
"roave/infection-static-analysis-plugin": "^1.35",
"spatie/phpunit-watcher": "^1.24",
- "vimeo/psalm": "^5.26.1|^6.4.1"
+ "vimeo/psalm": "^5.26.1 || ^6.13"
},
"autoload": {
"psr-4": {
@@ -51,7 +51,6 @@
},
"config": {
"sort-packages": true,
- "bump-after-update": "dev",
"allow-plugins": {
"infection/extension-installer": true,
"composer/package-versions-deprecated": true
diff --git a/psalm.xml b/psalm.xml
index d091d59..77a366b 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -3,6 +3,8 @@
errorLevel="1"
findUnusedBaselineEntry="true"
findUnusedCode="false"
+ ensureOverrideAttribute="false"
+ strictBinaryOperands="false"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
From 5d597329ec518ce6a53fc8dd9cac96174ada0206 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Tue, 29 Jul 2025 14:32:54 +0300
Subject: [PATCH 24/60] Add benchmarks for Inflector (#144)
Co-authored-by: samdark <47294+samdark@users.noreply.github.com>
Co-authored-by: Sergei Predvoditelev
---
.gitignore | 3 +
composer.json | 3 +-
phpbench.json | 14 +++
tests/benchmarks/CombinedRegexpBench.php | 75 +++++++++++++++
tests/benchmarks/InflectorBench.php | 109 ++++++++++++++++++++++
tests/benchmarks/NumericHelperBench.php | 36 +++++++
tests/benchmarks/StringHelperBench.php | 103 ++++++++++++++++++++
tests/benchmarks/WildcardPatternBench.php | 43 +++++++++
8 files changed, 385 insertions(+), 1 deletion(-)
create mode 100644 phpbench.json
create mode 100644 tests/benchmarks/CombinedRegexpBench.php
create mode 100644 tests/benchmarks/InflectorBench.php
create mode 100644 tests/benchmarks/NumericHelperBench.php
create mode 100644 tests/benchmarks/StringHelperBench.php
create mode 100644 tests/benchmarks/WildcardPatternBench.php
diff --git a/.gitignore b/.gitignore
index 5d023ef..d2d29e5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,6 @@ composer.phar
/phpunit.phar
/phpunit.xml
/.phpunit.cache
+
+# PHPBench
+.phpbench
diff --git a/composer.json b/composer.json
index 80e222e..275b3d8 100644
--- a/composer.json
+++ b/composer.json
@@ -37,7 +37,8 @@
"rector/rector": "^2.1.2",
"roave/infection-static-analysis-plugin": "^1.35",
"spatie/phpunit-watcher": "^1.24",
- "vimeo/psalm": "^5.26.1 || ^6.13"
+ "vimeo/psalm": "^5.26.1 || ^6.13",
+ "phpbench/phpbench": "^1.4.1"
},
"autoload": {
"psr-4": {
diff --git a/phpbench.json b/phpbench.json
new file mode 100644
index 0000000..c4443bd
--- /dev/null
+++ b/phpbench.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "./vendor/phpbench/phpbench/phpbench.schema.json",
+ "runner.bootstrap": "vendor/autoload.php",
+ "runner.path": "tests/benchmarks",
+ "runner.php_config": {
+ "memory_limit": "1G"
+ },
+ "report.generators": {
+ "default": {
+ "generator": "composite",
+ "reports": ["aggregate", "env"]
+ }
+ }
+}
diff --git a/tests/benchmarks/CombinedRegexpBench.php b/tests/benchmarks/CombinedRegexpBench.php
new file mode 100644
index 0000000..bd14c1c
--- /dev/null
+++ b/tests/benchmarks/CombinedRegexpBench.php
@@ -0,0 +1,75 @@
+combinedRegexp = new CombinedRegexp($patterns);
+ $this->memoizedCombinedRegexp = new MemoizedCombinedRegexp($this->combinedRegexp);
+ }
+
+ public function benchMatches(): void
+ {
+ $this->combinedRegexp->matches('string');
+ $this->combinedRegexp->matches('123');
+ $this->combinedRegexp->matches('String');
+ }
+
+ public function benchGetMatchingPattern(): void
+ {
+ $this->combinedRegexp->getMatchingPattern('string');
+ $this->combinedRegexp->getMatchingPattern('123');
+ $this->combinedRegexp->getMatchingPattern('String');
+ }
+
+ public function benchGetMatchingPatternPosition(): void
+ {
+ $this->combinedRegexp->getMatchingPatternPosition('string');
+ $this->combinedRegexp->getMatchingPatternPosition('123');
+ $this->combinedRegexp->getMatchingPatternPosition('String');
+ }
+
+ public function benchMemoizedMatches(): void
+ {
+ $this->memoizedCombinedRegexp->matches('string');
+ $this->memoizedCombinedRegexp->matches('123');
+ $this->memoizedCombinedRegexp->matches('String');
+ }
+
+ public function benchMemoizedGetMatchingPattern(): void
+ {
+ $this->memoizedCombinedRegexp->getMatchingPattern('string');
+ $this->memoizedCombinedRegexp->getMatchingPattern('123');
+ $this->memoizedCombinedRegexp->getMatchingPattern('String');
+ }
+
+ public function benchMemoizedGetMatchingPatternPosition(): void
+ {
+ $this->memoizedCombinedRegexp->getMatchingPatternPosition('string');
+ $this->memoizedCombinedRegexp->getMatchingPatternPosition('123');
+ $this->memoizedCombinedRegexp->getMatchingPatternPosition('String');
+ }
+}
diff --git a/tests/benchmarks/InflectorBench.php b/tests/benchmarks/InflectorBench.php
new file mode 100644
index 0000000..1937128
--- /dev/null
+++ b/tests/benchmarks/InflectorBench.php
@@ -0,0 +1,109 @@
+inflector = new Inflector();
+ }
+
+ public function benchToPlural(): void
+ {
+ $this->inflector->toPlural('apple');
+ $this->inflector->toPlural('book');
+ $this->inflector->toPlural('child');
+ $this->inflector->toPlural('person');
+ }
+
+ public function benchToSingular(): void
+ {
+ $this->inflector->toSingular('apples');
+ $this->inflector->toSingular('books');
+ $this->inflector->toSingular('children');
+ $this->inflector->toSingular('people');
+ }
+
+ public function benchToSentence(): void
+ {
+ $this->inflector->toSentence('some_string_to_convert');
+ $this->inflector->toSentence('someStringToConvert');
+ }
+
+ public function benchToWords(): void
+ {
+ $this->inflector->toWords('some_string_to_convert');
+ $this->inflector->toWords('someStringToConvert');
+ }
+
+ public function benchPascalCaseToId(): void
+ {
+ $this->inflector->pascalCaseToId('SomeString');
+ $this->inflector->pascalCaseToId('SomeOtherString');
+ }
+
+ public function benchToPascalCase(): void
+ {
+ $this->inflector->toPascalCase('some string');
+ $this->inflector->toPascalCase('some_other_string');
+ }
+
+ public function benchToCamelCase(): void
+ {
+ $this->inflector->toCamelCase('some string');
+ $this->inflector->toCamelCase('some_other_string');
+ }
+
+ public function benchToSnakeCase(): void
+ {
+ $this->inflector->toSnakeCase('SomeString');
+ $this->inflector->toSnakeCase('some other string');
+ }
+
+ public function benchToHumanReadable(): void
+ {
+ $this->inflector->toHumanReadable('some_string');
+ $this->inflector->toHumanReadable('SomeString');
+ }
+
+ public function benchClassToTable(): void
+ {
+ $this->inflector->classToTable('SomeClass');
+ $this->inflector->classToTable('Another\\ClassName');
+ }
+
+ public function benchTableToClass(): void
+ {
+ $this->inflector->tableToClass('some_table');
+ $this->inflector->tableToClass('another_table_name');
+ }
+
+ public function benchToSlug(): void
+ {
+ $this->inflector->toSlug('some string to slug');
+ $this->inflector->toSlug('some other_string-to slug');
+ }
+
+ public function benchToTransliterated(): void
+ {
+ $this->inflector->toTransliterated('some string to transliterate');
+ $this->inflector->toTransliterated('Українська мова');
+ $this->inflector->toTransliterated('Русский язык');
+ $this->inflector->toTransliterated('日本語って本当にかっこいいですね');
+ }
+}
diff --git a/tests/benchmarks/NumericHelperBench.php b/tests/benchmarks/NumericHelperBench.php
new file mode 100644
index 0000000..50469a8
--- /dev/null
+++ b/tests/benchmarks/NumericHelperBench.php
@@ -0,0 +1,36 @@
+b', '', '');
+ }
+
+ public function benchMatchAnyRegex(): void
+ {
+ StringHelper::matchAnyRegex('string', ['/[a-z]+/', '/[0-9]+/']);
+ }
+}
diff --git a/tests/benchmarks/WildcardPatternBench.php b/tests/benchmarks/WildcardPatternBench.php
new file mode 100644
index 0000000..4326d48
--- /dev/null
+++ b/tests/benchmarks/WildcardPatternBench.php
@@ -0,0 +1,43 @@
+wildcardPattern = new WildcardPattern('*a*');
+ }
+
+ public function benchMatch(): void
+ {
+ $this->wildcardPattern->match('banana');
+ $this->wildcardPattern->match('apple');
+ $this->wildcardPattern->match('orange');
+ }
+
+ public function benchIsDynamic(): void
+ {
+ WildcardPattern::isDynamic('*a*');
+ WildcardPattern::isDynamic('test');
+ }
+
+ public function benchQuote(): void
+ {
+ WildcardPattern::quote('*a*');
+ }
+}
From 2d5fea1f6e1b437996d6f3b994943b9065db1665 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Tue, 12 Aug 2025 08:10:21 +0300
Subject: [PATCH 25/60] Fix #119: Fix incorrect pluralization of words ending
in "tion", "sion" and "gion"
Co-authored-by: Alexander Makarov
---
CHANGELOG.md | 2 +-
src/Inflector.php | 7 ++++++-
tests/InflectorTest.php | 15 +++++++++++++++
3 files changed, 22 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bb63c4d..1ef0048 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,7 @@
## 2.6.1 under development
-- no changes in this release.
+- Bug #119: Fix incorrect pluralization of words ending in "tion", "sion" and "gion" (@samdark)
## 2.6.0 February 09, 2025
diff --git a/src/Inflector.php b/src/Inflector.php
index ef15aea..91ac2cd 100644
--- a/src/Inflector.php
+++ b/src/Inflector.php
@@ -101,7 +101,9 @@ final class Inflector
'/(alias)$/i' => '\1es',
'/(ax|cris|test)is$/i' => '\1es',
'/(currenc)y$/' => '\1ies',
- '/on$/i' => 'a',
+ '/(tion|sion|gion)$/i' => '\1s',
+ '/menon$/i' => 'mena',
+ '/maton$/i' => 'mata',
'/s$/' => 's',
'/^$/' => '',
'/$/' => 's',
@@ -145,6 +147,8 @@ final class Inflector
'/(^analy)ses$/i' => '\1sis',
'/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
'/criteria$/i' => 'criterion',
+ '/mena$/i' => 'menon',
+ '/mata$/i' => 'maton',
'/([ti])a$/i' => '\1um',
'/(p)eople$/i' => '\1\2erson',
'/(m)en$/i' => '\1an',
@@ -209,6 +213,7 @@ final class Inflector
'corps' => 'corps',
'corpus' => 'corpuses',
'cow' => 'cows',
+ 'criterion' => 'criteria',
'curve' => 'curves',
'debris' => 'debris',
'diabetes' => 'diabetes',
diff --git a/tests/InflectorTest.php b/tests/InflectorTest.php
index 08b6e4e..ecdf50c 100644
--- a/tests/InflectorTest.php
+++ b/tests/InflectorTest.php
@@ -39,6 +39,21 @@ private function getTestDataForToPlural(): array
'analysis' => 'analyses',
'datum' => 'data',
'schema' => 'schemas',
+ 'nation' => 'nations',
+ 'action' => 'actions',
+ 'creation' => 'creations',
+ 'decision' => 'decisions',
+ 'vision' => 'visions',
+ 'explosion' => 'explosions',
+ 'legion' => 'legions',
+ 'region' => 'regions',
+ 'information' => 'information', // special case - already ends in 'tion' but is uncountable
+ 'education' => 'educations',
+ 'attention' => 'attentions',
+ 'function' => 'functions',
+ 'junction' => 'junctions',
+ 'baron' => 'barons',
+ 'demon' => 'demons',
];
}
From e4aa788185bfac46a7ed8f7c667a0844546385e9 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Tue, 26 Aug 2025 11:24:31 +0300
Subject: [PATCH 26/60] Fix #3: Add `StringHelper::truncateWordsByLength()`
method to truncate text by character length while preserving word boundaries
Co-authored-by: Sergei Predvoditelev
Co-authored-by: Alexander Makarov
---
CHANGELOG.md | 1 +
README.md | 1 +
src/StringHelper.php | 51 +++++++++++++++++++++++++++++++++++---
tests/StringHelperTest.php | 27 ++++++++++++++++++++
4 files changed, 77 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1ef0048..e701ba9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
## 2.6.1 under development
+- New #3: Add `StringHelper::truncateWordsByLength()` method to truncate text by character length while preserving word boundaries (@samdark)
- Bug #119: Fix incorrect pluralization of words ending in "tion", "sion" and "gion" (@samdark)
## 2.6.0 February 09, 2025
diff --git a/README.md b/README.md
index f9dad8f..383e03d 100644
--- a/README.md
+++ b/README.md
@@ -74,6 +74,7 @@ Overall the helper has the following method groups.
- truncateMiddle
- truncateEnd
- truncateWords
+- truncateWordsByLength
- trim
- ltrim
- rtrim
diff --git a/src/StringHelper.php b/src/StringHelper.php
index 75b7c92..2f4dd77 100644
--- a/src/StringHelper.php
+++ b/src/StringHelper.php
@@ -15,6 +15,7 @@
use function implode;
use function max;
use function mb_strlen;
+use function mb_strpos;
use function mb_strrpos;
use function mb_strtolower;
use function mb_strtoupper;
@@ -223,7 +224,7 @@ public static function startsWithIgnoringCase(string $input, string|null $with):
* Binary and multibyte safe.
*
* @param string $input Input string to check.
- * @param string|null $with Part to search inside of the $string.
+ * @param string|null $with Part to search inside the $string.
*
* @return bool Returns true if first input ends with second input, false otherwise.
*/
@@ -237,7 +238,7 @@ public static function endsWith(string $input, string|null $with): bool
* Binary and multibyte safe.
*
* @param string $input Input string to check.
- * @param string|null $with Part to search inside of the $string.
+ * @param string|null $with Part to search inside the $string.
*
* @return bool Returns true if first input ends with second input, false otherwise.
*/
@@ -342,6 +343,50 @@ public static function truncateWords(string $input, int $count, string $trimMark
return $input;
}
+ /**
+ * Truncates a string to the specified character length while preserving word boundaries.
+ *
+ * @param string $input The string to truncate.
+ * @param int $length Maximum length of the truncated string including trim marker.
+ * @param string $trimMarker String to append to the end of truncated string.
+ * @param string $encoding The encoding to use, defaults to "UTF-8".
+ *
+ * @return string The truncated string.
+ */
+ public static function truncateWordsByLength(
+ string $input,
+ int $length,
+ string $trimMarker = '…',
+ string $encoding = 'UTF-8',
+ ): string {
+ $input = trim($input);
+ if ($input === '') {
+ return '';
+ }
+
+ if (mb_strlen($input, $encoding) <= $length) {
+ return $input;
+ }
+
+ $markerLength = mb_strlen($trimMarker, $encoding);
+ if ($length <= $markerLength) {
+ return mb_substr($trimMarker, 0, $length, $encoding);
+ }
+
+ $truncated = mb_substr($input, 0, $length - $markerLength, $encoding);
+
+ // Prefer not to break words if there's a space within the snippet.
+ $lastSpace = mb_strrpos($truncated, ' ', 0, $encoding);
+ if ($lastSpace !== false) {
+ $cut = rtrim(mb_substr($truncated, 0, $lastSpace, $encoding));
+ return $cut === ''
+ ? mb_substr($trimMarker, 0, $length, $encoding)
+ : $cut . $trimMarker;
+ }
+
+ return $truncated . $trimMarker;
+ }
+
/**
* Get string length.
*
@@ -467,7 +512,7 @@ public static function base64UrlDecode(string $input): string
/**
* Split a string to array with non-empty lines.
- * Whitespace from the beginning and end of a each line will be stripped.
+ * Whitespace from the beginning and end of each line will be stripped.
*
* @param string $string The input string. It must be valid UTF-8 string.
* @param string $separator The boundary string. It is a part of regular expression
diff --git a/tests/StringHelperTest.php b/tests/StringHelperTest.php
index 89bd206..3630743 100644
--- a/tests/StringHelperTest.php
+++ b/tests/StringHelperTest.php
@@ -120,6 +120,33 @@ public function testTruncateWords(): void
$this->assertEquals('это строка с неожиданными…', StringHelper::truncateWords(' это строка с неожиданными пробелами ', 4));
}
+ #[DataProvider('provideTruncateWordsByLength')]
+ public function testTruncateWordsByLength(string $expected, string $input, int $length, string $trimMarker = '…', string $encoding = 'UTF-8'): void
+ {
+ $this->assertSame($expected, StringHelper::truncateWordsByLength($input, $length, $trimMarker, $encoding));
+ }
+
+ public static function provideTruncateWordsByLength(): array
+ {
+ return [
+ 'basic truncation' => ['Do you like drink…', 'Do you like drink coffee at work?', 20],
+ 'string shorter than limit should return as-is' => ['Short text', 'Short text', 20],
+ 'string exactly at limit should return as-is' => ['Exact length text', 'Exact length text', 17],
+ 'custom trim marker' => ['Do you like drink!!!', 'Do you like drink coffee at work?', 23, '!!!'],
+ 'multibyte characters' => ['это тестовая…', 'это тестовая multibyte строка', 15],
+ 'no spaces (single word) - should break the word' => ['verylongwo…', 'verylongword', 11],
+ 'very short limit with marker' => ['A…', 'A long sentence', 2],
+ 'limit with the same length as marker' => ['…', 'Some text', 1],
+ 'empty string' => ['', '', 10],
+ 'spaces that exceed limit should truncate to empty' => ['', ' ', 3],
+ 'text with trailing spaces should be trimmed' => ['Hello world', 'Hello world ', 15],
+ 'multiple words that fit exactly' => ['Hello…', 'Hello world', 6],
+ 'space at start should add marker' => ['Hel…', ' Hello', 4],
+ 'spaces should be trimmed' => ['Hello', ' Hello ', 100],
+ 'nested spaces should be trimmed' => ['Hello…', 'Hello World', 8],
+ ];
+ }
+
#[DataProvider('providerStartsWith')]
public function testStartsWith(bool $result, string $string, ?string $with): void
{
From c8770364932bab9cea4f25f6a77f75eb37991eec Mon Sep 17 00:00:00 2001
From: Maksim Spirkov <63721828+max-s-lab@users.noreply.github.com>
Date: Sun, 5 Oct 2025 20:35:23 +0400
Subject: [PATCH 27/60] Add `NumericHelper::convertHumanReadableSizeToBytes()`
method (#148)
---
.gitignore | 3 ++
CHANGELOG.md | 1 +
README.md | 1 +
src/NumericHelper.php | 75 +++++++++++++++++++++++++++++++++
tests/NumericHelperTest.php | 83 +++++++++++++++++++++++++++++++++++++
5 files changed, 163 insertions(+)
diff --git a/.gitignore b/.gitignore
index d2d29e5..0f8b69d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,9 @@
# phpstorm project files
.idea
+# visual studio code project files
+.vscode
+
# netbeans project files
nbproject
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e701ba9..ae578ba 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
- New #3: Add `StringHelper::truncateWordsByLength()` method to truncate text by character length while preserving word boundaries (@samdark)
- Bug #119: Fix incorrect pluralization of words ending in "tion", "sion" and "gion" (@samdark)
+- New #148: Add `NumericHelper::convertHumanReadableSizeToBytes()` method (@max-s-lab)
## 2.6.0 February 09, 2025
diff --git a/README.md b/README.md
index 383e03d..a0ea44a 100644
--- a/README.md
+++ b/README.md
@@ -115,6 +115,7 @@ The following methods are available:
- toOrdinal
- normalize
- isInteger
+- convertHumanReadableSizeToBytes
## Inflector usage
diff --git a/src/NumericHelper.php b/src/NumericHelper.php
index 5f99ef3..6aaccef 100644
--- a/src/NumericHelper.php
+++ b/src/NumericHelper.php
@@ -16,12 +16,45 @@
use function is_scalar;
use function preg_replace;
use function str_replace;
+use function substr;
/**
* Provides static methods to work with numeric strings.
*/
final class NumericHelper
{
+ /**
+ * @psalm-var array>
+ */
+ private const FILESYSTEM_SIZE_POSTFIXES = [
+ 3 => [
+ 'KiB' => 1024,
+ 'MiB' => 1048576,
+ 'GiB' => 1073741824,
+ 'TiB' => 1099511627776,
+ 'PiB' => 1125899906842624,
+ ],
+ 2 => [
+ 'kB' => 1000,
+ 'MB' => 1000000,
+ 'GB' => 1000000000,
+ 'TB' => 1000000000000,
+ 'PB' => 1000000000000000,
+ ],
+ 1 => [
+ 'k' => 1024,
+ 'K' => 1024,
+ 'm' => 1048576,
+ 'M' => 1048576,
+ 'g' => 1073741824,
+ 'G' => 1073741824,
+ 't' => 1099511627776,
+ 'T' => 1099511627776,
+ 'p' => 1125899906842624,
+ 'P' => 1125899906842624,
+ ],
+ ];
+
/**
* Converts number to its ordinal English form. For example, converts 13 to 13th, 2 to 2nd etc.
*
@@ -86,4 +119,46 @@ public static function isInteger(mixed $value): bool
{
return filter_var($value, FILTER_VALIDATE_INT) !== false;
}
+
+ /**
+ * Converts human readable size to bytes.
+ *
+ * @param string $string Human readable size. Examples: `1024`, `1kB`, `1.5M`, `1GiB`. Full
+ * list of supported postfixes in {@see FILESYSTEM_SIZE_POSTFIXES}.
+
+ * Note: This parameter must be less than `8192P` on 64-bit systems and `2G` on 32-bit systems.
+ *
+ * @throws InvalidArgumentException when the string is invalid.
+ *
+ * @return int The number of bytes equivalent to the specified string.
+ *
+ * @see https://www.gnu.org/software/coreutils/manual/html_node/Block-size.html
+ */
+ public static function convertHumanReadableSizeToBytes(string $string): int
+ {
+ if (is_numeric($string)) {
+ return (int) $string;
+ }
+
+ foreach (self::FILESYSTEM_SIZE_POSTFIXES as $postfixLength => $postfixes) {
+ $postfix = substr($string, -$postfixLength);
+ if ($postfix === '' || preg_match('/\\d/', $postfix) === 1) {
+ continue;
+ }
+
+ $numericPart = substr($string, 0, -$postfixLength);
+ if (!is_numeric($numericPart)) {
+ throw new InvalidArgumentException("Incorrect input string: $string");
+ }
+
+ $postfixMultiplier = $postfixes[$postfix] ?? null;
+ if ($postfixMultiplier === null) {
+ throw new InvalidArgumentException("Not supported postfix '$postfix' in input string: $string");
+ }
+
+ return (int) ((float) $numericPart * $postfixMultiplier);
+ }
+
+ throw new InvalidArgumentException("Incorrect input string: $string");
+ }
}
diff --git a/tests/NumericHelperTest.php b/tests/NumericHelperTest.php
index 5291a4e..79a02a7 100644
--- a/tests/NumericHelperTest.php
+++ b/tests/NumericHelperTest.php
@@ -87,4 +87,87 @@ public function testIsInteger(mixed $value, bool $expected): void
{
$this->assertSame($expected, NumericHelper::isInteger($value));
}
+
+ public static function dataConvertHumanReadableSizeToBytes(): array
+ {
+ return [
+ // Only numbers
+ ['1024', 1024],
+ ['9223372036854775807', 9223372036854775807],
+ // Single-character postfix
+ ['512K', 524288],
+ ['512k', 524288],
+ ['2.5k', 2560],
+ ['2.5K', 2560],
+ ['128M', 134217728],
+ ['128m', 134217728],
+ ['4.5m', 4718592],
+ ['4.5M', 4718592],
+ ['2G', 2147483648],
+ ['2g', 2147483648],
+ ['2.5g', 2684354560],
+ ['2.5G', 2684354560],
+ ['1.1G', 1181116006],
+ ['2t', 2199023255552],
+ ['2T', 2199023255552],
+ ['6.5t', 7146825580544],
+ ['6.5T', 7146825580544],
+ ['3p', 3377699720527872],
+ ['3P', 3377699720527872],
+ ['3.5p', 3940649673949184],
+ ['3.5P', 3940649673949184],
+ // Two-character postfix
+ ['2kB', 2000],
+ ['2.5kB', 2500],
+ ['1MB', 1000000],
+ ['3.3MB', 3300000],
+ ['6GB', 6000000000],
+ ['7.4GB', 7400000000],
+ ['4TB', 4000000000000],
+ ['4.9TB', 4900000000000],
+ ['7PB', 7000000000000000],
+ ['7.7PB', 7700000000000000],
+ // Three-character postfix
+ ['512KiB', 524288],
+ ['2.5KiB', 2560],
+ ['128MiB', 134217728],
+ ['4.5MiB', 4718592],
+ ['2GiB', 2147483648],
+ ['2.5GiB', 2684354560],
+ ['2TiB', 2199023255552],
+ ['6.5TiB', 7146825580544],
+ ['3PiB', 3377699720527872],
+ ['3.5PiB', 3940649673949184],
+ ];
+ }
+
+ #[DataProvider('dataConvertHumanReadableSizeToBytes')]
+ public function testConvertHumanReadableSizeToBytes(string $string, int $expected): void
+ {
+ $this->assertSame($expected, NumericHelper::convertHumanReadableSizeToBytes($string));
+ }
+
+ public static function dataConvertHumanReadableSizeToBytesWithInvalidStrings(): array
+ {
+ return [
+ ['12cKib', 'Incorrect input string: 12cKib'],
+ ['12Kcb', 'Not supported postfix \'Kcb\' in input string: 12Kcb'],
+ ['1c2kB', 'Incorrect input string: 1c2kB'],
+ ['12Kc', 'Not supported postfix \'Kc\' in input string: 12Kc'],
+ ['1c2k', 'Incorrect input string: 1c2k'],
+ ['123n', 'Not supported postfix \'n\' in input string: 123n'],
+ ['k', 'Incorrect input string: k'],
+ ['K', 'Incorrect input string: K'],
+ ['m', 'Incorrect input string: m'],
+ ['M', 'Incorrect input string: M'],
+ ['', 'Incorrect input string: '],
+ ];
+ }
+
+ #[DataProvider('dataConvertHumanReadableSizeToBytesWithInvalidStrings')]
+ public function testConvertHumanReadableSizeToBytesWithInvalidStrings(string $string, string $message): void
+ {
+ $this->expectExceptionObject(new \InvalidArgumentException($message));
+ NumericHelper::convertHumanReadableSizeToBytes($string);
+ }
}
From 34bba9e4ee60940a4e2f8bcabc64d6fd9ea8d900 Mon Sep 17 00:00:00 2001
From: Maksim Spirkov <63721828+mspirkov@users.noreply.github.com>
Date: Tue, 7 Oct 2025 18:19:00 +0400
Subject: [PATCH 28/60] Minor edits in `NumericHelper` (#149)
Co-authored-by: Sergei Predvoditelev
---
CHANGELOG.md | 2 +-
src/NumericHelper.php | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ae578ba..5dcdf52 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,7 @@
- New #3: Add `StringHelper::truncateWordsByLength()` method to truncate text by character length while preserving word boundaries (@samdark)
- Bug #119: Fix incorrect pluralization of words ending in "tion", "sion" and "gion" (@samdark)
-- New #148: Add `NumericHelper::convertHumanReadableSizeToBytes()` method (@max-s-lab)
+- New #148, #149: Add `NumericHelper::convertHumanReadableSizeToBytes()` method (@mspirkov)
## 2.6.0 February 09, 2025
diff --git a/src/NumericHelper.php b/src/NumericHelper.php
index 6aaccef..7380ad8 100644
--- a/src/NumericHelper.php
+++ b/src/NumericHelper.php
@@ -14,6 +14,7 @@
use function is_bool;
use function is_numeric;
use function is_scalar;
+use function preg_match;
use function preg_replace;
use function str_replace;
use function substr;
From bce4a7c1b8280ce4aa922334ad0203c351a2851a Mon Sep 17 00:00:00 2001
From: Sergei Predvoditelev
Date: Sun, 23 Nov 2025 08:57:11 +0300
Subject: [PATCH 29/60] Add PHP 8.5 support (#150)
---
.github/workflows/build.yml | 2 +-
.github/workflows/composer-require-checker.yml | 2 +-
.github/workflows/mutation.yml | 5 +++--
.github/workflows/rector.yml | 2 +-
CHANGELOG.md | 4 +++-
README.md | 2 +-
composer.json | 14 ++++++++++----
tools/.gitignore | 2 ++
tools/infection/composer.json | 10 ++++++++++
tools/psalm/composer.json | 5 +++++
10 files changed, 37 insertions(+), 11 deletions(-)
create mode 100644 tools/.gitignore
create mode 100644 tools/infection/composer.json
create mode 100644 tools/psalm/composer.json
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 54dfa76..19b694d 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -29,4 +29,4 @@ jobs:
os: >-
['ubuntu-latest', 'windows-latest']
php: >-
- ['8.1', '8.2', '8.3', '8.4']
+ ['8.1', '8.2', '8.3', '8.4', '8.5']
diff --git a/.github/workflows/composer-require-checker.yml b/.github/workflows/composer-require-checker.yml
index a93390b..d2ef508 100644
--- a/.github/workflows/composer-require-checker.yml
+++ b/.github/workflows/composer-require-checker.yml
@@ -31,4 +31,4 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
- ['8.1', '8.2', '8.3', '8.4']
+ ['8.1', '8.2', '8.3', '8.4', '8.5']
diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml
index a56769e..cc40daa 100644
--- a/.github/workflows/mutation.yml
+++ b/.github/workflows/mutation.yml
@@ -22,11 +22,12 @@ name: mutation test
jobs:
mutation:
- uses: yiisoft/actions/.github/workflows/roave-infection.yml@master
+ uses: yiisoft/actions/.github/workflows/infection.yml@master
with:
os: >-
['ubuntu-latest']
php: >-
- ['8.4']
+ ['8.5']
+ infection-args: "--ignore-msi-with-no-mutations"
secrets:
STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }}
diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml
index 5d6931d..20f7d89 100644
--- a/.github/workflows/rector.yml
+++ b/.github/workflows/rector.yml
@@ -21,4 +21,4 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
- ['8.4']
+ ['8.5']
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5dcdf52..3870171 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,9 +2,11 @@
## 2.6.1 under development
-- New #3: Add `StringHelper::truncateWordsByLength()` method to truncate text by character length while preserving word boundaries (@samdark)
+- New #3: Add `StringHelper::truncateWordsByLength()` method to truncate text by character length while preserving word
+ boundaries (@samdark)
- Bug #119: Fix incorrect pluralization of words ending in "tion", "sion" and "gion" (@samdark)
- New #148, #149: Add `NumericHelper::convertHumanReadableSizeToBytes()` method (@mspirkov)
+- Enh #150: Add PHP 8.5 support (@vjik)
## 2.6.0 February 09, 2025
diff --git a/README.md b/README.md
index a0ea44a..2d16529 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ The package provides:
## Requirements
-- PHP 8.1 or higher.
+- PHP 8.1 - 8.5.
- `mbstring` PHP extension.
## Installation
diff --git a/composer.json b/composer.json
index 275b3d8..4c47136 100644
--- a/composer.json
+++ b/composer.json
@@ -28,16 +28,15 @@
}
],
"require": {
- "php": "8.1 - 8.4",
+ "php": "8.1 - 8.5",
"ext-mbstring": "*"
},
"require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
"maglnet/composer-require-checker": "^4.7.1",
"phpunit/phpunit": "^10.5.48",
"rector/rector": "^2.1.2",
- "roave/infection-static-analysis-plugin": "^1.35",
"spatie/phpunit-watcher": "^1.24",
- "vimeo/psalm": "^5.26.1 || ^6.13",
"phpbench/phpbench": "^1.4.1"
},
"autoload": {
@@ -53,10 +52,17 @@
"config": {
"sort-packages": true,
"allow-plugins": {
- "infection/extension-installer": true,
+ "bamarni/composer-bin-plugin": true,
"composer/package-versions-deprecated": true
}
},
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "target-directory": "tools",
+ "forward-command": true
+ }
+ },
"scripts": {
"test": "phpunit --testdox --no-interaction",
"test-watch": "phpunit-watcher watch"
diff --git a/tools/.gitignore b/tools/.gitignore
new file mode 100644
index 0000000..cf452dc
--- /dev/null
+++ b/tools/.gitignore
@@ -0,0 +1,2 @@
+/*/vendor
+/*/composer.lock
diff --git a/tools/infection/composer.json b/tools/infection/composer.json
new file mode 100644
index 0000000..18be2ea
--- /dev/null
+++ b/tools/infection/composer.json
@@ -0,0 +1,10 @@
+{
+ "require-dev": {
+ "infection/infection": "^0.26 || ^0.31.9"
+ },
+ "config": {
+ "allow-plugins": {
+ "infection/extension-installer": true
+ }
+ }
+}
diff --git a/tools/psalm/composer.json b/tools/psalm/composer.json
new file mode 100644
index 0000000..9751a9f
--- /dev/null
+++ b/tools/psalm/composer.json
@@ -0,0 +1,5 @@
+{
+ "require-dev": {
+ "vimeo/psalm": "^5.26.1 || ^6.5"
+ }
+}
From 9bc7fea56374619cccd4587848029fe97f98bb33 Mon Sep 17 00:00:00 2001
From: Sergei Predvoditelev
Date: Sun, 23 Nov 2025 21:00:58 +0300
Subject: [PATCH 30/60] Release version 2.7.0
---
CHANGELOG.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3870171..4770201 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,12 +1,12 @@
# Yii Strings Change Log
-## 2.6.1 under development
+## 2.7.0 November 23, 2025
+- New #148, #149: Add `NumericHelper::convertHumanReadableSizeToBytes()` method (@mspirkov)
- New #3: Add `StringHelper::truncateWordsByLength()` method to truncate text by character length while preserving word
boundaries (@samdark)
-- Bug #119: Fix incorrect pluralization of words ending in "tion", "sion" and "gion" (@samdark)
-- New #148, #149: Add `NumericHelper::convertHumanReadableSizeToBytes()` method (@mspirkov)
- Enh #150: Add PHP 8.5 support (@vjik)
+- Bug #119: Fix incorrect pluralization of words ending in "tion", "sion" and "gion" (@samdark)
## 2.6.0 February 09, 2025
From 434a7535d5a4f16deb2ad28c330462bdb68235d4 Mon Sep 17 00:00:00 2001
From: Sergei Predvoditelev
Date: Sun, 23 Nov 2025 21:01:03 +0300
Subject: [PATCH 31/60] Prepare for next release
---
CHANGELOG.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4770201..1099cbe 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
# Yii Strings Change Log
+## 2.7.1 under development
+
+- no changes in this release.
+
## 2.7.0 November 23, 2025
- New #148, #149: Add `NumericHelper::convertHumanReadableSizeToBytes()` method (@mspirkov)
From abc8ec6b3ae4ec1773376662f69bd722920a7b07 Mon Sep 17 00:00:00 2001
From: Sergei Predvoditelev
Date: Sun, 7 Dec 2025 00:50:47 +0300
Subject: [PATCH 32/60] Use default FUNDING and ISSUE_TEMPLATE (#152)
---
.github/FUNDING.yml | 4 ----
.github/ISSUE_TEMPLATE.md | 16 ----------------
2 files changed, 20 deletions(-)
delete mode 100644 .github/FUNDING.yml
delete mode 100644 .github/ISSUE_TEMPLATE.md
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
deleted file mode 100644
index f0dc531..0000000
--- a/.github/FUNDING.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-# These are supported funding model platforms
-
-open_collective: yiisoft
-github: [yiisoft]
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
deleted file mode 100644
index b748c2d..0000000
--- a/.github/ISSUE_TEMPLATE.md
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-### What steps will reproduce the problem?
-
-### What is the expected result?
-
-### What do you get instead?
-
-
-### Additional info
-
-| Q | A
-| ---------------- | ---
-| Version | 1.0.?
-| PHP version |
-| Operating system |
From 8b5bc076236dfd25c8fed1fdd87a22963da51aad Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Tue, 20 Jan 2026 12:26:05 +0300
Subject: [PATCH 33/60] Fix #156: Add `NumericHelper::trimDecimalZeros()`
Co-authored-by: samdark <47294+samdark@users.noreply.github.com>
Co-authored-by: Sergei Tigrov
Co-authored-by: Sergei Predvoditelev
---
CHANGELOG.md | 2 +-
README.md | 1 +
src/NumericHelper.php | 39 ++++++++++++++++++++++++-
tests/NumericHelperTest.php | 38 ++++++++++++++++++++++--
tests/benchmarks/NumericHelperBench.php | 12 ++++++++
5 files changed, 87 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1099cbe..b868d9a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,7 @@
## 2.7.1 under development
-- no changes in this release.
+- New #156: Add `NumericHelper::trimDecimalZeros()` (@samdark, @vjik)
## 2.7.0 November 23, 2025
diff --git a/README.md b/README.md
index 2d16529..d768fed 100644
--- a/README.md
+++ b/README.md
@@ -116,6 +116,7 @@ The following methods are available:
- normalize
- isInteger
- convertHumanReadableSizeToBytes
+- trimDecimalZeros
## Inflector usage
diff --git a/src/NumericHelper.php b/src/NumericHelper.php
index 7380ad8..bf68d64 100644
--- a/src/NumericHelper.php
+++ b/src/NumericHelper.php
@@ -122,7 +122,7 @@ public static function isInteger(mixed $value): bool
}
/**
- * Converts human readable size to bytes.
+ * Converts human-readable size to bytes.
*
* @param string $string Human readable size. Examples: `1024`, `1kB`, `1.5M`, `1GiB`. Full
* list of supported postfixes in {@see FILESYSTEM_SIZE_POSTFIXES}.
@@ -162,4 +162,41 @@ public static function convertHumanReadableSizeToBytes(string $string): int
throw new InvalidArgumentException("Incorrect input string: $string");
}
+
+ /**
+ * Trims spaces and trailing decimal zeros from a numeric string.
+ *
+ * If the fractional part consists only of zeros, the decimal dot separator is removed as well.
+ * The value that is `null` is returned as-is.
+ *
+ * @param string|null $value Numeric string or null.
+ *
+ * @return string|null The input string with spaces, trailing decimal zeros (and a trailing decimal
+ * dot separator, if any) removed, or `null` if the input was `null`.
+ *
+ * @see is_numeric()
+ */
+ public static function trimDecimalZeros(?string $value): ?string
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ if (!is_numeric($value)) {
+ throw new InvalidArgumentException(
+ sprintf('Value must be numeric string or null. "%s" given.', $value)
+ );
+ }
+
+ $value = trim($value);
+
+ if (!str_contains($value, '.')) {
+ return $value;
+ }
+
+ $value = rtrim($value, '0');
+ $value = rtrim($value, '.');
+
+ return $value ?: '0';
+ }
}
diff --git a/tests/NumericHelperTest.php b/tests/NumericHelperTest.php
index 79a02a7..460bfce 100644
--- a/tests/NumericHelperTest.php
+++ b/tests/NumericHelperTest.php
@@ -4,6 +4,7 @@
namespace Yiisoft\Strings\Tests;
+use InvalidArgumentException;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Yiisoft\Strings\NumericHelper;
@@ -35,7 +36,7 @@ public function testToOrdinal(): void
public function testToOrdinalWithIncorrectType(): void
{
- $this->expectException(\InvalidArgumentException::class);
+ $this->expectException(InvalidArgumentException::class);
NumericHelper::toOrdinal('bla-bla');
}
@@ -63,7 +64,7 @@ public function testNormalize(mixed $input, string $expected): void
public function testNormalizeWithIncorrectType(): void
{
- $this->expectException(\InvalidArgumentException::class);
+ $this->expectException(InvalidArgumentException::class);
NumericHelper::normalize([]);
}
@@ -167,7 +168,38 @@ public static function dataConvertHumanReadableSizeToBytesWithInvalidStrings():
#[DataProvider('dataConvertHumanReadableSizeToBytesWithInvalidStrings')]
public function testConvertHumanReadableSizeToBytesWithInvalidStrings(string $string, string $message): void
{
- $this->expectExceptionObject(new \InvalidArgumentException($message));
+ $this->expectExceptionObject(new InvalidArgumentException($message));
NumericHelper::convertHumanReadableSizeToBytes($string);
}
+
+ public static function dataTrimDecimalZeros(): array
+ {
+ return [
+ 'no decimals in integer with zeros' => ['390', '390'],
+ 'all zeros' => ['390.000', '390'],
+ 'no zeros' => ['3.14', '3.14'],
+ 'some zeros' => ['42.010', '42.01'],
+ 'zeros' => ['0.0', '0'],
+ 'decimal only' => ['.5', '.5'],
+ 'decimal zero' => ['.0', '0'],
+ 'start with zero' => ['0.25', '0.25'],
+ 'negative' => ['-3.000', '-3'],
+ 'null' => [null, null],
+ 'starts with zero' => ['02471', '02471'],
+ 'exponent' => ['1337e0', '1337e0'],
+ 'spaces' => ['3.140 ', '3.14'],
+ ];
+ }
+
+ #[DataProvider('dataTrimDecimalZeros')]
+ public function testTrimDecimalZeros(?string $input, ?string $expected): void
+ {
+ $this->assertSame($expected, NumericHelper::trimDecimalZeros($input));
+ }
+
+ public function trimDecimalZerosWithNonNumericString(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ NumericHelper::trimDecimalZeros('hello');
+ }
}
diff --git a/tests/benchmarks/NumericHelperBench.php b/tests/benchmarks/NumericHelperBench.php
index 50469a8..50081bf 100644
--- a/tests/benchmarks/NumericHelperBench.php
+++ b/tests/benchmarks/NumericHelperBench.php
@@ -33,4 +33,16 @@ public function benchNormalize(): void
NumericHelper::normalize('1,000,000.123');
NumericHelper::normalize('1 000 000,123');
}
+
+ public function benchTrimDecimalZeros(): void
+ {
+ NumericHelper::trimDecimalZeros('390');
+ NumericHelper::trimDecimalZeros('390.000');
+ NumericHelper::trimDecimalZeros('3.14');
+ NumericHelper::trimDecimalZeros('42.010');
+ NumericHelper::trimDecimalZeros('0.0');
+ NumericHelper::trimDecimalZeros('.5');
+ NumericHelper::trimDecimalZeros('0.25');
+ NumericHelper::trimDecimalZeros('-3.000');
+ }
}
From 73ccafd27d71025a160ccf4a4f9898eecc7c9351 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Fri, 19 Jun 2026 00:10:43 +0300
Subject: [PATCH 34/60] Harden GitHub workflows
---
.github/workflows/bc.yml | 5 ++++-
.github/workflows/build.yml | 5 ++++-
.github/workflows/composer-require-checker.yml | 5 ++++-
.github/workflows/mutation.yml | 5 ++++-
.github/workflows/rector.yml | 5 ++++-
.github/workflows/static.yml | 5 ++++-
6 files changed, 24 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/bc.yml b/.github/workflows/bc.yml
index 00041a9..bdfe214 100644
--- a/.github/workflows/bc.yml
+++ b/.github/workflows/bc.yml
@@ -23,9 +23,12 @@ on:
name: backwards compatibility
+permissions:
+ contents: read
+
jobs:
roave_bc_check:
- uses: yiisoft/actions/.github/workflows/bc.yml@master
+ uses: yiisoft/actions/.github/workflows/bc.yml@ab62d6b3b0e0cff6c9724ec5a395bedb41c639a2
with:
os: >-
['ubuntu-latest']
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index f980c7f..d30d533 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -22,9 +22,12 @@ on:
name: build
+permissions:
+ contents: read
+
jobs:
phpunit:
- uses: yiisoft/actions/.github/workflows/phpunit.yml@master
+ uses: yiisoft/actions/.github/workflows/phpunit.yml@ab62d6b3b0e0cff6c9724ec5a395bedb41c639a2
with:
os: >-
['ubuntu-latest', 'windows-latest']
diff --git a/.github/workflows/composer-require-checker.yml b/.github/workflows/composer-require-checker.yml
index 5473ec9..e231b98 100644
--- a/.github/workflows/composer-require-checker.yml
+++ b/.github/workflows/composer-require-checker.yml
@@ -24,9 +24,12 @@ on:
name: Composer require checker
+permissions:
+ contents: read
+
jobs:
composer-require-checker:
- uses: yiisoft/actions/.github/workflows/composer-require-checker.yml@master
+ uses: yiisoft/actions/.github/workflows/composer-require-checker.yml@ab62d6b3b0e0cff6c9724ec5a395bedb41c639a2
with:
os: >-
['ubuntu-latest']
diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml
index 8150499..2f07320 100644
--- a/.github/workflows/mutation.yml
+++ b/.github/workflows/mutation.yml
@@ -20,9 +20,12 @@ on:
name: mutation test
+permissions:
+ contents: read
+
jobs:
mutation:
- uses: yiisoft/actions/.github/workflows/roave-infection.yml@master
+ uses: yiisoft/actions/.github/workflows/roave-infection.yml@ab62d6b3b0e0cff6c9724ec5a395bedb41c639a2
with:
os: >-
['ubuntu-latest']
diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml
index 35411d0..da9413a 100644
--- a/.github/workflows/rector.yml
+++ b/.github/workflows/rector.yml
@@ -11,9 +11,12 @@ on:
name: rector
+permissions:
+ contents: read
+
jobs:
rector:
- uses: yiisoft/actions/.github/workflows/rector.yml@master
+ uses: yiisoft/actions/.github/workflows/rector.yml@ab62d6b3b0e0cff6c9724ec5a395bedb41c639a2
secrets:
token: ${{ secrets.YIISOFT_GITHUB_TOKEN }}
with:
diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml
index 8694d2d..c2c06a1 100644
--- a/.github/workflows/static.yml
+++ b/.github/workflows/static.yml
@@ -22,9 +22,12 @@ on:
name: static analysis
+permissions:
+ contents: read
+
jobs:
psalm:
- uses: yiisoft/actions/.github/workflows/psalm.yml@master
+ uses: yiisoft/actions/.github/workflows/psalm.yml@ab62d6b3b0e0cff6c9724ec5a395bedb41c639a2
with:
os: >-
['ubuntu-latest']
From 8eb30b2ccda1671ef9fa3d74b9ad5d6b5d151f8b Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Fri, 19 Jun 2026 02:10:21 +0300
Subject: [PATCH 35/60] Add zizmorify configuration
---
.github/dependabot.yml | 14 +++-----------
1 file changed, 3 insertions(+), 11 deletions(-)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index db86156..6cc0071 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,16 +1,8 @@
version: 2
updates:
- # Maintain dependencies for GitHub Actions.
- package-ecosystem: "github-actions"
directory: "/"
schedule:
- interval: "daily"
- # Too noisy. See https://github.community/t/increase-if-necessary-for-github-actions-in-dependabot/179581
- open-pull-requests-limit: 0
-
- # Maintain dependencies for Composer
- - package-ecosystem: "composer"
- directory: "/"
- schedule:
- interval: "daily"
- versioning-strategy: increase-if-necessary
+ interval: "weekly"
+ cooldown:
+ default-days: 7
From 29a6d768beba5516a768bd565fc1be6c4cf7bbe2 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Fri, 19 Jun 2026 02:33:43 +0300
Subject: [PATCH 36/60] Add zizmorify workflow
---
.github/workflows/zizmor.yml | 37 ++++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
create mode 100644 .github/workflows/zizmor.yml
diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml
new file mode 100644
index 0000000..e9b7e06
--- /dev/null
+++ b/.github/workflows/zizmor.yml
@@ -0,0 +1,37 @@
+name: GitHub Actions Security Analysis with zizmor 🌈
+
+on:
+ push:
+ branches:
+ - main
+ paths:
+ - '.github/**.yml'
+ - '.github/**.yaml'
+ pull_request:
+ paths:
+ - '.github/**.yml'
+ - '.github/**.yaml'
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+jobs:
+ zizmor:
+ name: Run zizmor 🌈
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+
+ - name: Run zizmor 🌈
+ uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
+ with:
+ advanced-security: false
+ annotations: true
+ persona: 'pedantic'
From 14254b8f901ea38256ca398e92d2f13672357c65 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Fri, 19 Jun 2026 12:02:35 +0300
Subject: [PATCH 37/60] Restore Dependabot non-GitHub-Actions updates
---
.github/dependabot.yml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 6cc0071..b185d42 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,5 +1,11 @@
version: 2
updates:
+ # Maintain dependencies for GitHub Actions.
+ - package-ecosystem: "composer"
+ directory: "/"
+ schedule:
+ interval: "daily"
+ versioning-strategy: increase-if-necessary
- package-ecosystem: "github-actions"
directory: "/"
schedule:
From 0ebdd185d87abcad12aef4d307a4902b340733ec Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Fri, 19 Jun 2026 12:11:17 +0300
Subject: [PATCH 38/60] Restore Dependabot non-GitHub-Actions updates
---
.github/dependabot.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index b185d42..988aa61 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,6 +1,8 @@
version: 2
updates:
# Maintain dependencies for GitHub Actions.
+
+ # Maintain dependencies for Composer
- package-ecosystem: "composer"
directory: "/"
schedule:
From a1ee0d0dd0dc4f31f6ff32ddc80f626ae9840adf Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Fri, 19 Jun 2026 12:18:16 +0300
Subject: [PATCH 39/60] Restore Dependabot non-GitHub-Actions updates
---
.github/dependabot.yml | 1 -
1 file changed, 1 deletion(-)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 988aa61..41b2db6 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,6 +1,5 @@
version: 2
updates:
- # Maintain dependencies for GitHub Actions.
# Maintain dependencies for Composer
- package-ecosystem: "composer"
From 8bb22d8e4997b5f72ab4a9cdac0fbb771105ecc2 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Fri, 19 Jun 2026 12:25:29 +0300
Subject: [PATCH 40/60] Restore Dependabot non-GitHub-Actions updates
---
.github/dependabot.yml | 1 -
1 file changed, 1 deletion(-)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 41b2db6..0088948 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,6 +1,5 @@
version: 2
updates:
-
# Maintain dependencies for Composer
- package-ecosystem: "composer"
directory: "/"
From 34819c28fa017305e0f9e2d2f0d21a76cd1975c2 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Fri, 19 Jun 2026 12:43:30 +0300
Subject: [PATCH 41/60] Remove Rector pull_request_target inputs
---
.github/workflows/rector.yml | 2 --
1 file changed, 2 deletions(-)
diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml
index da9413a..a62cbd6 100644
--- a/.github/workflows/rector.yml
+++ b/.github/workflows/rector.yml
@@ -17,8 +17,6 @@ permissions:
jobs:
rector:
uses: yiisoft/actions/.github/workflows/rector.yml@ab62d6b3b0e0cff6c9724ec5a395bedb41c639a2
- secrets:
- token: ${{ secrets.YIISOFT_GITHUB_TOKEN }}
with:
os: >-
['ubuntu-latest']
From 954fd6442a243ed23d56c7855512ff92a42ee752 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Fri, 19 Jun 2026 12:43:34 +0300
Subject: [PATCH 42/60] Remove Rector pull_request_target inputs
---
.github/workflows/rector.yml | 3 ---
1 file changed, 3 deletions(-)
diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml
index 20f7d89..136b0d1 100644
--- a/.github/workflows/rector.yml
+++ b/.github/workflows/rector.yml
@@ -14,10 +14,7 @@ name: rector
jobs:
rector:
uses: yiisoft/actions/.github/workflows/rector.yml@master
- secrets:
- token: ${{ secrets.YIISOFT_GITHUB_TOKEN }}
with:
- repository: ${{ github.event.pull_request.head.repo.full_name }}
os: >-
['ubuntu-latest']
php: >-
From a67e2cc707239a4dcfe9616d875a8ab74ffb46b9 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Sat, 20 Jun 2026 11:40:37 +0300
Subject: [PATCH 43/60] Use master for yiisoft actions
---
.github/workflows/bc.yml | 2 +-
.github/workflows/build.yml | 2 +-
.github/workflows/composer-require-checker.yml | 2 +-
.github/workflows/mutation.yml | 2 +-
.github/workflows/rector.yml | 2 +-
.github/workflows/static.yml | 2 +-
.github/zizmor.yml | 5 +++++
7 files changed, 11 insertions(+), 6 deletions(-)
create mode 100644 .github/zizmor.yml
diff --git a/.github/workflows/bc.yml b/.github/workflows/bc.yml
index bdfe214..b269391 100644
--- a/.github/workflows/bc.yml
+++ b/.github/workflows/bc.yml
@@ -28,7 +28,7 @@ permissions:
jobs:
roave_bc_check:
- uses: yiisoft/actions/.github/workflows/bc.yml@ab62d6b3b0e0cff6c9724ec5a395bedb41c639a2
+ uses: yiisoft/actions/.github/workflows/bc.yml@master
with:
os: >-
['ubuntu-latest']
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d30d533..d3a7abc 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -27,7 +27,7 @@ permissions:
jobs:
phpunit:
- uses: yiisoft/actions/.github/workflows/phpunit.yml@ab62d6b3b0e0cff6c9724ec5a395bedb41c639a2
+ uses: yiisoft/actions/.github/workflows/phpunit.yml@master
with:
os: >-
['ubuntu-latest', 'windows-latest']
diff --git a/.github/workflows/composer-require-checker.yml b/.github/workflows/composer-require-checker.yml
index e231b98..6c2e161 100644
--- a/.github/workflows/composer-require-checker.yml
+++ b/.github/workflows/composer-require-checker.yml
@@ -29,7 +29,7 @@ permissions:
jobs:
composer-require-checker:
- uses: yiisoft/actions/.github/workflows/composer-require-checker.yml@ab62d6b3b0e0cff6c9724ec5a395bedb41c639a2
+ uses: yiisoft/actions/.github/workflows/composer-require-checker.yml@master
with:
os: >-
['ubuntu-latest']
diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml
index 2f07320..1f7ad3d 100644
--- a/.github/workflows/mutation.yml
+++ b/.github/workflows/mutation.yml
@@ -25,7 +25,7 @@ permissions:
jobs:
mutation:
- uses: yiisoft/actions/.github/workflows/roave-infection.yml@ab62d6b3b0e0cff6c9724ec5a395bedb41c639a2
+ uses: yiisoft/actions/.github/workflows/roave-infection.yml@master
with:
os: >-
['ubuntu-latest']
diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml
index a62cbd6..98a6ff7 100644
--- a/.github/workflows/rector.yml
+++ b/.github/workflows/rector.yml
@@ -16,7 +16,7 @@ permissions:
jobs:
rector:
- uses: yiisoft/actions/.github/workflows/rector.yml@ab62d6b3b0e0cff6c9724ec5a395bedb41c639a2
+ uses: yiisoft/actions/.github/workflows/rector.yml@master
with:
os: >-
['ubuntu-latest']
diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml
index c2c06a1..f176c8e 100644
--- a/.github/workflows/static.yml
+++ b/.github/workflows/static.yml
@@ -27,7 +27,7 @@ permissions:
jobs:
psalm:
- uses: yiisoft/actions/.github/workflows/psalm.yml@ab62d6b3b0e0cff6c9724ec5a395bedb41c639a2
+ uses: yiisoft/actions/.github/workflows/psalm.yml@master
with:
os: >-
['ubuntu-latest']
diff --git a/.github/zizmor.yml b/.github/zizmor.yml
new file mode 100644
index 0000000..85ca798
--- /dev/null
+++ b/.github/zizmor.yml
@@ -0,0 +1,5 @@
+rules:
+ unpinned-uses:
+ config:
+ policies:
+ "yiisoft/*": any
From dad98de2594e1de9ba8b6a1fcf47f5436bae5a06 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Sat, 20 Jun 2026 11:40:42 +0300
Subject: [PATCH 44/60] Use master for yiisoft actions
---
.github/zizmor.yml | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 .github/zizmor.yml
diff --git a/.github/zizmor.yml b/.github/zizmor.yml
new file mode 100644
index 0000000..85ca798
--- /dev/null
+++ b/.github/zizmor.yml
@@ -0,0 +1,5 @@
+rules:
+ unpinned-uses:
+ config:
+ policies:
+ "yiisoft/*": any
From c62e98997335659d6a88fdd773e1fce95891bfa2 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Sat, 20 Jun 2026 12:10:13 +0300
Subject: [PATCH 45/60] Use master for yiisoft actions
---
.github/dependabot.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index db86156..7da1f95 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -9,6 +9,8 @@ updates:
open-pull-requests-limit: 0
# Maintain dependencies for Composer
+ ignore:
+ - dependency-name: "yiisoft/*"
- package-ecosystem: "composer"
directory: "/"
schedule:
From a483badbd02144672e46dad84327a825027a1ac5 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Sat, 20 Jun 2026 12:10:18 +0300
Subject: [PATCH 46/60] Use master for yiisoft actions
---
.github/dependabot.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 0088948..0510f85 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -12,3 +12,5 @@ updates:
interval: "weekly"
cooldown:
default-days: 7
+ ignore:
+ - dependency-name: "yiisoft/*"
From dedfc9d0880163eb7c4952cbe01a7092e6a1808e Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Sun, 21 Jun 2026 15:06:49 +0300
Subject: [PATCH 47/60] Remove redundant zizmor config
---
.github/zizmor.yml | 5 -----
1 file changed, 5 deletions(-)
delete mode 100644 .github/zizmor.yml
diff --git a/.github/zizmor.yml b/.github/zizmor.yml
deleted file mode 100644
index 85ca798..0000000
--- a/.github/zizmor.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-rules:
- unpinned-uses:
- config:
- policies:
- "yiisoft/*": any
From 34fd79af71f429defca053e172f02d6f15bb5cdd Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Wed, 24 Jun 2026 16:07:43 +0300
Subject: [PATCH 48/60] Remove redundant dependabot change
---
.github/dependabot.yml | 2 --
1 file changed, 2 deletions(-)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 7da1f95..db86156 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -9,8 +9,6 @@ updates:
open-pull-requests-limit: 0
# Maintain dependencies for Composer
- ignore:
- - dependency-name: "yiisoft/*"
- package-ecosystem: "composer"
directory: "/"
schedule:
From 9a1c47ea64a91fc10fe368b94be8538231cd5710 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Wed, 24 Jun 2026 16:38:20 +0300
Subject: [PATCH 49/60] Revert "Remove redundant dependabot change"
This reverts commit 34fd79af71f429defca053e172f02d6f15bb5cdd.
---
.github/dependabot.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index db86156..7da1f95 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -9,6 +9,8 @@ updates:
open-pull-requests-limit: 0
# Maintain dependencies for Composer
+ ignore:
+ - dependency-name: "yiisoft/*"
- package-ecosystem: "composer"
directory: "/"
schedule:
From 76284671a85c898624d2f5437c0f1241c57a4441 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Wed, 24 Jun 2026 18:00:51 +0300
Subject: [PATCH 50/60] Normalize Dependabot GitHub Actions updates
---
.github/dependabot.yml | 14 ++------------
1 file changed, 2 insertions(+), 12 deletions(-)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 2796240..6d4f88a 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,18 +1,8 @@
version: 2
updates:
- # Maintain dependencies for Composer
- ignore:
- - dependency-name: "yiisoft/*"
- - package-ecosystem: "composer"
+ # Maintain dependencies for GitHub Actions.
+ - package-ecosystem: "github-actions"\n directory: "/"\n schedule:\n interval: "weekly"\n cooldown:\n default-days: 7\n ignore:\n - dependency-name: "yiisoft/*"\n - package-ecosystem: "composer"
directory: "/"
schedule:
interval: "daily"
versioning-strategy: increase-if-necessary
- - package-ecosystem: "github-actions"
- directory: "/"
- schedule:
- interval: "weekly"
- cooldown:
- default-days: 7
- ignore:
- - dependency-name: "yiisoft/*"
From 73e5a594f70c0e1f48849fb483a66b9ecdf160b9 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Wed, 24 Jun 2026 22:40:08 +0300
Subject: [PATCH 51/60] Fix Dependabot GitHub Actions updates
---
.github/dependabot.yml | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 6d4f88a..a47ec9b 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,7 +1,17 @@
version: 2
updates:
# Maintain dependencies for GitHub Actions.
- - package-ecosystem: "github-actions"\n directory: "/"\n schedule:\n interval: "weekly"\n cooldown:\n default-days: 7\n ignore:\n - dependency-name: "yiisoft/*"\n - package-ecosystem: "composer"
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ cooldown:
+ default-days: 7
+ ignore:
+ - dependency-name: "yiisoft/*"
+
+ # Maintain dependencies for Composer
+ - package-ecosystem: "composer"
directory: "/"
schedule:
interval: "daily"
From af6dcf4396ecacf04bd7c48b1d04d6e6f3ee5413 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Thu, 25 Jun 2026 00:46:18 +0300
Subject: [PATCH 52/60] Fix zizmor workflow findings
---
.github/dependabot.yml | 2 ++
.github/workflows/zizmor.yml | 4 ++--
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index a47ec9b..10f7e30 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -15,4 +15,6 @@ updates:
directory: "/"
schedule:
interval: "daily"
+ cooldown:
+ default-days: 7
versioning-strategy: increase-if-necessary
diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml
index 9465846..430255d 100644
--- a/.github/workflows/zizmor.yml
+++ b/.github/workflows/zizmor.yml
@@ -14,8 +14,8 @@ on:
- '.github/**.yaml'
permissions:
- actions: read
- contents: read
+ actions: read # Required by zizmor when reading workflow metadata through the API.
+ contents: read # Required to read workflow files.
jobs:
zizmor:
From 521853ed749e181f551f0581ce41b60b22eac049 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Thu, 2 Jul 2026 23:31:48 +0300
Subject: [PATCH 53/60] Fix hardened workflow checks
---
.github/workflows/rector.yml | 2 +-
.github/workflows/zizmor.yml | 30 ++++++++++++++++++++++++++++--
2 files changed, 29 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml
index 2e4ba9e..69e39cc 100644
--- a/.github/workflows/rector.yml
+++ b/.github/workflows/rector.yml
@@ -1,5 +1,5 @@
on:
- pull_request_target:
+ pull_request:
paths-ignore:
- 'docs/**'
- 'README.md'
diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml
index 430255d..0861478 100644
--- a/.github/workflows/zizmor.yml
+++ b/.github/workflows/zizmor.yml
@@ -1,4 +1,4 @@
-name: GitHub Actions Security Analysis with zizmor 🌈
+name: GitHub Actions Security Analysis with zizmor
on:
push:
@@ -19,4 +19,30 @@ permissions:
jobs:
zizmor:
- uses: yiisoft/actions/.github/workflows/zizmor.yml@master
+ name: Run zizmor
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ with:
+ persist-credentials: false
+
+ - name: Create zizmor configuration
+ run: |
+ cat > .zizmor-shared.yml <<'YAML'
+ rules:
+ unpinned-uses:
+ config:
+ policies:
+ "yiisoft/*": any
+ YAML
+
+ - name: Run zizmor
+ uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
+ with:
+ advanced-security: false
+ annotations: true
+ config: .zizmor-shared.yml
+ inputs: .github
+ min-severity: high
+ persona: 'pedantic'
From 5c4e8998cf1c5d87cd714cdc3a3d20330c871910 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Fri, 3 Jul 2026 00:07:58 +0300
Subject: [PATCH 54/60] Use shared zizmor workflow
---
.github/workflows/zizmor.yml | 30 ++----------------------------
1 file changed, 2 insertions(+), 28 deletions(-)
diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml
index 0861478..430255d 100644
--- a/.github/workflows/zizmor.yml
+++ b/.github/workflows/zizmor.yml
@@ -1,4 +1,4 @@
-name: GitHub Actions Security Analysis with zizmor
+name: GitHub Actions Security Analysis with zizmor 🌈
on:
push:
@@ -19,30 +19,4 @@ permissions:
jobs:
zizmor:
- name: Run zizmor
- runs-on: ubuntu-latest
- steps:
- - name: Checkout repository
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- with:
- persist-credentials: false
-
- - name: Create zizmor configuration
- run: |
- cat > .zizmor-shared.yml <<'YAML'
- rules:
- unpinned-uses:
- config:
- policies:
- "yiisoft/*": any
- YAML
-
- - name: Run zizmor
- uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
- with:
- advanced-security: false
- annotations: true
- config: .zizmor-shared.yml
- inputs: .github
- min-severity: high
- persona: 'pedantic'
+ uses: yiisoft/actions/.github/workflows/zizmor.yml@master
From 8936c1f9a5177c023044f745190b56fbd1140563 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Fri, 3 Jul 2026 01:41:08 +0300
Subject: [PATCH 55/60] Run BC check on PHP 8.4
---
.github/workflows/bc.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/bc.yml b/.github/workflows/bc.yml
index b269391..0577a84 100644
--- a/.github/workflows/bc.yml
+++ b/.github/workflows/bc.yml
@@ -33,4 +33,4 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
- ['8.1']
+ ['8.4']
From bf37e0ff6010c02c8f69d79be54d2816704171a9 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Mon, 6 Jul 2026 13:32:41 +0300
Subject: [PATCH 56/60] Revert PHP changes
---
rector.php | 27 ++-
src/AbstractCombinedRegexp.php | 7 -
src/CombinedRegexp.php | 4 +-
src/Inflector.php | 89 +++-----
src/MemoizedCombinedRegexp.php | 2 +-
src/NumericHelper.php | 120 +----------
src/StringHelper.php | 139 ++----------
src/WildcardPattern.php | 2 -
...ase.php => AbstractCombinedRegexpTest.php} | 19 +-
tests/CombinedRegexpTest.php | 2 +-
tests/InflectorTest.php | 34 ++-
tests/IsOneOfAssert.php | 2 +-
tests/MemoizedCombinedRegexpTest.php | 2 +-
tests/NumericHelperTest.php | 132 +-----------
tests/StringHelper/MatchAnyRegexTest.php | 45 ----
tests/StringHelperTest.php | 198 ++++++++----------
tests/Support/StringableObject.php | 2 +-
tests/WildcardPatternTest.php | 25 ++-
tests/benchmarks/CombinedRegexpBench.php | 75 -------
tests/benchmarks/InflectorBench.php | 109 ----------
tests/benchmarks/NumericHelperBench.php | 48 -----
tests/benchmarks/StringHelperBench.php | 103 ---------
tests/benchmarks/WildcardPatternBench.php | 43 ----
23 files changed, 199 insertions(+), 1030 deletions(-)
rename tests/{AbstractCombinedRegexpTestCase.php => AbstractCombinedRegexpTest.php} (95%)
delete mode 100644 tests/StringHelper/MatchAnyRegexTest.php
delete mode 100644 tests/benchmarks/CombinedRegexpBench.php
delete mode 100644 tests/benchmarks/InflectorBench.php
delete mode 100644 tests/benchmarks/NumericHelperBench.php
delete mode 100644 tests/benchmarks/StringHelperBench.php
delete mode 100644 tests/benchmarks/WildcardPatternBench.php
diff --git a/rector.php b/rector.php
index 1f8a952..c80d86e 100644
--- a/rector.php
+++ b/rector.php
@@ -4,19 +4,26 @@
use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector;
use Rector\Config\RectorConfig;
+use Rector\Php73\Rector\FuncCall\JsonThrowOnErrorRector;
use Rector\Php74\Rector\Closure\ClosureToArrowFunctionRector;
-use Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector;
+use Rector\Set\ValueObject\LevelSetList;
-return RectorConfig::configure()
- ->withPaths([
+return static function (RectorConfig $rectorConfig): void {
+ $rectorConfig->paths([
__DIR__ . '/src',
__DIR__ . '/tests',
- ])
- ->withPhpSets(php81: true)
- ->withRules([
- InlineConstructorDefaultToPropertyRector::class,
- ])
- ->withSkip([
+ ]);
+
+ // register a single rule
+ $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class);
+
+ // define sets of rules
+ $rectorConfig->sets([
+ LevelSetList::UP_TO_PHP_80,
+ ]);
+
+ $rectorConfig->skip([
ClosureToArrowFunctionRector::class,
- NullToStrictStringFuncCallArgRector::class,
+ JsonThrowOnErrorRector::class,
]);
+};
diff --git a/src/AbstractCombinedRegexp.php b/src/AbstractCombinedRegexp.php
index 169172c..e40c91f 100644
--- a/src/AbstractCombinedRegexp.php
+++ b/src/AbstractCombinedRegexp.php
@@ -15,14 +15,7 @@
*/
abstract class AbstractCombinedRegexp
{
- /**
- * @psalm-suppress MissingClassConstType
- */
public const REGEXP_DELIMITER = '/';
-
- /**
- * @psalm-suppress MissingClassConstType
- */
public const QUOTE_REPLACER = '\\/';
/**
diff --git a/src/CombinedRegexp.php b/src/CombinedRegexp.php
index 81fc72c..cba76d2 100644
--- a/src/CombinedRegexp.php
+++ b/src/CombinedRegexp.php
@@ -28,7 +28,7 @@ final class CombinedRegexp extends AbstractCombinedRegexp
/**
* @psalm-var non-empty-string
*/
- private readonly string $compiledPattern;
+ private string $compiledPattern;
/**
* @param string[] $patterns Regular expressions to combine.
@@ -36,7 +36,7 @@ final class CombinedRegexp extends AbstractCombinedRegexp
*/
public function __construct(
array $patterns,
- private readonly string $flags = ''
+ private string $flags = ''
) {
if (empty($patterns)) {
throw new InvalidArgumentException('At least one pattern should be specified.');
diff --git a/src/Inflector.php b/src/Inflector.php
index beba938..07678d0 100644
--- a/src/Inflector.php
+++ b/src/Inflector.php
@@ -101,9 +101,7 @@ final class Inflector
'/(alias)$/i' => '\1es',
'/(ax|cris|test)is$/i' => '\1es',
'/(currenc)y$/' => '\1ies',
- '/(tion|sion|gion)$/i' => '\1s',
- '/menon$/i' => 'mena',
- '/maton$/i' => 'mata',
+ '/on$/i' => 'a',
'/s$/' => 's',
'/^$/' => '',
'/$/' => 's',
@@ -147,8 +145,6 @@ final class Inflector
'/(^analy)ses$/i' => '\1sis',
'/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
'/criteria$/i' => 'criterion',
- '/mena$/i' => 'menon',
- '/mata$/i' => 'maton',
'/([ti])a$/i' => '\1um',
'/(p)eople$/i' => '\1\2erson',
'/(m)en$/i' => '\1an',
@@ -213,7 +209,6 @@ final class Inflector
'corps' => 'corps',
'corpus' => 'corpuses',
'cow' => 'cows',
- 'criterion' => 'criteria',
'curve' => 'curves',
'debris' => 'debris',
'diabetes' => 'diabetes',
@@ -440,7 +435,6 @@ public function toPlural(string $input): string
}
foreach ($this->pluralizeRules as $rule => $replacement) {
if (preg_match($rule, $input)) {
- /** @var string `$rule` and `$replacement` always correct, so `preg_replace` always returns string */
return preg_replace($rule, $replacement, $input);
}
}
@@ -465,7 +459,6 @@ public function toSingular(string $input): string
foreach ($this->singularizeRules as $rule => $replacement) {
if (preg_match($rule, $input)) {
- /** @var string `$rule` and `$replacement` always correct, so `preg_replace` always returns string */
return preg_replace($rule, $replacement, $input);
}
}
@@ -495,21 +488,17 @@ public function toSentence(string $input, bool $uppercaseAll = false): string
* Converts a string into space-separated words.
* For example, 'PostTag' will be converted to 'Post Tag'.
*
- * @param string $input The valid UTF-8 string to be converted.
+ * @param string $input The string to be converted.
*
* @return string The resulting words.
*/
public function toWords(string $input): string
{
- /**
- * @var string $words We assume that `$input` is valid UTF-8 string, so `preg_replace()` never returns `false`.
- */
- $words = preg_replace('/(?pascalCaseToId($input, '_', $strict);
+ return $this->pascalCaseToId(preg_replace('/[^\pL\pN]+/u', '_', $input), '_', $strict);
}
/**
@@ -660,8 +635,8 @@ public function tableToClass(string $tableName): string
* and removes the rest. You may customize characters map via $transliteration property
* of the helper.
*
- * @param string $input An arbitrary valid UTF-8 string to convert.
- * @param string $replacement The replacement to use for spaces. It must be valid UTF-8 string.
+ * @param string $input An arbitrary string to convert.
+ * @param string $replacement The replacement to use for spaces.
* @param bool $lowercase whether to return the string in lowercase or not. Defaults to `true`.
*
* @return string The converted string.
@@ -669,21 +644,9 @@ public function tableToClass(string $tableName): string
public function toSlug(string $input, string $replacement = '-', bool $lowercase = true): string
{
$quotedReplacement = preg_quote($replacement, '/');
-
- /**
- * Replace all non-words character
- *
- * @var string $input We assume that `$input` and `$replacement` are valid UTF-8 strings, so `preg_replace()`
- * never returns `false`.
- */
+ // replace all non words character
$input = preg_replace('/[^a-zA-Z0-9]+/u', $replacement, $this->toTransliterated($input));
-
- /**
- * Remove first and last replacements
- *
- * @var string $input We assume that `$input` and `$quotedReplacement` are valid UTF-8 strings, so
- * `preg_replace()` never returns `false`.
- */
+ // remove first and last replacements
$input = preg_replace(
"/^(?:$quotedReplacement)+|(?:$quotedReplacement)+$/u" . ($lowercase ? 'i' : ''),
'',
@@ -702,10 +665,12 @@ public function toSlug(string $input, string $replacement = '-', bool $lowercase
*
* @noinspection PhpComposerExtensionStubsInspection
*
- * @param string $input Input string. It must be valid UTF-8 string.
+ * @param string $input Input string.
* @param string|Transliterator|null $transliterator either a {@see \Transliterator} or a string
* from which a {@see \Transliterator} can be built. If null, value set with {@see withTransliterator()}
* or {@see TRANSLITERATE_LOOSE} is used.
+ *
+ * @return string
*/
public function toTransliterated(string $input, $transliterator = null): string
{
@@ -714,11 +679,7 @@ public function toTransliterated(string $input, $transliterator = null): string
$transliterator = $this->transliterator;
}
- /**
- * @noinspection PhpComposerExtensionStubsInspection
- * @var string We assume that `$input` are valid UTF-8 strings and `$transliterator` is valid, so
- * `preg_replace()` never returns `false`.
- */
+ /* @noinspection PhpComposerExtensionStubsInspection */
return transliterator_transliterate($transliterator, $input);
}
diff --git a/src/MemoizedCombinedRegexp.php b/src/MemoizedCombinedRegexp.php
index f366462..1e49245 100644
--- a/src/MemoizedCombinedRegexp.php
+++ b/src/MemoizedCombinedRegexp.php
@@ -18,7 +18,7 @@ final class MemoizedCombinedRegexp extends AbstractCombinedRegexp
private array $results = [];
public function __construct(
- private readonly AbstractCombinedRegexp $decorated,
+ private AbstractCombinedRegexp $decorated,
) {
}
diff --git a/src/NumericHelper.php b/src/NumericHelper.php
index bf68d64..9a665cd 100644
--- a/src/NumericHelper.php
+++ b/src/NumericHelper.php
@@ -14,48 +14,14 @@
use function is_bool;
use function is_numeric;
use function is_scalar;
-use function preg_match;
use function preg_replace;
use function str_replace;
-use function substr;
/**
* Provides static methods to work with numeric strings.
*/
final class NumericHelper
{
- /**
- * @psalm-var array>
- */
- private const FILESYSTEM_SIZE_POSTFIXES = [
- 3 => [
- 'KiB' => 1024,
- 'MiB' => 1048576,
- 'GiB' => 1073741824,
- 'TiB' => 1099511627776,
- 'PiB' => 1125899906842624,
- ],
- 2 => [
- 'kB' => 1000,
- 'MB' => 1000000,
- 'GB' => 1000000000,
- 'TB' => 1000000000000,
- 'PB' => 1000000000000000,
- ],
- 1 => [
- 'k' => 1024,
- 'K' => 1024,
- 'm' => 1048576,
- 'M' => 1048576,
- 'g' => 1073741824,
- 'G' => 1073741824,
- 't' => 1099511627776,
- 'T' => 1099511627776,
- 'p' => 1125899906842624,
- 'P' => 1125899906842624,
- ],
- ];
-
/**
* Converts number to its ordinal English form. For example, converts 13 to 13th, 2 to 2nd etc.
*
@@ -87,7 +53,7 @@ public static function toOrdinal(mixed $value): string
/**
* Returns string representation of a number value without thousands separators and with dot as decimal separator.
*
- * @param bool|float|int|string|Stringable $value String in `string` or `Stringable` must be valid UTF-8 string.
+ * @param bool|float|int|string|Stringable $value
*
* @throws InvalidArgumentException if value is not scalar.
*/
@@ -103,11 +69,8 @@ public static function normalize(mixed $value): string
return $value ? '1' : '0';
}
- $value = str_replace([' ', ','], ['', '.'], (string) $value);
+ $value = str_replace([' ', ','], ['', '.'], (string)$value);
- /**
- * @var string We assume that `$value` is valid UTF-8 string, so `preg_replace()` never returns `false`.
- */
return preg_replace('/\.(?=.*\.)/', '', $value);
}
@@ -120,83 +83,4 @@ public static function isInteger(mixed $value): bool
{
return filter_var($value, FILTER_VALIDATE_INT) !== false;
}
-
- /**
- * Converts human-readable size to bytes.
- *
- * @param string $string Human readable size. Examples: `1024`, `1kB`, `1.5M`, `1GiB`. Full
- * list of supported postfixes in {@see FILESYSTEM_SIZE_POSTFIXES}.
-
- * Note: This parameter must be less than `8192P` on 64-bit systems and `2G` on 32-bit systems.
- *
- * @throws InvalidArgumentException when the string is invalid.
- *
- * @return int The number of bytes equivalent to the specified string.
- *
- * @see https://www.gnu.org/software/coreutils/manual/html_node/Block-size.html
- */
- public static function convertHumanReadableSizeToBytes(string $string): int
- {
- if (is_numeric($string)) {
- return (int) $string;
- }
-
- foreach (self::FILESYSTEM_SIZE_POSTFIXES as $postfixLength => $postfixes) {
- $postfix = substr($string, -$postfixLength);
- if ($postfix === '' || preg_match('/\\d/', $postfix) === 1) {
- continue;
- }
-
- $numericPart = substr($string, 0, -$postfixLength);
- if (!is_numeric($numericPart)) {
- throw new InvalidArgumentException("Incorrect input string: $string");
- }
-
- $postfixMultiplier = $postfixes[$postfix] ?? null;
- if ($postfixMultiplier === null) {
- throw new InvalidArgumentException("Not supported postfix '$postfix' in input string: $string");
- }
-
- return (int) ((float) $numericPart * $postfixMultiplier);
- }
-
- throw new InvalidArgumentException("Incorrect input string: $string");
- }
-
- /**
- * Trims spaces and trailing decimal zeros from a numeric string.
- *
- * If the fractional part consists only of zeros, the decimal dot separator is removed as well.
- * The value that is `null` is returned as-is.
- *
- * @param string|null $value Numeric string or null.
- *
- * @return string|null The input string with spaces, trailing decimal zeros (and a trailing decimal
- * dot separator, if any) removed, or `null` if the input was `null`.
- *
- * @see is_numeric()
- */
- public static function trimDecimalZeros(?string $value): ?string
- {
- if ($value === null) {
- return null;
- }
-
- if (!is_numeric($value)) {
- throw new InvalidArgumentException(
- sprintf('Value must be numeric string or null. "%s" given.', $value)
- );
- }
-
- $value = trim($value);
-
- if (!str_contains($value, '.')) {
- return $value;
- }
-
- $value = rtrim($value, '0');
- $value = rtrim($value, '.');
-
- return $value ?: '0';
- }
}
diff --git a/src/StringHelper.php b/src/StringHelper.php
index 2f4dd77..75a1bf4 100644
--- a/src/StringHelper.php
+++ b/src/StringHelper.php
@@ -15,7 +15,6 @@
use function implode;
use function max;
use function mb_strlen;
-use function mb_strpos;
use function mb_strrpos;
use function mb_strtolower;
use function mb_strtoupper;
@@ -68,7 +67,7 @@ public static function byteLength(string|null $input): int
*
* @see https://www.php.net/manual/en/function.substr.php
*/
- public static function byteSubstring(string $input, int $start, ?int $length = null): string
+ public static function byteSubstring(string $input, int $start, int $length = null): string
{
return mb_substr($input, $start, $length ?? mb_strlen($input, '8bit'), '8bit');
}
@@ -134,7 +133,7 @@ public static function directoryName(string $path): string
*
* @see https://php.net/manual/en/function.mb-substr.php
*/
- public static function substring(string $string, int $start, ?int $length = null, string $encoding = 'UTF-8'): string
+ public static function substring(string $string, int $start, int $length = null, string $encoding = 'UTF-8'): string
{
return mb_substr($string, $start, $length, $encoding);
}
@@ -224,7 +223,7 @@ public static function startsWithIgnoringCase(string $input, string|null $with):
* Binary and multibyte safe.
*
* @param string $input Input string to check.
- * @param string|null $with Part to search inside the $string.
+ * @param string|null $with Part to search inside of the $string.
*
* @return bool Returns true if first input ends with second input, false otherwise.
*/
@@ -238,7 +237,7 @@ public static function endsWith(string $input, string|null $with): bool
* Binary and multibyte safe.
*
* @param string $input Input string to check.
- * @param string|null $with Part to search inside the $string.
+ * @param string|null $with Part to search inside of the $string.
*
* @return bool Returns true if first input ends with second input, false otherwise.
*/
@@ -343,50 +342,6 @@ public static function truncateWords(string $input, int $count, string $trimMark
return $input;
}
- /**
- * Truncates a string to the specified character length while preserving word boundaries.
- *
- * @param string $input The string to truncate.
- * @param int $length Maximum length of the truncated string including trim marker.
- * @param string $trimMarker String to append to the end of truncated string.
- * @param string $encoding The encoding to use, defaults to "UTF-8".
- *
- * @return string The truncated string.
- */
- public static function truncateWordsByLength(
- string $input,
- int $length,
- string $trimMarker = '…',
- string $encoding = 'UTF-8',
- ): string {
- $input = trim($input);
- if ($input === '') {
- return '';
- }
-
- if (mb_strlen($input, $encoding) <= $length) {
- return $input;
- }
-
- $markerLength = mb_strlen($trimMarker, $encoding);
- if ($length <= $markerLength) {
- return mb_substr($trimMarker, 0, $length, $encoding);
- }
-
- $truncated = mb_substr($input, 0, $length - $markerLength, $encoding);
-
- // Prefer not to break words if there's a space within the snippet.
- $lastSpace = mb_strrpos($truncated, ' ', 0, $encoding);
- if ($lastSpace !== false) {
- $cut = rtrim(mb_substr($truncated, 0, $lastSpace, $encoding));
- return $cut === ''
- ? mb_substr($trimMarker, 0, $length, $encoding)
- : $cut . $trimMarker;
- }
-
- return $truncated . $trimMarker;
- }
-
/**
* Get string length.
*
@@ -455,16 +410,13 @@ public static function uppercaseFirstCharacter(string $string, string $encoding
/**
* Uppercase the first character of each word in a string.
*
- * @param string $string The valid UTF-8 string to be processed.
+ * @param string $string The string to be processed.
* @param string $encoding The encoding to use, defaults to "UTF-8".
*
* @see https://php.net/manual/en/function.ucwords.php
*/
public static function uppercaseFirstCharacterInEachWord(string $string, string $encoding = 'UTF-8'): string
{
- /**
- * @var array $words We assume that `$string` is valid UTF-8 string, so `preg_split()` never returns `false`.
- */
$words = preg_split('/\s/u', $string, -1, PREG_SPLIT_NO_EMPTY);
$wordsWithUppercaseFirstCharacter = array_map(
@@ -486,10 +438,6 @@ public static function uppercaseFirstCharacterInEachWord(string $string, string
* @param string $input The string to encode.
*
* @return string Encoded string.
- *
- * @psalm-template T as string
- * @psalm-param T $input
- * @psalm-return (T is non-empty-string ? non-empty-string : "")
*/
public static function base64UrlEncode(string $input): string
{
@@ -512,24 +460,15 @@ public static function base64UrlDecode(string $input): string
/**
* Split a string to array with non-empty lines.
- * Whitespace from the beginning and end of each line will be stripped.
+ * Whitespace from the beginning and end of a each line will be stripped.
*
- * @param string $string The input string. It must be valid UTF-8 string.
+ * @param string $string The input string.
* @param string $separator The boundary string. It is a part of regular expression
- * so should be taken into account or properly escaped with {@see preg_quote()}. It must be valid UTF-8 string.
+ * so should be taken into account or properly escaped with {@see preg_quote()}.
*/
public static function split(string $string, string $separator = '\R'): array
{
- /**
- * @var string $string We assume that `$string` is valid UTF-8 string, so `preg_replace()` never returns
- * `false`.
- */
$string = preg_replace('(^\s*|\s*$)', '', $string);
-
- /**
- * @var array We assume that $separator is prepared by `preg_quote()` and $string is valid UTF-8 string,
- * so `preg_split()` never returns `false`.
- */
return preg_split('~\s*' . $separator . '\s*~u', $string, -1, PREG_SPLIT_NO_EMPTY);
}
@@ -546,7 +485,7 @@ public static function split(string $string, string $separator = '\R'): array
*
* @return string[]
*
- * @psalm-return non-empty-list
+ * @psalm-return list
*/
public static function parsePath(
string $path,
@@ -567,7 +506,7 @@ public static function parsePath(
}
if ($path === '') {
- return [''];
+ return [];
}
if (!str_contains($path, $delimiter)) {
@@ -637,13 +576,8 @@ public static function parsePath(
*/
public static function trim(string|array $string, string $pattern = self::DEFAULT_WHITESPACE_PATTERN): string|array
{
- self::ensureUtf8String($string);
self::ensureUtf8Pattern($pattern);
- /**
- * @var string|string[] `$string` is correct UTF-8 string and `$pattern` is correct (it should be passed
- * already prepared), so `preg_replace` never returns `null`.
- */
return preg_replace("#^[$pattern]+|[$pattern]+$#uD", '', $string);
}
@@ -665,13 +599,8 @@ public static function trim(string|array $string, string $pattern = self::DEFAUL
*/
public static function ltrim(string|array $string, string $pattern = self::DEFAULT_WHITESPACE_PATTERN): string|array
{
- self::ensureUtf8String($string);
self::ensureUtf8Pattern($pattern);
- /**
- * @var string|string[] `$string` is correct UTF-8 string and `$pattern` is correct (it should be passed
- * already prepared), so `preg_replace` never returns `null`.
- */
return preg_replace("#^[$pattern]+#u", '', $string);
}
@@ -693,13 +622,8 @@ public static function ltrim(string|array $string, string $pattern = self::DEFAU
*/
public static function rtrim(string|array $string, string $pattern = self::DEFAULT_WHITESPACE_PATTERN): string|array
{
- self::ensureUtf8String($string);
self::ensureUtf8Pattern($pattern);
- /**
- * @var string|string[] `$string` is correct UTF-8 string and `$pattern` is correct (it should be passed
- * already prepared), so `preg_replace` never returns `null`.
- */
return preg_replace("#[$pattern]+$#uD", '', $string);
}
@@ -802,30 +726,9 @@ public static function findBetweenLast(string $string, string $start, ?string $e
}
/**
- * Checks if a given string matches any of the provided patterns.
+ * Ensure the input string is a valid UTF-8 string.
*
- * Note that patterns should be provided without delimiters on both sides. For example, `te(s|x)t`.
- *
- * @see https://www.php.net/manual/reference.pcre.pattern.syntax.php
- * @see https://www.php.net/manual/reference.pcre.pattern.modifiers.php
- *
- * @param string $string The string to match against the patterns.
- * @param string[] $patterns Regular expressions without delimiters on both sides.
- * @param string $flags Flags to apply to all regular expressions.
- */
- public static function matchAnyRegex(string $string, array $patterns, string $flags = ''): bool
- {
- if (empty($patterns)) {
- return false;
- }
-
- return (new CombinedRegexp($patterns, $flags))->matches($string);
- }
-
- /**
- * Ensure the pattern is a valid UTF-8 string.
- *
- * @param string $pattern The pattern.
+ * @param string $pattern The input string.
*
* @throws InvalidArgumentException
*/
@@ -835,22 +738,4 @@ private static function ensureUtf8Pattern(string $pattern): void
throw new InvalidArgumentException('Pattern is not a valid UTF-8 string.');
}
}
-
- /**
- * Ensure the string is a valid UTF-8 string.
- *
- * @param array|string $string The string.
- *
- * @throws InvalidArgumentException
- *
- * @psalm-param string|string[] $string
- */
- private static function ensureUtf8String(string|array $string): void
- {
- foreach ((array) $string as $s) {
- if (!preg_match('##u', $s)) {
- throw new InvalidArgumentException('String is not a valid UTF-8 string.');
- }
- }
- }
}
diff --git a/src/WildcardPattern.php b/src/WildcardPattern.php
index f5b0e27..95e5206 100644
--- a/src/WildcardPattern.php
+++ b/src/WildcardPattern.php
@@ -80,7 +80,6 @@ public function ignoreCase(bool $flag = true): self
*/
public static function isDynamic(string $pattern): bool
{
- /** @var string $pattern `$rule` and `$replacement` always correct, so `preg_replace` always returns string */
$pattern = preg_replace('/\\\\./', '', $pattern);
return preg_match('/[*{?\[]/', $pattern) === 1;
}
@@ -94,7 +93,6 @@ public static function isDynamic(string $pattern): bool
*/
public static function quote(string $string): string
{
- /** @var string `$rule` and `$replacement` always correct, so `preg_replace` always returns string */
return preg_replace('#([\\\\?*\\[\\]])#', '\\\\$1', $string);
}
diff --git a/tests/AbstractCombinedRegexpTestCase.php b/tests/AbstractCombinedRegexpTest.php
similarity index 95%
rename from tests/AbstractCombinedRegexpTestCase.php
rename to tests/AbstractCombinedRegexpTest.php
index bf06246..178d075 100644
--- a/tests/AbstractCombinedRegexpTestCase.php
+++ b/tests/AbstractCombinedRegexpTest.php
@@ -4,13 +4,14 @@
namespace Yiisoft\Strings\Tests;
-use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Yiisoft\Strings\AbstractCombinedRegexp;
-abstract class AbstractCombinedRegexpTestCase extends TestCase
+abstract class AbstractCombinedRegexpTest extends TestCase
{
- #[DataProvider('dataMatchAny')]
+ /**
+ * @dataProvider dataMatchAny
+ */
public function testMatchAny(array $patterns, string $string, bool $expectedResult): void
{
$regexp = $this->createCombinedRegexp($patterns);
@@ -105,7 +106,9 @@ public static function dataMatchAny(): iterable
];
}
- #[DataProvider('dataMatchingPattern')]
+ /**
+ * @dataProvider dataMatchingPattern
+ */
public function testMatchingPattern(array $patterns, string $string, string $expectedResult): void
{
$regexp = $this->createCombinedRegexp($patterns);
@@ -150,7 +153,9 @@ public static function dataMatchingPattern(): iterable
];
}
- #[DataProvider('dataMatchingPatternPosition')]
+ /**
+ * @dataProvider dataMatchingPatternPosition
+ */
public function testMatchingPatternPosition(array $patterns, string $string, int $expectedResult): void
{
$regexp = $this->createCombinedRegexp($patterns);
@@ -195,7 +200,9 @@ public static function dataMatchingPatternPosition(): iterable
];
}
- #[DataProvider('dataMatchDifferentDelimiters')]
+ /**
+ * @dataProvider dataMatchDifferentDelimiters
+ */
public function testMatchDifferentDelimiters(
array $patterns,
string $flags,
diff --git a/tests/CombinedRegexpTest.php b/tests/CombinedRegexpTest.php
index aa9a4ed..7a59dd2 100644
--- a/tests/CombinedRegexpTest.php
+++ b/tests/CombinedRegexpTest.php
@@ -7,7 +7,7 @@
use Yiisoft\Strings\AbstractCombinedRegexp;
use Yiisoft\Strings\CombinedRegexp;
-final class CombinedRegexpTest extends AbstractCombinedRegexpTestCase
+final class CombinedRegexpTest extends AbstractCombinedRegexpTest
{
protected function createCombinedRegexp(array $patterns, string $flags = ''): AbstractCombinedRegexp
{
diff --git a/tests/InflectorTest.php b/tests/InflectorTest.php
index 2b17ade..68cf25b 100644
--- a/tests/InflectorTest.php
+++ b/tests/InflectorTest.php
@@ -4,7 +4,6 @@
namespace Yiisoft\Strings\Tests;
-use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Yiisoft\Strings\Inflector;
@@ -39,21 +38,6 @@ private function getTestDataForToPlural(): array
'analysis' => 'analyses',
'datum' => 'data',
'schema' => 'schemas',
- 'nation' => 'nations',
- 'action' => 'actions',
- 'creation' => 'creations',
- 'decision' => 'decisions',
- 'vision' => 'visions',
- 'explosion' => 'explosions',
- 'legion' => 'legions',
- 'region' => 'regions',
- 'information' => 'information', // special case - already ends in 'tion' but is uncountable
- 'education' => 'educations',
- 'attention' => 'attentions',
- 'function' => 'functions',
- 'junction' => 'junctions',
- 'baron' => 'barons',
- 'demon' => 'demons',
];
}
@@ -151,7 +135,9 @@ public function testToWords(): void
$this->assertEquals('і це дійсно так!', $inflector->toWords('ІЦеДійсноТак!'));
}
- #[DataProvider('pascalCaseToIdProvider')]
+ /**
+ * @dataProvider pascalCaseToIdProvider()
+ */
public function testPascalCaseToId(string $expectedResult, array $arguments): void
{
$inflector = new Inflector();
@@ -176,7 +162,7 @@ public function testToCamelCase(): void
$this->assertEquals('ひらがなHepimiz', $inflector->toCamelCase('ひらがな_hepimiz'));
}
- public static function dataToSnakeCase(): array
+ public function dataToSnakeCase(): array
{
return [
[['input' => 'userName'], 'user_name'],
@@ -190,7 +176,9 @@ public static function dataToSnakeCase(): array
];
}
- #[DataProvider('dataToSnakeCase')]
+ /**
+ * @dataProvider dataToSnakeCase
+ */
public function testToSnakeCase(array $arguments, string $expectedOutput): void
{
$inflector = new Inflector();
@@ -205,7 +193,7 @@ public function testToTable(): void
$this->assertEquals('customer_tables', $inflector->classToTable('customerTable'));
}
- public static function toSlugCommonsDataProvider(): array
+ public function toSlugCommonsDataProvider(): array
{
return [
['', ''],
@@ -228,7 +216,9 @@ public static function toSlugCommonsDataProvider(): array
];
}
- #[DataProvider('toSlugCommonsDataProvider')]
+ /**
+ * @dataProvider toSlugCommonsDataProvider
+ */
public function testToSlugCommons(string $input, string $expected, string $replacement = '-'): void
{
$inflector = new Inflector();
@@ -499,7 +489,7 @@ private function assertIsOneOf(mixed $actual, array $expected, $message = ''): v
self::assertThat($actual, new IsOneOfAssert($expected), $message);
}
- public static function pascalCaseToIdProvider(): array
+ public function pascalCaseToIdProvider(): array
{
return [
['photo\\album-controller', ['Photo\\AlbumController', '-', false]],
diff --git a/tests/IsOneOfAssert.php b/tests/IsOneOfAssert.php
index 429245a..700c598 100644
--- a/tests/IsOneOfAssert.php
+++ b/tests/IsOneOfAssert.php
@@ -11,7 +11,7 @@
*/
final class IsOneOfAssert extends Constraint
{
- public function __construct(private readonly array $allowedValues)
+ public function __construct(private array $allowedValues)
{
}
diff --git a/tests/MemoizedCombinedRegexpTest.php b/tests/MemoizedCombinedRegexpTest.php
index e406f0e..138e385 100644
--- a/tests/MemoizedCombinedRegexpTest.php
+++ b/tests/MemoizedCombinedRegexpTest.php
@@ -8,7 +8,7 @@
use Yiisoft\Strings\CombinedRegexp;
use Yiisoft\Strings\MemoizedCombinedRegexp;
-final class MemoizedCombinedRegexpTest extends AbstractCombinedRegexpTestCase
+final class MemoizedCombinedRegexpTest extends AbstractCombinedRegexpTest
{
protected function createCombinedRegexp(array $patterns, string $flags = ''): AbstractCombinedRegexp
{
diff --git a/tests/NumericHelperTest.php b/tests/NumericHelperTest.php
index 460bfce..2cc509e 100644
--- a/tests/NumericHelperTest.php
+++ b/tests/NumericHelperTest.php
@@ -4,8 +4,6 @@
namespace Yiisoft\Strings\Tests;
-use InvalidArgumentException;
-use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Yiisoft\Strings\NumericHelper;
use Yiisoft\Strings\Tests\Support\StringableObject;
@@ -36,11 +34,11 @@ public function testToOrdinal(): void
public function testToOrdinalWithIncorrectType(): void
{
- $this->expectException(InvalidArgumentException::class);
+ $this->expectException(\InvalidArgumentException::class);
NumericHelper::toOrdinal('bla-bla');
}
- public static function dataNormalize(): array
+ public function dataNormalize(): array
{
return [
'French' => ['4 294 967 295,000', '4294967295.000'],
@@ -56,7 +54,9 @@ public static function dataNormalize(): array
];
}
- #[DataProvider('dataNormalize')]
+ /**
+ * @dataProvider dataNormalize
+ */
public function testNormalize(mixed $input, string $expected): void
{
$this->assertSame($expected, NumericHelper::normalize($input));
@@ -64,11 +64,11 @@ public function testNormalize(mixed $input, string $expected): void
public function testNormalizeWithIncorrectType(): void
{
- $this->expectException(InvalidArgumentException::class);
+ $this->expectException(\InvalidArgumentException::class);
NumericHelper::normalize([]);
}
- public static function dataIsInteger(): array
+ public function dataIsInteger(): array
{
return [
[new \stdClass(), false],
@@ -83,123 +83,11 @@ public static function dataIsInteger(): array
];
}
- #[DataProvider('dataIsInteger')]
+ /**
+ * @dataProvider dataIsInteger
+ */
public function testIsInteger(mixed $value, bool $expected): void
{
$this->assertSame($expected, NumericHelper::isInteger($value));
}
-
- public static function dataConvertHumanReadableSizeToBytes(): array
- {
- return [
- // Only numbers
- ['1024', 1024],
- ['9223372036854775807', 9223372036854775807],
- // Single-character postfix
- ['512K', 524288],
- ['512k', 524288],
- ['2.5k', 2560],
- ['2.5K', 2560],
- ['128M', 134217728],
- ['128m', 134217728],
- ['4.5m', 4718592],
- ['4.5M', 4718592],
- ['2G', 2147483648],
- ['2g', 2147483648],
- ['2.5g', 2684354560],
- ['2.5G', 2684354560],
- ['1.1G', 1181116006],
- ['2t', 2199023255552],
- ['2T', 2199023255552],
- ['6.5t', 7146825580544],
- ['6.5T', 7146825580544],
- ['3p', 3377699720527872],
- ['3P', 3377699720527872],
- ['3.5p', 3940649673949184],
- ['3.5P', 3940649673949184],
- // Two-character postfix
- ['2kB', 2000],
- ['2.5kB', 2500],
- ['1MB', 1000000],
- ['3.3MB', 3300000],
- ['6GB', 6000000000],
- ['7.4GB', 7400000000],
- ['4TB', 4000000000000],
- ['4.9TB', 4900000000000],
- ['7PB', 7000000000000000],
- ['7.7PB', 7700000000000000],
- // Three-character postfix
- ['512KiB', 524288],
- ['2.5KiB', 2560],
- ['128MiB', 134217728],
- ['4.5MiB', 4718592],
- ['2GiB', 2147483648],
- ['2.5GiB', 2684354560],
- ['2TiB', 2199023255552],
- ['6.5TiB', 7146825580544],
- ['3PiB', 3377699720527872],
- ['3.5PiB', 3940649673949184],
- ];
- }
-
- #[DataProvider('dataConvertHumanReadableSizeToBytes')]
- public function testConvertHumanReadableSizeToBytes(string $string, int $expected): void
- {
- $this->assertSame($expected, NumericHelper::convertHumanReadableSizeToBytes($string));
- }
-
- public static function dataConvertHumanReadableSizeToBytesWithInvalidStrings(): array
- {
- return [
- ['12cKib', 'Incorrect input string: 12cKib'],
- ['12Kcb', 'Not supported postfix \'Kcb\' in input string: 12Kcb'],
- ['1c2kB', 'Incorrect input string: 1c2kB'],
- ['12Kc', 'Not supported postfix \'Kc\' in input string: 12Kc'],
- ['1c2k', 'Incorrect input string: 1c2k'],
- ['123n', 'Not supported postfix \'n\' in input string: 123n'],
- ['k', 'Incorrect input string: k'],
- ['K', 'Incorrect input string: K'],
- ['m', 'Incorrect input string: m'],
- ['M', 'Incorrect input string: M'],
- ['', 'Incorrect input string: '],
- ];
- }
-
- #[DataProvider('dataConvertHumanReadableSizeToBytesWithInvalidStrings')]
- public function testConvertHumanReadableSizeToBytesWithInvalidStrings(string $string, string $message): void
- {
- $this->expectExceptionObject(new InvalidArgumentException($message));
- NumericHelper::convertHumanReadableSizeToBytes($string);
- }
-
- public static function dataTrimDecimalZeros(): array
- {
- return [
- 'no decimals in integer with zeros' => ['390', '390'],
- 'all zeros' => ['390.000', '390'],
- 'no zeros' => ['3.14', '3.14'],
- 'some zeros' => ['42.010', '42.01'],
- 'zeros' => ['0.0', '0'],
- 'decimal only' => ['.5', '.5'],
- 'decimal zero' => ['.0', '0'],
- 'start with zero' => ['0.25', '0.25'],
- 'negative' => ['-3.000', '-3'],
- 'null' => [null, null],
- 'starts with zero' => ['02471', '02471'],
- 'exponent' => ['1337e0', '1337e0'],
- 'spaces' => ['3.140 ', '3.14'],
- ];
- }
-
- #[DataProvider('dataTrimDecimalZeros')]
- public function testTrimDecimalZeros(?string $input, ?string $expected): void
- {
- $this->assertSame($expected, NumericHelper::trimDecimalZeros($input));
- }
-
- public function trimDecimalZerosWithNonNumericString(): void
- {
- $this->expectException(InvalidArgumentException::class);
- NumericHelper::trimDecimalZeros('hello');
- }
}
diff --git a/tests/StringHelper/MatchAnyRegexTest.php b/tests/StringHelper/MatchAnyRegexTest.php
deleted file mode 100644
index 5be92a3..0000000
--- a/tests/StringHelper/MatchAnyRegexTest.php
+++ /dev/null
@@ -1,45 +0,0 @@
-assertSame($expected, $result);
- }
-
- /**
- * @testWith [true, "test"]
- * [false, "hello"]
- */
- public function testWithoutFlags(bool $expected, string $string): void
- {
- $result = StringHelper::matchAnyRegex($string, ['te[sx]t', 'm(o|a)n']);
- $this->assertSame($expected, $result);
- }
-
- public function testWithoutPatterns(): void
- {
- $result = StringHelper::matchAnyRegex('test', []);
- $this->assertFalse($result);
- }
-}
diff --git a/tests/StringHelperTest.php b/tests/StringHelperTest.php
index 3630743..d412145 100644
--- a/tests/StringHelperTest.php
+++ b/tests/StringHelperTest.php
@@ -5,8 +5,6 @@
namespace Yiisoft\Strings\Tests;
use InvalidArgumentException;
-use PHPUnit\Framework\Attributes\DataProvider;
-use PHPUnit\Framework\Attributes\TestWith;
use PHPUnit\Framework\TestCase;
use Yiisoft\Strings\StringHelper;
@@ -120,34 +118,11 @@ public function testTruncateWords(): void
$this->assertEquals('это строка с неожиданными…', StringHelper::truncateWords(' это строка с неожиданными пробелами ', 4));
}
- #[DataProvider('provideTruncateWordsByLength')]
- public function testTruncateWordsByLength(string $expected, string $input, int $length, string $trimMarker = '…', string $encoding = 'UTF-8'): void
- {
- $this->assertSame($expected, StringHelper::truncateWordsByLength($input, $length, $trimMarker, $encoding));
- }
-
- public static function provideTruncateWordsByLength(): array
- {
- return [
- 'basic truncation' => ['Do you like drink…', 'Do you like drink coffee at work?', 20],
- 'string shorter than limit should return as-is' => ['Short text', 'Short text', 20],
- 'string exactly at limit should return as-is' => ['Exact length text', 'Exact length text', 17],
- 'custom trim marker' => ['Do you like drink!!!', 'Do you like drink coffee at work?', 23, '!!!'],
- 'multibyte characters' => ['это тестовая…', 'это тестовая multibyte строка', 15],
- 'no spaces (single word) - should break the word' => ['verylongwo…', 'verylongword', 11],
- 'very short limit with marker' => ['A…', 'A long sentence', 2],
- 'limit with the same length as marker' => ['…', 'Some text', 1],
- 'empty string' => ['', '', 10],
- 'spaces that exceed limit should truncate to empty' => ['', ' ', 3],
- 'text with trailing spaces should be trimmed' => ['Hello world', 'Hello world ', 15],
- 'multiple words that fit exactly' => ['Hello…', 'Hello world', 6],
- 'space at start should add marker' => ['Hel…', ' Hello', 4],
- 'spaces should be trimmed' => ['Hello', ' Hello ', 100],
- 'nested spaces should be trimmed' => ['Hello…', 'Hello World', 8],
- ];
- }
-
- #[DataProvider('providerStartsWith')]
+ /**
+ * @dataProvider providerStartsWith
+ *
+ * @param string|null $with
+ */
public function testStartsWith(bool $result, string $string, ?string $with): void
{
$this->assertSame($result, StringHelper::startsWith($string, $with));
@@ -156,7 +131,7 @@ public function testStartsWith(bool $result, string $string, ?string $with): voi
/**
* Rules that should work the same for case-sensitive and case-insensitive `startsWith()`.
*/
- public static function providerStartsWith(): array
+ public function providerStartsWith(): array
{
return [
// positive check
@@ -195,7 +170,11 @@ public function testStartsWithIgnoringCase(): void
$this->assertTrue(StringHelper::startsWithIgnoringCase('anything', null));
}
- #[DataProvider('providerEndsWith')]
+ /**
+ * @dataProvider providerEndsWith
+ *
+ * @param string|null $with
+ */
public function testEndsWith(bool $result, string $string, ?string $with): void
{
// case sensitive version check
@@ -205,7 +184,7 @@ public function testEndsWith(bool $result, string $string, ?string $with): void
/**
* Rules that should work the same for case-sensitive and case-insensitive `endsWith()`.
*/
- public static function providerEndsWith(): array
+ public function providerEndsWith(): array
{
return [
// positive check
@@ -252,32 +231,38 @@ public function testCountWords(): void
$this->assertEquals(1, StringHelper::countWords(' слово '));
}
- #[DataProvider('base64UrlEncodedStringsProvider')]
+ /**
+ * @dataProvider base64UrlEncodedStringsProvider
+ */
public function testBase64UrlEncode(string $input, string $base64UrlEncoded): void
{
$encoded = StringHelper::base64UrlEncode($input);
$this->assertEquals($base64UrlEncoded, $encoded);
}
- #[DataProvider('base64UrlEncodedStringsProvider')]
+ /**
+ * @dataProvider base64UrlEncodedStringsProvider
+ *
+ * @param $output
+ * @param $base64UrlEncoded
+ */
public function testBase64UrlDecode($output, $base64UrlEncoded): void
{
$decoded = StringHelper::base64UrlDecode($base64UrlEncoded);
$this->assertEquals($output, $decoded);
}
- public static function base64UrlEncodedStringsProvider(): array
+ public function base64UrlEncodedStringsProvider(): array
{
return [
'Regular string' => ['This is an encoded string', 'VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw=='],
'? and _ characters' => ['subjects?_d=1', 'c3ViamVjdHM_X2Q9MQ=='],
'> character' => ['subjects>_d=1', 'c3ViamVjdHM-X2Q9MQ=='],
'Unicode' => ['Это закодированная строка', '0K3RgtC-INC30LDQutC-0LTQuNGA0L7QstCw0L3QvdCw0Y8g0YHRgtGA0L7QutCw'],
- 'empty string' => ['', ''],
];
}
- public static function uppercaseFirstCharacterProvider(): array
+ public function uppercaseFirstCharacterProvider(): array
{
return [
['foo', 'Foo'],
@@ -288,13 +273,15 @@ public static function uppercaseFirstCharacterProvider(): array
];
}
- #[DataProvider('uppercaseFirstCharacterProvider')]
+ /**
+ * @dataProvider uppercaseFirstCharacterProvider
+ */
public function testUppercaseFirstCharacter(string $string, string $expectedResult): void
{
$this->assertSame($expectedResult, StringHelper::uppercaseFirstCharacter($string));
}
- public static function uppercaseFirstCharacterInEachWordProvider(): array
+ public function uppercaseFirstCharacterInEachWordProvider(): array
{
return [
'Single word' => ['foo', 'Foo'],
@@ -305,7 +292,9 @@ public static function uppercaseFirstCharacterInEachWordProvider(): array
];
}
- #[DataProvider('uppercaseFirstCharacterInEachWordProvider')]
+ /**
+ * @dataProvider uppercaseFirstCharacterInEachWordProvider
+ */
public function testUppercaseFirstCharacterInEachWord(string $string, string $expectedResult): void
{
$this->assertSame($expectedResult, StringHelper::uppercaseFirstCharacterInEachWord($string));
@@ -358,7 +347,7 @@ public function testLength(): void
/**
* @see https://github.com/php/php-src/blob/master/ext/standard/tests/strings/substr_replace.phpt
*/
- public static function replaceSubstringProvider(): array
+ public function replaceSubstringProvider(): array
{
return [
['trbala ', ['try this', 'bala ', 2]],
@@ -371,13 +360,15 @@ public static function replaceSubstringProvider(): array
];
}
- #[DataProvider('replaceSubstringProvider')]
+ /**
+ * @dataProvider replaceSubstringProvider
+ */
public function testReplaceSubstring(string $expected, array $arguments): void
{
$this->assertSame($expected, StringHelper::replaceSubstring(...$arguments));
}
- public static function dataSplit(): array
+ public function dataSplit(): array
{
return [
['', []],
@@ -419,7 +410,9 @@ public static function dataSplit(): array
];
}
- #[DataProvider('dataSplit')]
+ /**
+ * @dataProvider dataSplit
+ */
public function testSplit(string $string, array $expected): void
{
$this->assertSame($expected, StringHelper::split($string));
@@ -430,7 +423,7 @@ public function testSplitWithSeparator(): void
$this->assertSame(['A', 'B', 'C'], StringHelper::split(' A 2 B3C', '\d'));
}
- public static function dataParsePath(): array
+ public function dataParsePath(): array
{
return [
['key1.key2.key3', '.', '\\', false, ['key1', 'key2', 'key3']],
@@ -471,12 +464,14 @@ public static function dataParsePath(): array
['.key1.key2', '.', '\\', false, ['', 'key1', 'key2']],
['~key1~key2', '~', '\\', false, ['', 'key1', 'key2']],
- ['', '.', '\\', false, ['']],
- ['', '.', '\\', true, ['']],
+ ['', '.', '\\', false, []],
+ ['', '.', '\\', true, []],
];
}
- #[DataProvider('dataParsePath')]
+ /**
+ * @dataProvider dataParsePath
+ */
public function testParsePath(
string $path,
string $delimiter,
@@ -512,7 +507,7 @@ public function testParsePathWithDelimiterEqualsEscapeCharacter(): void
StringHelper::parsePath('key1.key2.key3', '.', '.');
}
- public static function dataInvariantTrim(): iterable
+ public function dataInvariantTrim(): iterable
{
$base = 'Здесь我' . self::WS['nbsp'] . '-' . self::WS['thsp'] . 'Multibyte我' . self::WS['lsep'] . 'Строка 👍🏻';
@@ -530,7 +525,7 @@ public static function dataInvariantTrim(): iterable
];
}
- public static function dataTrim(): iterable
+ public function dataTrim(): iterable
{
$base = 'Здесь我' . self::WS['nbsp'] . '-' . self::WS['thsp'] . 'Multibyte我' . self::WS['lsep'] . 'Строка 👍🏻';
@@ -556,7 +551,7 @@ public static function dataTrim(): iterable
];
}
- public static function dataLtrim(): iterable
+ public function dataLtrim(): iterable
{
$base = 'Здесь我' . self::WS['nbsp'] . '-' . self::WS['thsp'] . 'Multibyte我' . self::WS['lsep'] . 'Строка 👍🏻';
@@ -582,7 +577,7 @@ public static function dataLtrim(): iterable
];
}
- public static function dataRtrim(): iterable
+ public function dataRtrim(): iterable
{
$base = 'Здесь我' . self::WS['nbsp'] . '-' . self::WS['thsp'] . 'Multibyte我' . self::WS['lsep'] . 'Строка 👍🏻';
@@ -604,7 +599,7 @@ public static function dataRtrim(): iterable
];
}
- public static function dataTrimPattern(): iterable
+ public function dataTrimPattern(): iterable
{
$base = 'Здесь我' . self::WS['nbsp'] . '-' . self::WS['thsp'] . 'Multibyte我' . self::WS['lsep'] . 'Строка 👍🏻';
@@ -645,7 +640,7 @@ public static function dataTrimPattern(): iterable
];
}
- public static function dataLtrimPattern(): iterable
+ public function dataLtrimPattern(): iterable
{
$base = 'Здесь我' . self::WS['nbsp'] . '-' . self::WS['thsp'] . 'Multibyte我' . self::WS['lsep'] . 'Строка 👍🏻';
@@ -671,7 +666,7 @@ public static function dataLtrimPattern(): iterable
];
}
- public static function dataRtrimPattern(): iterable
+ public function dataRtrimPattern(): iterable
{
$base = 'Здесь我' . self::WS['nbsp'] . '-' . self::WS['thsp'] . 'Multibyte我' . self::WS['lsep'] . 'Строка 👍🏻';
@@ -712,40 +707,52 @@ public static function dataRtrimPattern(): iterable
];
}
- #[DataProvider('dataTrim')]
- #[DataProvider('dataInvariantTrim')]
+ /**
+ * @dataProvider dataInvariantTrim
+ * @dataProvider dataTrim
+ */
public function testTrim(string|array $string, string|array $expected): void
{
$this->assertSame($expected, StringHelper::trim($string));
}
- #[DataProvider('dataLtrim')]
- #[DataProvider('dataInvariantTrim')]
+ /**
+ * @dataProvider dataInvariantTrim
+ * @dataProvider dataLtrim
+ */
public function testLtrim(string|array $string, string|array $expected): void
{
$this->assertSame($expected, StringHelper::ltrim($string));
}
- #[DataProvider('dataRtrim')]
- #[DataProvider('dataInvariantTrim')]
+ /**
+ * @dataProvider dataInvariantTrim
+ * @dataProvider dataRtrim
+ */
public function testRtrim(string|array $string, string|array $expected): void
{
$this->assertSame($expected, StringHelper::rtrim($string));
}
- #[DataProvider('dataTrimPattern')]
+ /**
+ * @dataProvider dataTrimPattern
+ */
public function testTrimPattern(string|array $string, string $pattern, string|array $expected): void
{
$this->assertSame($expected, StringHelper::trim($string, $pattern));
}
- #[DataProvider('dataLtrimPattern')]
+ /**
+ * @dataProvider dataLtrimPattern
+ */
public function testLtrimPattern(string|array $string, string $pattern, string|array $expected): void
{
$this->assertSame($expected, StringHelper::ltrim($string, $pattern));
}
- #[DataProvider('dataRtrimPattern')]
+ /**
+ * @dataProvider dataRtrimPattern
+ */
public function testRtrimPattern(string|array $string, string $pattern, string|array $expected): void
{
$this->assertSame($expected, StringHelper::rtrim($string, $pattern));
@@ -754,58 +761,19 @@ public function testRtrimPattern(string|array $string, string $pattern, string|a
public function testInvalidTrimPattern(): void
{
$this->expectException(InvalidArgumentException::class);
- $this->expectExceptionMessage('Pattern is not a valid UTF-8 string.');
- StringHelper::trim('string', "\xC3\x28");
- }
-
- #[TestWith(["abc\xFF"])]
- #[TestWith([['hello', "abc\xFF"]])]
- public function testInvalidTrimString(string|array $string): void
- {
- $this->expectException(InvalidArgumentException::class);
- $this->expectExceptionMessage('String is not a valid UTF-8 string.');
- StringHelper::trim($string);
- }
-
- public function testInvalidLtrimPattern(): void
- {
- $this->expectException(InvalidArgumentException::class);
- $this->expectExceptionMessage('Pattern is not a valid UTF-8 string.');
- StringHelper::ltrim('string', "\xC3\x28");
- }
- #[TestWith(["abc\xFF"])]
- #[TestWith([['hello', "abc\xFF"]])]
- public function testInvalidLtrimString(string|array $string): void
- {
- $this->expectException(InvalidArgumentException::class);
- $this->expectExceptionMessage('String is not a valid UTF-8 string.');
- StringHelper::ltrim($string);
- }
-
- public function testInvalidRtrimPattern(): void
- {
- $this->expectException(InvalidArgumentException::class);
- $this->expectExceptionMessage('Pattern is not a valid UTF-8 string.');
- StringHelper::ltrim('string', "\xC3\x28");
- }
-
- #[TestWith(["abc\xFF"])]
- #[TestWith([['hello', "abc\xFF"]])]
- public function testInvalidRtrimString(string|array $string): void
- {
- $this->expectException(InvalidArgumentException::class);
- $this->expectExceptionMessage('String is not a valid UTF-8 string.');
- StringHelper::rtrim($string);
+ StringHelper::trim('string', "\xC3\x28");
}
- #[DataProvider('dataProviderFindBetween')]
+ /**
+ * @dataProvider dataProviderFindBetween
+ */
public function testFindBetween(string $string, string $start, ?string $end, ?string $expectedResult): void
{
$this->assertSame($expectedResult, StringHelper::findBetween($string, $start, $end));
}
- public static function dataProviderFindBetween(): array
+ public function dataProviderFindBetween(): array
{
return [
['hello world hello', ' hello', ' world', null], // end before start
@@ -825,13 +793,15 @@ public static function dataProviderFindBetween(): array
];
}
- #[DataProvider('dataProviderFindBetweenFirst')]
+ /**
+ * @dataProvider dataProviderFindBetweenFirst
+ */
public function testFindBetweenFirst(string $string, string $start, ?string $end, ?string $expectedResult): void
{
$this->assertSame($expectedResult, StringHelper::findBetweenFirst($string, $start, $end));
}
- public static function dataProviderFindBetweenFirst(): array
+ public function dataProviderFindBetweenFirst(): array
{
return [
['[a][b][c]', '[', ']', 'a'], // normal case
@@ -853,13 +823,15 @@ public static function dataProviderFindBetweenFirst(): array
];
}
- #[DataProvider('dataProviderFindBetweenLast')]
+ /**
+ * @dataProvider dataProviderFindBetweenLast
+ */
public function testFindBetweenLast(string $string, string $start, ?string $end, ?string $expectedResult): void
{
$this->assertSame($expectedResult, StringHelper::findBetweenLast($string, $start, $end));
}
- public static function dataProviderFindBetweenLast(): array
+ public function dataProviderFindBetweenLast(): array
{
return [
['[a][b][c]', '[', ']', 'c'], // normal case
diff --git a/tests/Support/StringableObject.php b/tests/Support/StringableObject.php
index 5c83bb0..e4d1614 100644
--- a/tests/Support/StringableObject.php
+++ b/tests/Support/StringableObject.php
@@ -9,7 +9,7 @@
final class StringableObject implements Stringable
{
public function __construct(
- private readonly string $string
+ private string $string
) {
}
diff --git a/tests/WildcardPatternTest.php b/tests/WildcardPatternTest.php
index 99b9aee..acee2ac 100644
--- a/tests/WildcardPatternTest.php
+++ b/tests/WildcardPatternTest.php
@@ -4,7 +4,6 @@
namespace Yiisoft\Strings\Tests;
-use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Yiisoft\Strings\WildcardPattern;
@@ -15,7 +14,7 @@ final class WildcardPatternTest extends TestCase
*
* @return array test data.
*/
- public static function dataProviderMatchWildcard(): array
+ public function dataProviderMatchWildcard(): array
{
return [
// *
@@ -94,7 +93,9 @@ public static function dataProviderMatchWildcard(): array
];
}
- #[DataProvider('dataProviderMatchWildcard')]
+ /**
+ * @dataProvider dataProviderMatchWildcard
+ */
public function testMatchWildcard(string $pattern, string $string, bool $expectedResult, array $options = []): void
{
$wildcardPattern = $this->getWildcardPattern($pattern, $options);
@@ -125,13 +126,15 @@ public function testImmutability(): void
$this->assertNotSame($original, $original->ignoreCase());
}
- #[DataProvider('isDynamicDataProvider')]
+ /**
+ * @dataProvider isDynamicDataProvider
+ */
public function testIsDynamic(string $pattern, bool $expected): void
{
$this->assertSame($expected, WildcardPattern::isDynamic($pattern));
}
- public static function isDynamicDataProvider(): array
+ public function isDynamicDataProvider(): array
{
return [
'not-dynamic' => ['just-some-string', false],
@@ -142,7 +145,7 @@ public static function isDynamicDataProvider(): array
];
}
- public static function customDelimitersProvider(): array
+ public function customDelimitersProvider(): array
{
return [
'empty' => ['begin*end', 'begin/end', [], true],
@@ -151,14 +154,16 @@ public static function customDelimitersProvider(): array
];
}
- #[DataProvider('customDelimitersProvider')]
+ /**
+ * @dataProvider customDelimitersProvider
+ */
public function testCustomDelimiters(string $pattern, string $string, array $delimiters, bool $expected): void
{
$wildcardPattern = $wildcardPattern = new WildcardPattern($pattern, $delimiters);
$this->assertSame($expected, $wildcardPattern->match($string));
}
- public static function quoteDataProvider(): array
+ public function quoteDataProvider(): array
{
return [
'no-pattern' => ['test', 'test'],
@@ -166,7 +171,9 @@ public static function quoteDataProvider(): array
];
}
- #[DataProvider('quoteDataProvider')]
+ /**
+ * @dataProvider quoteDataProvider
+ */
public function testQuote(string $string, string $expected): void
{
$this->assertSame($expected, WildcardPattern::quote($string));
diff --git a/tests/benchmarks/CombinedRegexpBench.php b/tests/benchmarks/CombinedRegexpBench.php
deleted file mode 100644
index bd14c1c..0000000
--- a/tests/benchmarks/CombinedRegexpBench.php
+++ /dev/null
@@ -1,75 +0,0 @@
-combinedRegexp = new CombinedRegexp($patterns);
- $this->memoizedCombinedRegexp = new MemoizedCombinedRegexp($this->combinedRegexp);
- }
-
- public function benchMatches(): void
- {
- $this->combinedRegexp->matches('string');
- $this->combinedRegexp->matches('123');
- $this->combinedRegexp->matches('String');
- }
-
- public function benchGetMatchingPattern(): void
- {
- $this->combinedRegexp->getMatchingPattern('string');
- $this->combinedRegexp->getMatchingPattern('123');
- $this->combinedRegexp->getMatchingPattern('String');
- }
-
- public function benchGetMatchingPatternPosition(): void
- {
- $this->combinedRegexp->getMatchingPatternPosition('string');
- $this->combinedRegexp->getMatchingPatternPosition('123');
- $this->combinedRegexp->getMatchingPatternPosition('String');
- }
-
- public function benchMemoizedMatches(): void
- {
- $this->memoizedCombinedRegexp->matches('string');
- $this->memoizedCombinedRegexp->matches('123');
- $this->memoizedCombinedRegexp->matches('String');
- }
-
- public function benchMemoizedGetMatchingPattern(): void
- {
- $this->memoizedCombinedRegexp->getMatchingPattern('string');
- $this->memoizedCombinedRegexp->getMatchingPattern('123');
- $this->memoizedCombinedRegexp->getMatchingPattern('String');
- }
-
- public function benchMemoizedGetMatchingPatternPosition(): void
- {
- $this->memoizedCombinedRegexp->getMatchingPatternPosition('string');
- $this->memoizedCombinedRegexp->getMatchingPatternPosition('123');
- $this->memoizedCombinedRegexp->getMatchingPatternPosition('String');
- }
-}
diff --git a/tests/benchmarks/InflectorBench.php b/tests/benchmarks/InflectorBench.php
deleted file mode 100644
index 1937128..0000000
--- a/tests/benchmarks/InflectorBench.php
+++ /dev/null
@@ -1,109 +0,0 @@
-inflector = new Inflector();
- }
-
- public function benchToPlural(): void
- {
- $this->inflector->toPlural('apple');
- $this->inflector->toPlural('book');
- $this->inflector->toPlural('child');
- $this->inflector->toPlural('person');
- }
-
- public function benchToSingular(): void
- {
- $this->inflector->toSingular('apples');
- $this->inflector->toSingular('books');
- $this->inflector->toSingular('children');
- $this->inflector->toSingular('people');
- }
-
- public function benchToSentence(): void
- {
- $this->inflector->toSentence('some_string_to_convert');
- $this->inflector->toSentence('someStringToConvert');
- }
-
- public function benchToWords(): void
- {
- $this->inflector->toWords('some_string_to_convert');
- $this->inflector->toWords('someStringToConvert');
- }
-
- public function benchPascalCaseToId(): void
- {
- $this->inflector->pascalCaseToId('SomeString');
- $this->inflector->pascalCaseToId('SomeOtherString');
- }
-
- public function benchToPascalCase(): void
- {
- $this->inflector->toPascalCase('some string');
- $this->inflector->toPascalCase('some_other_string');
- }
-
- public function benchToCamelCase(): void
- {
- $this->inflector->toCamelCase('some string');
- $this->inflector->toCamelCase('some_other_string');
- }
-
- public function benchToSnakeCase(): void
- {
- $this->inflector->toSnakeCase('SomeString');
- $this->inflector->toSnakeCase('some other string');
- }
-
- public function benchToHumanReadable(): void
- {
- $this->inflector->toHumanReadable('some_string');
- $this->inflector->toHumanReadable('SomeString');
- }
-
- public function benchClassToTable(): void
- {
- $this->inflector->classToTable('SomeClass');
- $this->inflector->classToTable('Another\\ClassName');
- }
-
- public function benchTableToClass(): void
- {
- $this->inflector->tableToClass('some_table');
- $this->inflector->tableToClass('another_table_name');
- }
-
- public function benchToSlug(): void
- {
- $this->inflector->toSlug('some string to slug');
- $this->inflector->toSlug('some other_string-to slug');
- }
-
- public function benchToTransliterated(): void
- {
- $this->inflector->toTransliterated('some string to transliterate');
- $this->inflector->toTransliterated('Українська мова');
- $this->inflector->toTransliterated('Русский язык');
- $this->inflector->toTransliterated('日本語って本当にかっこいいですね');
- }
-}
diff --git a/tests/benchmarks/NumericHelperBench.php b/tests/benchmarks/NumericHelperBench.php
deleted file mode 100644
index 50081bf..0000000
--- a/tests/benchmarks/NumericHelperBench.php
+++ /dev/null
@@ -1,48 +0,0 @@
-b', '', '');
- }
-
- public function benchMatchAnyRegex(): void
- {
- StringHelper::matchAnyRegex('string', ['/[a-z]+/', '/[0-9]+/']);
- }
-}
diff --git a/tests/benchmarks/WildcardPatternBench.php b/tests/benchmarks/WildcardPatternBench.php
deleted file mode 100644
index 4326d48..0000000
--- a/tests/benchmarks/WildcardPatternBench.php
+++ /dev/null
@@ -1,43 +0,0 @@
-wildcardPattern = new WildcardPattern('*a*');
- }
-
- public function benchMatch(): void
- {
- $this->wildcardPattern->match('banana');
- $this->wildcardPattern->match('apple');
- $this->wildcardPattern->match('orange');
- }
-
- public function benchIsDynamic(): void
- {
- WildcardPattern::isDynamic('*a*');
- WildcardPattern::isDynamic('test');
- }
-
- public function benchQuote(): void
- {
- WildcardPattern::quote('*a*');
- }
-}
From cc6993cde8c6b121f8ca9da780a271ca1c3bb433 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Mon, 6 Jul 2026 13:38:44 +0300
Subject: [PATCH 57/60] Revert unrelated workflow changes
---
.gitattributes | 1 +
.github/FUNDING.yml | 4 ++
.github/ISSUE_TEMPLATE.md | 16 +++++
.github/dependabot.yml | 2 -
.github/workflows/bc.yml | 2 +-
.github/workflows/build.yml | 2 +-
.../workflows/composer-require-checker.yml | 2 +-
.github/workflows/mutation.yml | 5 +-
.github/workflows/rector.yml | 2 +-
.github/workflows/static.yml | 2 +-
.gitignore | 14 ++--
.scrutinizer.yml | 35 ++++++++++
CHANGELOG.md | 24 -------
README.md | 65 ++++++++++++++-----
UPGRADE.md | 13 ++--
composer.json | 37 +++--------
docs/internals.md | 44 -------------
phpbench.json | 14 ----
phpunit.xml.dist | 28 ++++----
psalm.xml | 2 -
tools/.gitignore | 2 -
tools/infection/composer.json | 10 ---
tools/psalm/composer.json | 5 --
23 files changed, 145 insertions(+), 186 deletions(-)
create mode 100644 .github/FUNDING.yml
create mode 100644 .github/ISSUE_TEMPLATE.md
create mode 100644 .scrutinizer.yml
delete mode 100644 docs/internals.md
delete mode 100644 phpbench.json
delete mode 100644 tools/.gitignore
delete mode 100644 tools/infection/composer.json
delete mode 100644 tools/psalm/composer.json
diff --git a/.gitattributes b/.gitattributes
index 9f9e9af..5583f36 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -26,6 +26,7 @@
/.editorconfig export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
+/.scrutinizer.yml export-ignore
/phpunit.xml.dist export-ignore
/tests export-ignore
/docs export-ignore
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..f0dc531
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,4 @@
+# These are supported funding model platforms
+
+open_collective: yiisoft
+github: [yiisoft]
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..b748c2d
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,16 @@
+
+
+### What steps will reproduce the problem?
+
+### What is the expected result?
+
+### What do you get instead?
+
+
+### Additional info
+
+| Q | A
+| ---------------- | ---
+| Version | 1.0.?
+| PHP version |
+| Operating system |
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 10f7e30..a47ec9b 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -15,6 +15,4 @@ updates:
directory: "/"
schedule:
interval: "daily"
- cooldown:
- default-days: 7
versioning-strategy: increase-if-necessary
diff --git a/.github/workflows/bc.yml b/.github/workflows/bc.yml
index 0577a84..b269391 100644
--- a/.github/workflows/bc.yml
+++ b/.github/workflows/bc.yml
@@ -33,4 +33,4 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
- ['8.4']
+ ['8.1']
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 35abbae..d3a7abc 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -32,4 +32,4 @@ jobs:
os: >-
['ubuntu-latest', 'windows-latest']
php: >-
- ['8.1', '8.2', '8.3', '8.4', '8.5']
+ ['8.0', '8.1', '8.2', '8.3']
diff --git a/.github/workflows/composer-require-checker.yml b/.github/workflows/composer-require-checker.yml
index 1a56fef..6c2e161 100644
--- a/.github/workflows/composer-require-checker.yml
+++ b/.github/workflows/composer-require-checker.yml
@@ -34,4 +34,4 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
- ['8.1', '8.2', '8.3', '8.4', '8.5']
+ ['8.0', '8.1', '8.2', '8.3']
diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml
index ce83871..1f7ad3d 100644
--- a/.github/workflows/mutation.yml
+++ b/.github/workflows/mutation.yml
@@ -25,12 +25,11 @@ permissions:
jobs:
mutation:
- uses: yiisoft/actions/.github/workflows/infection.yml@master
+ uses: yiisoft/actions/.github/workflows/roave-infection.yml@master
with:
os: >-
['ubuntu-latest']
php: >-
- ['8.5']
- infection-args: "--ignore-msi-with-no-mutations"
+ ['8.2']
secrets:
STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }}
diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml
index 69e39cc..98a6ff7 100644
--- a/.github/workflows/rector.yml
+++ b/.github/workflows/rector.yml
@@ -21,4 +21,4 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
- ['8.5']
+ ['8.3']
diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml
index cab5775..f176c8e 100644
--- a/.github/workflows/static.yml
+++ b/.github/workflows/static.yml
@@ -32,4 +32,4 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
- ['8.1', '8.2', '8.3', '8.4']
+ ['8.0', '8.1', '8.2', '8.3']
diff --git a/.gitignore b/.gitignore
index 0f8b69d..56da114 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,6 @@
# phpstorm project files
.idea
-# visual studio code project files
-.vscode
-
# netbeans project files
nbproject
@@ -26,10 +23,9 @@ composer.phar
# Mac DS_Store Files
.DS_Store
-# PHPUnit
-/phpunit.phar
+# phpunit itself is not needed
+phpunit.phar
+# local phpunit config
/phpunit.xml
-/.phpunit.cache
-
-# PHPBench
-.phpbench
+# phpunit cache
+.phpunit.result.cache
diff --git a/.scrutinizer.yml b/.scrutinizer.yml
new file mode 100644
index 0000000..61fb502
--- /dev/null
+++ b/.scrutinizer.yml
@@ -0,0 +1,35 @@
+checks:
+ php: true
+
+filter:
+ paths:
+ - src/
+
+build:
+ image: default-bionic
+
+ environment:
+ php:
+ version: 8.0.18
+ ini:
+ xdebug.mode: coverage
+
+ nodes:
+ analysis:
+ tests:
+ override:
+ - php-scrutinizer-run
+
+ phpunit:
+ dependencies:
+ override:
+ - composer self-update
+ - composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi
+
+ tests:
+ override:
+ - command: ./vendor/bin/phpunit --coverage-clover ./coverage.xml
+ on_node: 1
+ coverage:
+ file: coverage.xml
+ format: php-clover
diff --git a/CHANGELOG.md b/CHANGELOG.md
index aa82df6..7ea8f41 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,30 +3,6 @@
## 3.0.0 under development
- Chg #112: Change default value of `$strict` parameter in `Inflector::toSnakeCase()` to `false` (@vjik)
-## 2.7.1 under development
-
-- New #156: Add `NumericHelper::trimDecimalZeros()` (@samdark, @vjik)
-
-## 2.7.0 November 23, 2025
-
-- New #148, #149: Add `NumericHelper::convertHumanReadableSizeToBytes()` method (@mspirkov)
-- New #3: Add `StringHelper::truncateWordsByLength()` method to truncate text by character length while preserving word
- boundaries (@samdark)
-- Enh #150: Add PHP 8.5 support (@vjik)
-- Bug #119: Fix incorrect pluralization of words ending in "tion", "sion" and "gion" (@samdark)
-
-## 2.6.0 February 09, 2025
-
-- Chg #140: Bump minimal required PHP version to 8.1 and minor refactoring (@vjik)
-- Chg #143: Change PHP constraint in `composer.json` to `~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0` (@vjik)
-- Bug #138: Explicitly mark nullable parameters (@ferrumfist)
-- Bug #141: `StringHelper::parsePath()` for empty string returns path `['']` instead of `[]` before (@vjik)
-- Bug #142: Check string on a valid UTF-8 in `StringHelper` methods: `trim()`, `ltrim()` and `rtrim()` (@vjik)
-
-## 2.5.0 January 19, 2025
-
-- New #137: Add `StringHelper::matchAnyRegex()` method as a facade for `CombinedRegexp` (@vjik)
-- Enh #128: Add more specific psalm type for result of `StringHelper::base64UrlEncode()` method (@vjik)
## 2.4.0 December 22, 2023
diff --git a/README.md b/README.md
index d768fed..e6ca88e 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,16 @@
-[](https://packagist.org/packages/yiisoft/strings)
-[](https://packagist.org/packages/yiisoft/strings)
-[](https://github.com/yiisoft/strings/actions/workflows/build.yml)
-[](https://codecov.io/gh/yiisoft/strings)
+[](https://packagist.org/packages/yiisoft/strings)
+[](https://packagist.org/packages/yiisoft/strings)
+[](https://github.com/yiisoft/strings/actions)
+[](https://scrutinizer-ci.com/g/yiisoft/strings/?branch=master)
+[](https://scrutinizer-ci.com/g/yiisoft/strings/?branch=master)
[](https://dashboard.stryker-mutator.io/reports/github.com/yiisoft/strings/master)
[](https://github.com/yiisoft/strings/actions?query=workflow%3A%22static+analysis%22)
[](https://shepherd.dev/github/yiisoft/strings)
@@ -20,18 +21,16 @@ The package provides:
- `NumericHelper` that has static methods to work with numeric strings;
- `Inflector` provides methods such as `toPlural()` or `toSlug()` that derive a new string based on the string given;
- `WildcardPattern` is a shell wildcard pattern to match strings against;
-- `CombinedRegexp` is a wrapper that optimizes multiple regular expressions matching and
+- `CombinedRegexp` is a wrapper that optimizes multiple regular expressions matching and
`MemoizedCombinedRegexp` is a decorator that caches results of `CombinedRegexp` to speed up matching.
## Requirements
-- PHP 8.1 - 8.5.
+- PHP 8.0 or higher.
- `mbstring` PHP extension.
## Installation
-The package could be installed with [Composer](https://getcomposer.org):
-
```shell
composer require yiisoft/strings
```
@@ -74,7 +73,6 @@ Overall the helper has the following method groups.
- truncateMiddle
- truncateEnd
- truncateWords
-- truncateWordsByLength
- trim
- ltrim
- rtrim
@@ -98,7 +96,6 @@ Overall the helper has the following method groups.
### Other
-- matchAnyRegex
- parsePath
- split
@@ -115,8 +112,6 @@ The following methods are available:
- toOrdinal
- normalize
- isInteger
-- convertHumanReadableSizeToBytes
-- trimDecimalZeros
## Inflector usage
@@ -160,7 +155,7 @@ Overall the inflector has the following method groups.
## WildcardPattern usage
-`WildcardPattern` allows a simple POSIX-style string matching.
+`WildcardPattern` allows a simple POSIX-style string matching.
```php
use \Yiisoft\Strings\WildcardPattern;
@@ -246,12 +241,46 @@ if ($regexp->matches('second')) {
}
```
-## Documentation
+## Testing
+
+### Unit testing
+
+The package is tested with [PHPUnit](https://phpunit.de/). To run tests:
+
+```shell
+./vendor/bin/phpunit
+```
+
+### Mutation testing
+
+The package tests are checked with [Infection](https://infection.github.io/) mutation framework with
+[Infection Static Analysis Plugin](https://github.com/Roave/infection-static-analysis-plugin). To run it:
+
+```shell
+./vendor/bin/roave-infection-static-analysis-plugin
+```
+
+### Static analysis
+
+The code is statically analyzed with [Psalm](https://psalm.dev/). To run static analysis:
+
+```shell
+./vendor/bin/psalm
+```
+
+### Code style
+
+Use [Rector](https://github.com/rectorphp/rector) to make codebase follow some specific rules or
+use either newest or any specific version of PHP:
+
+```shell
+./vendor/bin/rector
+```
-- [Internals](docs/internals.md)
+### Dependencies
-If you need help or have a question, the [Yii Forum](https://forum.yiiframework.com/c/yii-3-0/63) is a good place for that.
-You may also check out other [Yii Community Resources](https://www.yiiframework.com/community).
+Use [ComposerRequireChecker](https://github.com/maglnet/ComposerRequireChecker) to detect transitive
+[Composer](https://getcomposer.org/) dependencies.
## License
diff --git a/UPGRADE.md b/UPGRADE.md
index 42dc833..3d9851e 100644
--- a/UPGRADE.md
+++ b/UPGRADE.md
@@ -1,11 +1,12 @@
-# Upgrading Instructions for Yii Strings
+# Upgrading Instructions
-This file contains the upgrade notes. These notes highlight changes that could break your
-application when you upgrade the package from one version to another.
+These notes highlight changes that could break your application when you upgrade package from one version to another.
-> **Important!** The following upgrading instructions are cumulative. That is, if you want
-> to upgrade from version A to version C and there is version B between A and C, you need
-> to following the instructions for both A and B.
+Upgrading in general is as simple as updating your dependency in your `composer.json` and running `composer update`.
+In a big application however there may be more things to consider, which are explained in the following.
+
+> Note: The following upgrading instructions are cumulative. That is, if you want to upgrade from a version A to version
+> C and there is a version B between A and C, you need to follow the instructions for both A and B.
## Upgrade from 2.x to 3.x
diff --git a/composer.json b/composer.json
index 4c47136..df553e2 100644
--- a/composer.json
+++ b/composer.json
@@ -10,34 +10,24 @@
"homepage": "https://www.yiiframework.com/",
"license": "BSD-3-Clause",
"support": {
- "issues": "https://github.com/yiisoft/strings/issues?state=open",
"source": "https://github.com/yiisoft/strings",
+ "issues": "https://github.com/yiisoft/strings/issues",
"forum": "https://www.yiiframework.com/forum/",
"wiki": "https://www.yiiframework.com/wiki/",
- "irc": "ircs://irc.libera.chat:6697/yii",
+ "irc": "irc://irc.freenode.net/yii",
"chat": "https://t.me/yii3en"
},
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/yiisoft"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/yiisoft"
- }
- ],
"require": {
- "php": "8.1 - 8.5",
+ "php": "^8.0",
"ext-mbstring": "*"
},
"require-dev": {
- "bamarni/composer-bin-plugin": "^1.8.2",
- "maglnet/composer-require-checker": "^4.7.1",
- "phpunit/phpunit": "^10.5.48",
- "rector/rector": "^2.1.2",
- "spatie/phpunit-watcher": "^1.24",
- "phpbench/phpbench": "^1.4.1"
+ "maglnet/composer-require-checker": "^4.2",
+ "phpunit/phpunit": "^9.5",
+ "rector/rector": "^0.18.3",
+ "roave/infection-static-analysis-plugin": "^1.16",
+ "spatie/phpunit-watcher": "^1.23",
+ "vimeo/psalm": "^4.30|^5.8"
},
"autoload": {
"psr-4": {
@@ -52,17 +42,10 @@
"config": {
"sort-packages": true,
"allow-plugins": {
- "bamarni/composer-bin-plugin": true,
+ "infection/extension-installer": true,
"composer/package-versions-deprecated": true
}
},
- "extra": {
- "bamarni-bin": {
- "bin-links": true,
- "target-directory": "tools",
- "forward-command": true
- }
- },
"scripts": {
"test": "phpunit --testdox --no-interaction",
"test-watch": "phpunit-watcher watch"
diff --git a/docs/internals.md b/docs/internals.md
deleted file mode 100644
index 087a514..0000000
--- a/docs/internals.md
+++ /dev/null
@@ -1,44 +0,0 @@
-# Internals
-
-## Unit testing
-
-The package is tested with [PHPUnit](https://phpunit.de/). To run tests:
-
-```shell
-./vendor/bin/phpunit
-```
-
-## Mutation testing
-
-The package tests are checked with [Infection](https://infection.github.io/) mutation framework with
-[Infection Static Analysis Plugin](https://github.com/Roave/infection-static-analysis-plugin). To run it:
-
-```shell
-./vendor/bin/roave-infection-static-analysis-plugin
-```
-
-## Static analysis
-
-The code is statically analyzed with [Psalm](https://psalm.dev/). To run static analysis:
-
-```shell
-./vendor/bin/psalm
-```
-
-## Code style
-
-Use [Rector](https://github.com/rectorphp/rector) to make codebase follow some specific rules or
-use either newest or any specific version of PHP:
-
-```shell
-./vendor/bin/rector
-```
-
-## Dependencies
-
-This package uses [composer-require-checker](https://github.com/maglnet/ComposerRequireChecker) to check if
-all dependencies are correctly defined in `composer.json`. To run the checker, execute the following command:
-
-```shell
-./vendor/bin/composer-require-checker
-```
diff --git a/phpbench.json b/phpbench.json
deleted file mode 100644
index c4443bd..0000000
--- a/phpbench.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "$schema": "./vendor/phpbench/phpbench/phpbench.schema.json",
- "runner.bootstrap": "vendor/autoload.php",
- "runner.path": "tests/benchmarks",
- "runner.php_config": {
- "memory_limit": "1G"
- },
- "report.generators": {
- "default": {
- "generator": "composite",
- "reports": ["aggregate", "env"]
- }
- }
-}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 98b0b95..14acb8c 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,20 +1,18 @@
-
+ executionOrder="random"
+ resolveDependencies="true">
-
+
@@ -23,9 +21,9 @@
-
+
- ./src
+ ./src
-
+
diff --git a/psalm.xml b/psalm.xml
index 77a366b..d091d59 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -3,8 +3,6 @@
errorLevel="1"
findUnusedBaselineEntry="true"
findUnusedCode="false"
- ensureOverrideAttribute="false"
- strictBinaryOperands="false"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
diff --git a/tools/.gitignore b/tools/.gitignore
deleted file mode 100644
index cf452dc..0000000
--- a/tools/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/*/vendor
-/*/composer.lock
diff --git a/tools/infection/composer.json b/tools/infection/composer.json
deleted file mode 100644
index 18be2ea..0000000
--- a/tools/infection/composer.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "require-dev": {
- "infection/infection": "^0.26 || ^0.31.9"
- },
- "config": {
- "allow-plugins": {
- "infection/extension-installer": true
- }
- }
-}
diff --git a/tools/psalm/composer.json b/tools/psalm/composer.json
deleted file mode 100644
index 9751a9f..0000000
--- a/tools/psalm/composer.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "require-dev": {
- "vimeo/psalm": "^5.26.1 || ^6.5"
- }
-}
From 352f07db3745f0046e906e65f3360e78a63083ad Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Mon, 6 Jul 2026 13:44:10 +0300
Subject: [PATCH 58/60] Run BC check on PHP 8.4
---
.github/workflows/bc.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/bc.yml b/.github/workflows/bc.yml
index b269391..0577a84 100644
--- a/.github/workflows/bc.yml
+++ b/.github/workflows/bc.yml
@@ -33,4 +33,4 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
- ['8.1']
+ ['8.4']
From d7a4dfdeb53b40e18f55d7df89eb7cefbe71855a Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Mon, 6 Jul 2026 13:45:53 +0300
Subject: [PATCH 59/60] Allow BC checker install on PHP 8.4
---
.github/workflows/bc.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.github/workflows/bc.yml b/.github/workflows/bc.yml
index 0577a84..0e4777e 100644
--- a/.github/workflows/bc.yml
+++ b/.github/workflows/bc.yml
@@ -34,3 +34,5 @@ jobs:
['ubuntu-latest']
php: >-
['8.4']
+ composer-require-options: >-
+ --with-all-dependencies --no-progress --ansi --ignore-platform-req=php+
From ca2728e31d68b8346e45d15babfeb8d3adb5e1d7 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Mon, 6 Jul 2026 13:50:12 +0300
Subject: [PATCH 60/60] Make BC checker installable on PHP 8.4
---
.github/workflows/bc.yml | 2 --
composer.json | 2 +-
2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/.github/workflows/bc.yml b/.github/workflows/bc.yml
index 0e4777e..0577a84 100644
--- a/.github/workflows/bc.yml
+++ b/.github/workflows/bc.yml
@@ -34,5 +34,3 @@ jobs:
['ubuntu-latest']
php: >-
['8.4']
- composer-require-options: >-
- --with-all-dependencies --no-progress --ansi --ignore-platform-req=php+
diff --git a/composer.json b/composer.json
index df553e2..4503cb3 100644
--- a/composer.json
+++ b/composer.json
@@ -27,7 +27,7 @@
"rector/rector": "^0.18.3",
"roave/infection-static-analysis-plugin": "^1.16",
"spatie/phpunit-watcher": "^1.23",
- "vimeo/psalm": "^4.30|^5.8"
+ "vimeo/psalm": "^4.30|^5.8|^6.16"
},
"autoload": {
"psr-4": {