From 099e19440ef15954072eb51db1482e61680afe9f Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Sun, 14 Dec 2025 12:46:40 +0000 Subject: [PATCH 1/2] Add Str::from helper --- src/support/src/Str.php | 27 +++++++ tests/Support/StrTest.php | 143 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 tests/Support/StrTest.php diff --git a/src/support/src/Str.php b/src/support/src/Str.php index 39de5637..eef6fa49 100644 --- a/src/support/src/Str.php +++ b/src/support/src/Str.php @@ -4,6 +4,7 @@ namespace Hypervel\Support; +use BackedEnum; use Hyperf\Stringable\Str as BaseStr; use Ramsey\Uuid\Exception\InvalidUuidStringException; use Ramsey\Uuid\Rfc4122\FieldsInterface; @@ -11,6 +12,32 @@ class Str extends BaseStr { + /** + * Get a string from a BackedEnum or return the value as-is. + * + * Useful for APIs that accept either a string or an enum, such as + * cache tags, session keys, or Sanctum token abilities. + */ + public static function from(string|int|BackedEnum $value): string + { + if ($value instanceof BackedEnum) { + return (string) $value->value; + } + + return (string) $value; + } + + /** + * Get strings from an array of BackedEnums or scalar values. + * + * @param array $values + * @return array + */ + public static function fromAll(array $values): array + { + return array_map(self::from(...), $values); + } + /** * Determine if a given string matches a given pattern. * diff --git a/tests/Support/StrTest.php b/tests/Support/StrTest.php new file mode 100644 index 00000000..a25d6fe0 --- /dev/null +++ b/tests/Support/StrTest.php @@ -0,0 +1,143 @@ +assertSame('hello', Str::from('hello')); + $this->assertSame('', Str::from('')); + $this->assertSame('with spaces', Str::from('with spaces')); + } + + public function testFromWithInt(): void + { + $result = Str::from(42); + + $this->assertIsString($result); + $this->assertSame('42', $result); + $this->assertSame('0', Str::from(0)); + $this->assertSame('-1', Str::from(-1)); + } + + public function testFromWithStringBackedEnum(): void + { + $this->assertSame('active', Str::from(TestStringStatus::Active)); + $this->assertSame('pending', Str::from(TestStringStatus::Pending)); + $this->assertSame('archived', Str::from(TestStringStatus::Archived)); + } + + public function testFromWithIntBackedEnum(): void + { + $result = Str::from(TestIntStatus::Ok); + + $this->assertIsString($result); + $this->assertSame('200', $result); + $this->assertSame('404', Str::from(TestIntStatus::NotFound)); + $this->assertSame('500', Str::from(TestIntStatus::ServerError)); + } + + public function testFromAllWithStrings(): void + { + $result = Str::fromAll(['users', 'posts', 'comments']); + + $this->assertSame(['users', 'posts', 'comments'], $result); + } + + public function testFromAllWithEnums(): void + { + $result = Str::fromAll([ + TestStringStatus::Active, + TestStringStatus::Pending, + TestStringStatus::Archived, + ]); + + $this->assertSame(['active', 'pending', 'archived'], $result); + } + + public function testFromAllWithIntBackedEnums(): void + { + $result = Str::fromAll([ + TestIntStatus::Ok, + TestIntStatus::NotFound, + ]); + + $this->assertSame(['200', '404'], $result); + } + + public function testFromAllWithMixedInput(): void + { + $result = Str::fromAll([ + 'users', + TestStringStatus::Active, + 42, + TestIntStatus::NotFound, + 'legacy-tag', + ]); + + $this->assertSame(['users', 'active', '42', '404', 'legacy-tag'], $result); + } + + public function testFromAllWithEmptyArray(): void + { + $this->assertSame([], Str::fromAll([])); + } + + public function testFromAllPreservesArrayKeys(): void + { + $result = Str::fromAll([ + 'first' => TestStringStatus::Active, + 'second' => 'manual', + 0 => TestIntStatus::Ok, + ]); + + $this->assertSame([ + 'first' => 'active', + 'second' => 'manual', + 0 => '200', + ], $result); + } + + #[DataProvider('fromDataProvider')] + public function testFromWithDataProvider(string|int|BackedEnum $input, string $expected): void + { + $this->assertSame($expected, Str::from($input)); + } + + public static function fromDataProvider(): iterable + { + yield 'string value' => ['hello', 'hello']; + yield 'empty string' => ['', '']; + yield 'integer' => [123, '123']; + yield 'zero' => [0, '0']; + yield 'negative integer' => [-42, '-42']; + yield 'string-backed enum' => [TestStringStatus::Active, 'active']; + yield 'int-backed enum' => [TestIntStatus::Ok, '200']; + } +} + +enum TestStringStatus: string +{ + case Active = 'active'; + case Pending = 'pending'; + case Archived = 'archived'; +} + +enum TestIntStatus: int +{ + case Ok = 200; + case NotFound = 404; + case ServerError = 500; +} From 280c63edcf78d23473e0faa6049238c6768b80b6 Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Thu, 18 Dec 2025 11:54:20 +0000 Subject: [PATCH 2/2] Add Stringable support --- src/support/src/Str.php | 15 ++++++++++----- tests/Support/StrTest.php | 37 +++++++++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/support/src/Str.php b/src/support/src/Str.php index eef6fa49..59d0b9b5 100644 --- a/src/support/src/Str.php +++ b/src/support/src/Str.php @@ -9,28 +9,33 @@ use Ramsey\Uuid\Exception\InvalidUuidStringException; use Ramsey\Uuid\Rfc4122\FieldsInterface; use Ramsey\Uuid\UuidFactory; +use Stringable; class Str extends BaseStr { /** - * Get a string from a BackedEnum or return the value as-is. + * Get a string from a BackedEnum, Stringable, or scalar value. * - * Useful for APIs that accept either a string or an enum, such as + * Useful for APIs that accept mixed identifier types, such as * cache tags, session keys, or Sanctum token abilities. */ - public static function from(string|int|BackedEnum $value): string + public static function from(string|int|BackedEnum|Stringable $value): string { if ($value instanceof BackedEnum) { return (string) $value->value; } + if ($value instanceof Stringable) { + return (string) $value; + } + return (string) $value; } /** - * Get strings from an array of BackedEnums or scalar values. + * Get strings from an array of BackedEnums, Stringable objects, or scalar values. * - * @param array $values + * @param array $values * @return array */ public static function fromAll(array $values): array diff --git a/tests/Support/StrTest.php b/tests/Support/StrTest.php index a25d6fe0..a8c968fd 100644 --- a/tests/Support/StrTest.php +++ b/tests/Support/StrTest.php @@ -8,6 +8,7 @@ use Hypervel\Support\Str; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; +use Stringable; /** * @internal @@ -49,6 +50,13 @@ public function testFromWithIntBackedEnum(): void $this->assertSame('500', Str::from(TestIntStatus::ServerError)); } + public function testFromWithStringable(): void + { + $this->assertSame('stringable-value', Str::from(new TestStringable('stringable-value'))); + $this->assertSame('', Str::from(new TestStringable(''))); + $this->assertSame('with spaces', Str::from(new TestStringable('with spaces'))); + } + public function testFromAllWithStrings(): void { $result = Str::fromAll(['users', 'posts', 'comments']); @@ -77,6 +85,16 @@ public function testFromAllWithIntBackedEnums(): void $this->assertSame(['200', '404'], $result); } + public function testFromAllWithStringables(): void + { + $result = Str::fromAll([ + new TestStringable('first'), + new TestStringable('second'), + ]); + + $this->assertSame(['first', 'second'], $result); + } + public function testFromAllWithMixedInput(): void { $result = Str::fromAll([ @@ -84,10 +102,11 @@ public function testFromAllWithMixedInput(): void TestStringStatus::Active, 42, TestIntStatus::NotFound, + new TestStringable('dynamic-tag'), 'legacy-tag', ]); - $this->assertSame(['users', 'active', '42', '404', 'legacy-tag'], $result); + $this->assertSame(['users', 'active', '42', '404', 'dynamic-tag', 'legacy-tag'], $result); } public function testFromAllWithEmptyArray(): void @@ -111,7 +130,7 @@ public function testFromAllPreservesArrayKeys(): void } #[DataProvider('fromDataProvider')] - public function testFromWithDataProvider(string|int|BackedEnum $input, string $expected): void + public function testFromWithDataProvider(string|int|BackedEnum|Stringable $input, string $expected): void { $this->assertSame($expected, Str::from($input)); } @@ -125,6 +144,7 @@ public static function fromDataProvider(): iterable yield 'negative integer' => [-42, '-42']; yield 'string-backed enum' => [TestStringStatus::Active, 'active']; yield 'int-backed enum' => [TestIntStatus::Ok, '200']; + yield 'stringable' => [new TestStringable('from-stringable'), 'from-stringable']; } } @@ -141,3 +161,16 @@ enum TestIntStatus: int case NotFound = 404; case ServerError = 500; } + +class TestStringable implements Stringable +{ + public function __construct( + private readonly string $value, + ) { + } + + public function __toString(): string + { + return $this->value; + } +}