Str::of(): Chaining String Transformations Without the Ternary Soup
Here's a small piece of code that shows up in almost every Laravel app that has slugs: take a user-submitted title, fall back to a default if it's empty, trim it, slugify it, and cap the length so it doesn't blow out a column or a URL. Written the naive way, with nested global functions, it looks like this:
$slug = Str::limit(
Str::slug(trim($title !== '' ? $title : 'untitled-article')),
50,
''
);
It works. It's also hard to read in one pass — you have to parse it from the inside out, and if you need to add one more step (say, lowercasing something before slugifying), you're nesting another function call around the whole thing.
The fluent version
Str::of() wraps a string in a Stringable object and lets you chain methods left to right, in the order they actually execute:
$slug = Str::of($title)
->trim()
->when(fn ($str) => $str->isEmpty(), fn ($str) => $str->append('untitled-article'))
->slug()
->limit(50, '');
Every step reads in the order it happens: trim, fall back if empty, slugify, truncate. No inside-out parsing, and adding a step is just adding another line to the chain.
Useful chainable methods
A handful of Stringable methods pull their weight in everyday app code:
->when() / ->unless() — conditionally apply a transformation without breaking out of the chain:
Str::of($article->title)
->when($article->isDraft(), fn ($str) => $str->prepend('[Draft] '))
->headline();
->headline() — converts a slug-ish or snake-ish string into title case, handy for turning stored values into display names:
Str::of('senior_php_developer')->headline();
// "Senior Php Developer"
->squish() — collapses repeated whitespace (including leading/trailing) down to single spaces, which is exactly what you want on freeform text pasted from somewhere else:
Str::of(" PHP Architect Conference ")->squish();
// "PHP Architect Conference"
->test() — runs a regex against the string and returns a boolean, useful for quick validation without reaching for preg_match directly:
Str::of($employeeId)->test('/^PA-\d{4}$/');
// true for "PA-2026", false otherwise
->markdown() — renders Markdown to HTML inline, useful for short bits of user-authored content like a bio field:
Str::of($author->bio)->markdown();
It's a real object, and Blade knows what to do with it
Str::of() returns an instance of Illuminate\Support\Stringable, and it implements Htmlable and __toString(), so you can echo it directly in a Blade view without manually casting it back to a string:
<h1>{{ Str::of($author->name)->headline() }}</h1>
<span class="badge">
{{ Str::of($subscription->plan)->replace('_', ' ')->title() }}
</span>
You can also build a small helper on a model that returns a Stringable, so views get the chain "for free" without repeating the transformation logic in every template:
class Author extends Model
{
public function displayName(): Stringable
{
return Str::of($this->first_name . ' ' . $this->last_name)
->squish()
->title();
}
}
Mapping collections through it
If you've got a collection of raw strings — tag names pulled from a form, category slugs from a query — map them through Str::of() instead of writing a closure full of nested function calls:
$tags = collect($request->input('tags'))
->map(fn ($tag) => Str::of($tag)->trim()->lower()->slug()->toString())
->unique()
->values();
This is the same idea as an Eloquent string cast: you're centralizing the "how do I normalize this string" logic in one readable place instead of scattering trim()/strtolower()/preg_replace() calls across controllers. Str::of() doesn't replace casts or accessors, but it's frequently the cleanest way to write the transformation logic that goes inside one.
Next time you catch yourself nesting three string functions inside each other, reach for Str::of() instead. It costs nothing and reads like a sentence.