diff --git a/src/support/src/Str.php b/src/support/src/Str.php index 39de5637..59d0b9b5 100644 --- a/src/support/src/Str.php +++ b/src/support/src/Str.php @@ -4,13 +4,45 @@ namespace Hypervel\Support; +use BackedEnum; use Hyperf\Stringable\Str as BaseStr; 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, Stringable, or scalar value. + * + * 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|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, Stringable objects, 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..a8c968fd --- /dev/null +++ b/tests/Support/StrTest.php @@ -0,0 +1,176 @@ +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 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']); + + $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 testFromAllWithStringables(): void + { + $result = Str::fromAll([ + new TestStringable('first'), + new TestStringable('second'), + ]); + + $this->assertSame(['first', 'second'], $result); + } + + public function testFromAllWithMixedInput(): void + { + $result = Str::fromAll([ + 'users', + TestStringStatus::Active, + 42, + TestIntStatus::NotFound, + new TestStringable('dynamic-tag'), + 'legacy-tag', + ]); + + $this->assertSame(['users', 'active', '42', '404', 'dynamic-tag', '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|Stringable $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']; + yield 'stringable' => [new TestStringable('from-stringable'), 'from-stringable']; + } +} + +enum TestStringStatus: string +{ + case Active = 'active'; + case Pending = 'pending'; + case Archived = 'archived'; +} + +enum TestIntStatus: int +{ + case Ok = 200; + case NotFound = 404; + case ServerError = 500; +} + +class TestStringable implements Stringable +{ + public function __construct( + private readonly string $value, + ) { + } + + public function __toString(): string + { + return $this->value; + } +}