Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/support/src/Str.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<BackedEnum|int|string|Stringable> $values
* @return array<string>
*/
public static function fromAll(array $values): array
{
return array_map(self::from(...), $values);
}

/**
* Determine if a given string matches a given pattern.
*
Expand Down
176 changes: 176 additions & 0 deletions tests/Support/StrTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
<?php

declare(strict_types=1);

namespace Hypervel\Tests\Support;

use BackedEnum;
use Hypervel\Support\Str;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Stringable;

/**
* @internal
* @coversNothing
*/
class StrTest extends TestCase
{
public function testFromWithString(): void
{
$this->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;
}
}
Loading