From 4a83b8482733a46402341e733f572df08947a7a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 02:04:40 +0000 Subject: [PATCH 01/10] Add PHPUnit compatibility plan progress Agent-Logs-Url: https://github.com/voku/Stringy/sessions/ab96a171-0cfe-4ebc-bd6a-9b52ff092f05 Co-authored-by: voku <264695+voku@users.noreply.github.com> --- .github/workflows/ci.yml | 54 +++++++++++++++++++++++++++---------- phpunit.legacy.xml.dist | 16 +++++++++++ src/Stringy.php | 2 +- tests/StringyStrictTest.php | 6 ++++- tests/StringyTest.php | 6 ++++- 5 files changed, 67 insertions(+), 17 deletions(-) create mode 100644 phpunit.legacy.xml.dist diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 19bd4ce..8cc562c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,19 +16,43 @@ jobs: strategy: fail-fast: false matrix: - php: [ - 7.1, - 7.2, - 7.3 - 7.4, - 8.0, - 8.1, - 8.2, - 8.3, - 8.4, - 8.5 - ] - composer: [basic] + include: + - php: '7.1' + composer: basic + phpunit: '^7.5' + phpunit_config: phpunit.legacy.xml.dist + - php: '7.2' + composer: basic + phpunit: '^8.5' + phpunit_config: phpunit.legacy.xml.dist + - php: '7.3' + composer: basic + phpunit: '^9.6' + phpunit_config: phpunit.xml.dist + - php: '7.4' + composer: basic + phpunit: '^9.6' + phpunit_config: phpunit.xml.dist + - php: '8.0' + composer: basic + phpunit: '^9.6' + phpunit_config: phpunit.xml.dist + - php: '8.1' + composer: basic + phpunit: '^9.6' + phpunit_config: phpunit.xml.dist + - php: '8.2' + composer: basic + phpunit: '^9.6' + phpunit_config: phpunit.xml.dist + - php: '8.3' + composer: basic + phpunit: '^9.6' + phpunit_config: phpunit.xml.dist + - php: '8.4' + composer: basic + phpunit: '^9.6' + phpunit_config: phpunit.xml.dist timeout-minutes: 10 steps: - name: Checkout code @@ -57,6 +81,8 @@ jobs: - name: Install dependencies run: | + composer require --dev --no-update "phpunit/phpunit:${{ matrix.phpunit }}" + if [[ "${{ matrix.composer }}" == "lowest" ]]; then composer update --prefer-dist --no-interaction --prefer-lowest --prefer-stable fi; @@ -70,7 +96,7 @@ jobs: - name: Run tests run: | mkdir -p build/logs - XDEBUG_MODE=coverage php vendor/bin/phpunit -c phpunit.xml.dist + XDEBUG_MODE=coverage php vendor/bin/phpunit -c "${{ matrix.phpunit_config }}" - name: Run phpstan continue-on-error: true diff --git a/phpunit.legacy.xml.dist b/phpunit.legacy.xml.dist new file mode 100644 index 0000000..7c87358 --- /dev/null +++ b/phpunit.legacy.xml.dist @@ -0,0 +1,16 @@ + + + + + ./src/ + + + + + + + + tests + + + diff --git a/src/Stringy.php b/src/Stringy.php index f7b600b..c7e83b4 100644 --- a/src/Stringy.php +++ b/src/Stringy.php @@ -2513,7 +2513,7 @@ public function isWhitespace(): bool * * @return string The current value of the $str property */ - public function jsonSerialize(): mixed + public function jsonSerialize() { return (string) $this; } diff --git a/tests/StringyStrictTest.php b/tests/StringyStrictTest.php index 5708beb..08fb531 100644 --- a/tests/StringyStrictTest.php +++ b/tests/StringyStrictTest.php @@ -1909,7 +1909,11 @@ public function testAppendRandomStringProducesCorrectChars() { $result = S::create('')->appendRandomString(8, 'abc'); static::assertSame(8, $result->length()); - static::assertMatchesRegularExpression('/^[abc]{8}$/', $result->toString()); + if (\method_exists(__CLASS__, 'assertMatchesRegularExpression')) { + static::assertMatchesRegularExpression('/^[abc]{8}$/', $result->toString()); + } else { + static::assertRegExp('/^[abc]{8}$/', $result->toString()); + } } public function testAddUniqueIdentifier() diff --git a/tests/StringyTest.php b/tests/StringyTest.php index 3081551..04232ea 100644 --- a/tests/StringyTest.php +++ b/tests/StringyTest.php @@ -1907,7 +1907,11 @@ public function testAppendRandomStringProducesCorrectChars() { $result = S::create('')->appendRandomString(8, 'abc'); static::assertSame(8, $result->length()); - static::assertMatchesRegularExpression('/^[abc]{8}$/', $result->toString()); + if (\method_exists(__CLASS__, 'assertMatchesRegularExpression')) { + static::assertMatchesRegularExpression('/^[abc]{8}$/', $result->toString()); + } else { + static::assertRegExp('/^[abc]{8}$/', $result->toString()); + } } public function testAddUniqueIdentifier() From bfb3e65f8c39b12a861e9ae0cdd2b3571efec536 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 02:06:54 +0000 Subject: [PATCH 02/10] Document CI PHPUnit matrix limit Agent-Logs-Url: https://github.com/voku/Stringy/sessions/ab96a171-0cfe-4ebc-bd6a-9b52ff092f05 Co-authored-by: voku <264695+voku@users.noreply.github.com> --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8cc562c..d02b8e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,8 @@ jobs: strategy: fail-fast: false matrix: + # Keep the "modern" leg on PHPUnit 9 until the suite is upgraded for + # PHPUnit 10+, so CI currently stops at PHP 8.4. include: - php: '7.1' composer: basic From 34b076980101ed4cdf5cd1c42a77240e22c479f9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 02:12:53 +0000 Subject: [PATCH 03/10] Tighten phpstan type metadata Agent-Logs-Url: https://github.com/voku/Stringy/sessions/c994b095-7d56-4d26-ab6a-452e4181411e Co-authored-by: voku <264695+voku@users.noreply.github.com> --- phpstan.neon | 1 + src/StaticStringy.php | 12 ++++++------ src/Stringy.php | 2 ++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index a7a1e36..7ab0718 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,4 +1,5 @@ parameters: + phpVersion: 70100 level: 8 paths: - %currentWorkingDirectory%/src/ diff --git a/src/StaticStringy.php b/src/StaticStringy.php index 752b83a..1f6418c 100644 --- a/src/StaticStringy.php +++ b/src/StaticStringy.php @@ -18,8 +18,8 @@ * @method static string[] chars(string $stringInput, ?string $encoding = null) * @method static Stringy collapseWhitespace(string $stringInput, ?string $encoding = null) * @method static bool contains(string $stringInput, string $needle, bool $caseSensitive = true, ?string $encoding = null) - * @method static bool containsAll(string $stringInput, array $needle, bool $caseSensitive = true, ?string $encoding = null) - * @method static bool containsAny(string $stringInput, string $needle, bool $caseSensitive = true, ?string $encoding = null) + * @method static bool containsAll(string $stringInput, string[] $needle, bool $caseSensitive = true, ?string $encoding = null) + * @method static bool containsAny(string $stringInput, string[] $needle, bool $caseSensitive = true, ?string $encoding = null) * @method static int count(string $stringInput, ?string $encoding = null) * @method static int countSubstr(string $stringInput, string $substring, bool $caseSensitive = true, ?string $encoding = null) * @method static Stringy dasherize(string $stringInput, ?string $encoding = null) @@ -32,8 +32,8 @@ * @method static Stringy first(string $stringInput, int $n, ?string $encoding = null) * @method static bool hasLowerCase(string $stringInput, ?string $encoding = null) * @method static bool hasUpperCase(string $stringInput, ?string $encoding = null) - * @method static Stringy htmlDecode(string $stringInput, int $flags = ENT_COMPAT, ?string $encoding = null) - * @method static Stringy htmlEncode(string $stringInput, int $flags = ENT_COMPAT, ?string $encoding = null) + * @method static Stringy htmlDecode(string $stringInput, int $flags, ?string $encoding = null) + * @method static Stringy htmlEncode(string $stringInput, int $flags, ?string $encoding = null) * @method static Stringy humanize(string $stringInput, ?string $encoding = null) * @method static int|bool indexOf(string $stringInput, string $needle, int $offset = 0, ?string $encoding = null) * @method static int|bool indexOfLast(string $stringInput, string $needle, int $offset = 0, ?string $encoding = null) @@ -71,12 +71,12 @@ * @method static Stringy removeXss(string $stringInput, ?string $encoding = null) * @method static Stringy repeat(string $stringInput, int $multiplier, ?string $encoding = null) * @method static Stringy replace(string $stringInput, string $search, string $replacement, bool $caseSensitive, ?string $encoding = null) - * @method static Stringy replaceAll(string $stringInput, array $search, string $replacement, bool $caseSensitive, ?string $encoding = null) + * @method static Stringy replaceAll(string $stringInput, string[] $search, string|string[] $replacement, bool $caseSensitive, ?string $encoding = null) * @method static Stringy reverse(string $stringInput, ?string $encoding = null) * @method static Stringy safeTruncate(string $stringInput, int $length, string $substring = '', ?string $encoding = null) * @method static Stringy shuffle(string $stringInput, ?string $encoding = null) * @method static Stringy shortenAfterWord(string $stringInput, int $length, string $strAddOn) - * @method static Stringy slugify(string $stringInput, string $separator = '-', string $language = 'en', array $replacements = [], ?string $encoding = null) + * @method static Stringy slugify(string $stringInput, string $separator = '-', string $language = 'en', array $replacements = [], ?string $encoding = null) * @method static Stringy stripeCssMediaQueries(string $stringInput) * @method static Stringy stripeEmptyHtmlTags(string $stringInput) * @method static Stringy utf8ify(string $stringInput) diff --git a/src/Stringy.php b/src/Stringy.php index c7e83b4..51cf2c4 100644 --- a/src/Stringy.php +++ b/src/Stringy.php @@ -4988,6 +4988,8 @@ public function urlEncodeRaw(): self * @return static *

Object whose $str has been converted to an URL slug.

* + * @phpstan-param ASCII::*_LANGUAGE_CODE $language + * * @psalm-suppress ImpureMethodCall :/ */ public function urlify( From 61662fdfbc9d83b6625c8dea0f40ac027e06f70e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 02:14:03 +0000 Subject: [PATCH 04/10] Polish CI and phpstan comments Agent-Logs-Url: https://github.com/voku/Stringy/sessions/c994b095-7d56-4d26-ab6a-452e4181411e Co-authored-by: voku <264695+voku@users.noreply.github.com> --- .github/workflows/ci.yml | 1 - phpstan.neon | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d02b8e9..b2b7f97 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,7 +84,6 @@ jobs: - name: Install dependencies run: | composer require --dev --no-update "phpunit/phpunit:${{ matrix.phpunit }}" - if [[ "${{ matrix.composer }}" == "lowest" ]]; then composer update --prefer-dist --no-interaction --prefer-lowest --prefer-stable fi; diff --git a/phpstan.neon b/phpstan.neon index 7ab0718..ea39177 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,4 +1,6 @@ parameters: + # Analyze against the minimum supported runtime so PHP 7.1 compatibility + # issues are caught even when phpstan itself runs on a newer PHP in CI. phpVersion: 70100 level: 8 paths: From 471b26b2f8d2e2311c001d005e9e9d57849df1e1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 02:25:27 +0000 Subject: [PATCH 05/10] Handle jsonSerialize deprecation on PHP 8.1+ Agent-Logs-Url: https://github.com/voku/Stringy/sessions/fb88e031-1c45-47d1-a3d4-7675a49c19b9 Co-authored-by: voku <264695+voku@users.noreply.github.com> --- src/JsonSerializableReturnTypeTrait.php | 43 +++++++++++++++++++++++++ src/Stringy.php | 19 +++-------- 2 files changed, 47 insertions(+), 15 deletions(-) create mode 100644 src/JsonSerializableReturnTypeTrait.php diff --git a/src/JsonSerializableReturnTypeTrait.php b/src/JsonSerializableReturnTypeTrait.php new file mode 100644 index 0000000..f7de2fa --- /dev/null +++ b/src/JsonSerializableReturnTypeTrait.php @@ -0,0 +1,43 @@ += 80100) { + eval(<<<'PHP' +namespace Stringy; + +trait JsonSerializableReturnTypeTrait +{ + /** + * Returns value which can be serialized by json_encode(). + * + * @psalm-mutation-free + * + * @return string The current value of the $str property + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return (string) $this; + } +} +PHP + ); +} else { + trait JsonSerializableReturnTypeTrait + { + /** + * Returns value which can be serialized by json_encode(). + * + * @psalm-mutation-free + * + * @return string The current value of the $str property + */ + public function jsonSerialize() + { + return (string) $this; + } + } +} diff --git a/src/Stringy.php b/src/Stringy.php index 51cf2c4..fd9ebb9 100644 --- a/src/Stringy.php +++ b/src/Stringy.php @@ -4,6 +4,8 @@ namespace Stringy; +require_once __DIR__ . '/JsonSerializableReturnTypeTrait.php'; + use Defuse\Crypto\Crypto; use voku\helper\AntiXSS; use voku\helper\ASCII; @@ -33,6 +35,8 @@ */ class Stringy implements \ArrayAccess, \Countable, \IteratorAggregate, \JsonSerializable { + use JsonSerializableReturnTypeTrait; + /** * An instance's string. * @@ -2503,21 +2507,6 @@ public function isWhitespace(): bool return $this->isBlank(); } - /** - * Returns value which can be serialized by json_encode(). - * - * EXAMPLE: - * - * - * @psalm-mutation-free - * - * @return string The current value of the $str property - */ - public function jsonSerialize() - { - return (string) $this; - } - /** * Convert the string to kebab-case. * From 087d58c0d6540d7364bf2c71a02071633998bdc4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 02:26:32 +0000 Subject: [PATCH 06/10] Rely on trait autoload for jsonSerialize shim Agent-Logs-Url: https://github.com/voku/Stringy/sessions/fb88e031-1c45-47d1-a3d4-7675a49c19b9 Co-authored-by: voku <264695+voku@users.noreply.github.com> --- src/Stringy.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Stringy.php b/src/Stringy.php index fd9ebb9..776e726 100644 --- a/src/Stringy.php +++ b/src/Stringy.php @@ -4,8 +4,6 @@ namespace Stringy; -require_once __DIR__ . '/JsonSerializableReturnTypeTrait.php'; - use Defuse\Crypto\Crypto; use voku\helper\AntiXSS; use voku\helper\ASCII; From 0611d9eee1244c00bef7490a6e4ba91c8c5b661a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 02:27:24 +0000 Subject: [PATCH 07/10] Remove eval from jsonSerialize compatibility shim Agent-Logs-Url: https://github.com/voku/Stringy/sessions/fb88e031-1c45-47d1-a3d4-7675a49c19b9 Co-authored-by: voku <264695+voku@users.noreply.github.com> --- .../JsonSerializableReturnTypeTraitPhp81.php | 24 +++++++++++++++++++ src/JsonSerializableReturnTypeTrait.php | 21 +--------------- 2 files changed, 25 insertions(+), 20 deletions(-) create mode 100644 resources/JsonSerializableReturnTypeTraitPhp81.php diff --git a/resources/JsonSerializableReturnTypeTraitPhp81.php b/resources/JsonSerializableReturnTypeTraitPhp81.php new file mode 100644 index 0000000..de330b3 --- /dev/null +++ b/resources/JsonSerializableReturnTypeTraitPhp81.php @@ -0,0 +1,24 @@ += 80100) { - eval(<<<'PHP' -namespace Stringy; - -trait JsonSerializableReturnTypeTrait -{ - /** - * Returns value which can be serialized by json_encode(). - * - * @psalm-mutation-free - * - * @return string The current value of the $str property - */ - #[\ReturnTypeWillChange] - public function jsonSerialize() - { - return (string) $this; - } -} -PHP - ); + require_once __DIR__ . '/../resources/JsonSerializableReturnTypeTraitPhp81.php'; } else { trait JsonSerializableReturnTypeTrait { From 5490dd3430a22c5e9befb60850b2d113f11d8da8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 10:47:21 +0000 Subject: [PATCH 08/10] Move PHP 8.1 trait to src, flip to default/fallback pattern Agent-Logs-Url: https://github.com/voku/Stringy/sessions/807a633f-9165-4e29-b1e9-f29041d1a64f Co-authored-by: voku <264695+voku@users.noreply.github.com> --- phpstan.neon | 4 ++++ src/JsonSerializableReturnTypeTrait.php | 6 +++--- {resources => src}/JsonSerializableReturnTypeTraitPhp81.php | 0 3 files changed, 7 insertions(+), 3 deletions(-) rename {resources => src}/JsonSerializableReturnTypeTraitPhp81.php (100%) diff --git a/phpstan.neon b/phpstan.neon index ea39177..5e251e7 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -5,6 +5,10 @@ parameters: level: 8 paths: - %currentWorkingDirectory%/src/ + excludePaths: + # This file is only loaded on PHP 8.1+ by JsonSerializableReturnTypeTrait.php. + # It defines the same trait name, so excluding it prevents a duplicate-symbol error. + - %currentWorkingDirectory%/src/JsonSerializableReturnTypeTraitPhp81.php reportUnmatchedIgnoredErrors: false ignoreErrors: # false-positive? diff --git a/src/JsonSerializableReturnTypeTrait.php b/src/JsonSerializableReturnTypeTrait.php index bb3b608..b42d8ca 100644 --- a/src/JsonSerializableReturnTypeTrait.php +++ b/src/JsonSerializableReturnTypeTrait.php @@ -4,9 +4,7 @@ namespace Stringy; -if (\PHP_VERSION_ID >= 80100) { - require_once __DIR__ . '/../resources/JsonSerializableReturnTypeTraitPhp81.php'; -} else { +if (\PHP_VERSION_ID < 80100) { trait JsonSerializableReturnTypeTrait { /** @@ -21,4 +19,6 @@ public function jsonSerialize() return (string) $this; } } +} else { + require_once __DIR__ . '/JsonSerializableReturnTypeTraitPhp81.php'; } diff --git a/resources/JsonSerializableReturnTypeTraitPhp81.php b/src/JsonSerializableReturnTypeTraitPhp81.php similarity index 100% rename from resources/JsonSerializableReturnTypeTraitPhp81.php rename to src/JsonSerializableReturnTypeTraitPhp81.php From c7a6c48f05bf21b5e333a2eab7220f4e1e273368 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 11:41:02 +0000 Subject: [PATCH 09/10] fix: exclude PHP81 trait shim from PHPUnit coverage scan Agent-Logs-Url: https://github.com/voku/Stringy/sessions/2b5f4210-b56e-44c2-bed8-9a5bb2986468 Co-authored-by: voku <264695+voku@users.noreply.github.com> --- phpunit.xml.dist | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index cddd6d0..308ffc2 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -4,6 +4,11 @@ ./src/ + + + ./src/JsonSerializableReturnTypeTraitPhp81.php + From a7d1e332322aabc7bc7356d5e89b68b39b029590 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 11:56:17 +0000 Subject: [PATCH 10/10] fix: exclude PHP81 trait shim from legacy PHPUnit coverage scan Agent-Logs-Url: https://github.com/voku/Stringy/sessions/fbd3e2e3-969a-4f17-9f59-a7fd64f8a534 Co-authored-by: voku <264695+voku@users.noreply.github.com> --- phpunit.legacy.xml.dist | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/phpunit.legacy.xml.dist b/phpunit.legacy.xml.dist index 7c87358..f6faeb7 100644 --- a/phpunit.legacy.xml.dist +++ b/phpunit.legacy.xml.dist @@ -3,6 +3,11 @@ ./src/ + + + ./src/JsonSerializableReturnTypeTraitPhp81.php +