diff --git a/typo3/sysext/core/Classes/Utility/StringUtility.php b/typo3/sysext/core/Classes/Utility/StringUtility.php index f8ec246d14408bb10a762ec2b35502386c7c393c..2b69abb8f38a9c8ebcd744d59dd0fb2e06295365 100644 --- a/typo3/sysext/core/Classes/Utility/StringUtility.php +++ b/typo3/sysext/core/Classes/Utility/StringUtility.php @@ -20,6 +20,38 @@ namespace TYPO3\CMS\Core\Utility; */ class StringUtility { + /** + * Casts applicable types (string, bool, finite numeric) to string. + * + * Any other type will be replaced by the `$default` value. + * + * @param mixed $value + */ + public static function cast($value, ?string $default = null): ?string + { + if (is_string($value)) { + return $value; + } + + if (is_bool($value) || (is_numeric($value) && is_finite($value))) { + return (string)$value; + } + + return $default; + } + + /** + * Keeps only string types (filters out non-strings). + * + * Any other non-string type will be replaced by the `$default` value. + * + * @param mixed $value + */ + public static function filter($value, ?string $default = null): ?string + { + return is_string($value) ? $value : $default; + } + /** * Returns TRUE if $haystack begins with $needle. * The input string is not trimmed before and search is done case sensitive. diff --git a/typo3/sysext/core/Tests/Unit/Utility/StringUtilityTest.php b/typo3/sysext/core/Tests/Unit/Utility/StringUtilityTest.php index b8a464245310649e0307b013aa7af923130b6ffb..381f439b5582022082d1677ce38771cf144a12a5 100644 --- a/typo3/sysext/core/Tests/Unit/Utility/StringUtilityTest.php +++ b/typo3/sysext/core/Tests/Unit/Utility/StringUtilityTest.php @@ -25,6 +25,129 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase; */ class StringUtilityTest extends UnitTestCase { + /** + * @return \Generator<string, array{0: mixed}> + */ + public static function stringCastableValuesDataProvider(): \Generator + { + yield 'empty string' => ['']; + yield 'string' => ['value']; + yield 'int' => [1]; + yield 'float' => [1.2345]; + yield 'bool' => [true]; + } + + /** + * @param mixed $value + * + * @test + * @dataProvider stringCastableValuesDataProvider + */ + public function castWithStringCastableReturnsValueCastToString($value): void + { + $expected = (string)$value; + + self::assertSame($expected, StringUtility::cast($value, 'default')); + } + + /** + * @return \Generator<string, array{0: mixed}> + */ + public static function nonStringCastableValuesDataProvider(): \Generator + { + yield 'array' => [['1']]; + yield 'null' => [null]; + yield 'object' => [new \stdClass()]; + yield 'closure' => [static fn (): string => 'fn']; + // PHP interprets it as `lim(x→0) log(x) = -∞` + yield 'infinite' => [log(0)]; + // acos only supports values in range [-1; +1] + yield 'NaN' => [acos(2)]; + } + + /** + * @param mixed $value + * + * @test + * @dataProvider nonStringCastableValuesDataProvider + */ + public function castWithWithNonStringCastableReturnsDefault($value): void + { + $default = 'default'; + + self::assertSame($default, StringUtility::cast($value, $default)); + } + + /** + * @param mixed $value + * + * @test + * @dataProvider nonStringCastableValuesDataProvider + */ + public function castWithWithNonStringCastableAndNoDefaultProvidedReturnsNull($value): void + { + self::assertNull(StringUtility::cast($value)); + } + + /** + * @return \Generator<string, array{0: mixed}> + */ + public static function nonStringValueToFilterDataProvider(): \Generator + { + yield 'int' => [1]; + yield 'float' => [1.2345]; + yield 'bool' => [true]; + yield 'array' => [['1']]; + yield 'null' => [null]; + yield 'object' => [new \stdClass()]; + yield 'closure' => [static fn (): string => 'fn']; + // PHP interprets it as `lim(x→0) log(x) = -∞` + yield 'infinite' => [log(0)]; + // acos only supports values in range [-1; +1] + yield 'NaN' => [acos(2)]; + } + + /** + * @param mixed $value + * + * @test + * @dataProvider nonStringValueToFilterDataProvider + */ + public function filterForNonStringValueAndDefaultProvidedReturnsDefault($value): void + { + $default = 'default'; + + self::assertSame($default, StringUtility::filter($value, $default)); + } + + /** + * @param mixed $value + * + * @test + * @dataProvider nonStringValueToFilterDataProvider + */ + public function filterForNonStringValueAndNoDefaultProvidedReturnsNull($value): void + { + self::assertNull(StringUtility::filter($value)); + } + + /** + * @return \Generator<string, array{0: string}> + */ + public static function stringValueToFilterDataProvider(): \Generator + { + yield 'empty string' => ['']; + yield 'non-empty string' => ['value']; + } + /** + * @test + * @dataProvider stringValueToFilterDataProvider + */ + public function filterForStringValuesReturnsProvidedValue(string $value): void + { + self::assertSame($value, StringUtility::filter($value, 'some default')); + } + /** * @test */