ZeroPHP

Stringable (Fluent strings)

Stringable (Fluent strings)

Fluent string wrapper. Namespace Zero\Lib\Support\Stringable. Build with Str::of($value) or the global str($value) helper.

use Zero\Lib\Support\Str;

Str::of('users.profile-photo')
    ->replaceLast('.', '/')
    ->slug('/');             // 'users/profile-photo'

str('Hello {name}')
    ->swap(['{name}' => 'World'])
    ->upper();               // 'HELLO WORLD'

Stringable::__toString() returns the underlying string, so it casts cleanly: (string) $stringable.

Auto-proxy to Str

Stringable forwards every static Str::* call automatically: anything that takes a string as its first argument and returns a string is chainable. So all of these work:

str('foo')->upper();                  // FOO
str('Hello World')->slug();           // hello-world
str('a/b/c')->after('/');             // 'b/c'
str('foo bar')->replace('foo', 'XX'); // 'XX bar'

Methods that return non-strings (->isUuid(), ->length(), ->test(), …) return that value directly.

Fluent-only methods

Implementation: Stringable.php.

Composition

->append(...$values): self / ->prepend(...$values): self

(string) str('foo')->append('bar', 'baz'); // 'foobarbaz'
(string) str('bar')->prepend('foo');       // 'foobar'

->newLine(int $count = 1): self

(string) str('a')->newLine(2); // "a\n\n"

->basename(string $suffix = ''): self

(string) str('/a/b/c.txt')->basename();         // 'c.txt'
(string) str('/a/b/c.txt')->basename('.txt');   // 'c'

->dirname(int $levels = 1): self

(string) str('/a/b/c.txt')->dirname();      // '/a/b'
(string) str('/a/b/c.txt')->dirname(2);     // '/a'

->classBasename(): self

Last segment of a fully qualified class name.

(string) str('Foo\\Bar\\Baz')->classBasename(); // 'Baz'

->stripTags(?string $allowed = null): self

(string) str('<b>x</b>')->stripTags();          // 'x'
(string) str('<b>x</b><i>y</i>')->stripTags('<b>'); // '<b>x</b>y'

->hash(string $algorithm = 'sha256'): self

(string) str('x')->hash();       // 64-char SHA-256
(string) str('x')->hash('md5');  // '9dd4e461268c8034f5c8564e155c67a6'

Inspection (return non-self)

->exactly(string $value): bool

str('hello')->exactly('hello'); // true

->isEmpty(): bool / ->isNotEmpty(): bool

str('')->isEmpty();  // true
str('x')->isNotEmpty(); // true

->test(string $pattern): bool

str('hello')->test('/^h/'); // true

->explode(string $delimiter, int $limit = PHP_INT_MAX): array

str('a,b,c')->explode(','); // ['a', 'b', 'c']

->split(string $pattern, int $limit = -1, int $flags = 0): array

Regex split.

str('a1b2c')->split('/\d/'); // ['a', 'b', 'c']

->scan(string $format): array

sscanf shortcut.

str('SN/2020/01')->scan('SN/%d/%d'); // [2020, 1]

Conditional chains

These run $cb only when the predicate matches; otherwise $default (if given). Each returns the (possibly modified) Stringable.

->when(mixed $cond, callable $cb, ?callable $default = null): self / ->unless(...)

$cond may be a callable, in which case its return value is checked.

(string) str('foo')->when(true, fn ($s) => $s->upper());   // 'FOO'
(string) str('foo')->when(false, fn ($s) => $s->upper());  // 'foo'
(string) str('foo')->unless(false, fn ($s) => $s->upper()); // 'FOO'

->whenEmpty($cb) / ->whenNotEmpty($cb)

(string) str('')->whenEmpty(fn ($s) => $s->append('x'));    // 'x'
(string) str('x')->whenNotEmpty(fn ($s) => $s->append('y')); // 'xy'

->whenContains($needles, $cb)

(string) str('hello')->whenContains('ell', fn ($s) => $s->upper()); // 'HELLO'

->whenStartsWith($needles, $cb) / ->whenEndsWith($needles, $cb)

(string) str('hello')->whenStartsWith('he', fn ($s) => $s->upper()); // 'HELLO'
(string) str('hello')->whenEndsWith('lo', fn ($s) => $s->upper());   // 'HELLO'

->whenExactly($value, $cb) / ->whenNotExactly($value, $cb)

(string) str('foo')->whenExactly('foo', fn ($s) => $s->upper()); // 'FOO'

->whenIs($pattern, $cb)

Wildcard match (Str::is).

(string) str('foobar')->whenIs('foo*', fn ($s) => $s->upper()); // 'FOOBAR'

->whenIsAscii($cb) / ->whenIsUuid($cb) / ->whenIsUlid($cb)

(string) str('hi')->whenIsAscii(fn ($s) => $s->upper()); // 'HI'

->whenTest('/regex/', $cb)

(string) str('hi5')->whenTest('/\d/', fn ($s) => $s->upper()); // 'HI5'

Pipeline

->pipe(callable $cb): mixed

Send through a closure (return whatever you want).

str('queue')->pipe(fn ($s) => (string) $s . ':default'); // 'queue:default'

->tap(callable $cb): self

Side effect; returns the Stringable.

(string) str('foo')->tap(fn ($s) => logger($s)); // 'foo'