Str::finish() and Str::start() Get a Major Speed Boost
Contributed by HamidRezaLS
Part of the framework v13.35.0 release
Laravel 13.x includes a small but meaningful performance improvement to two of the most frequently called helpers in Illuminate\Support\Str. PR #61803 rewrites Str::finish() and Str::start() to short-circuit the common cases with native str_ends_with() and str_starts_with() checks, falling back to the existing regex implementation only when duplicate caps actually need collapsing. The result is a 2.7x to 4.2x speedup in typical scenarios, with no behavioral changes for callers. If you are upgrading from v13.34.0, there is nothing to do — you simply get faster string handling in any release that includes this PR.
What changed
Both methods previously followed the same flow on every call: quote the cap with preg_quote(), run a preg_replace() with the /u modifier to strip any run of trailing (or leading) caps, then append (or prepend) exactly one cap. That regex machinery ran even when the input had no cap at all, or exactly one — which, in practice, is almost always.
The new implementation checks the cheap cases first:
- An empty cap or prefix returns the value immediately.
- If the value does not end (or start) with the cap, it is simply concatenated on — no regex involved.
- If the value ends (or starts) with exactly one cap, the value is returned untouched.
- Only when duplicates are present —
foo//,//foo,abbcbc— does execution fall through to the originalpreg_quote()+preg_replace()logic.
That last point matters for correctness: the tricky overlapping and duplicate cases are still handled by the battle-tested regex path, so the observable contract of both methods is identical.
Why it matters
str_ends_with() and str_starts_with() are lightweight, C-level byte comparisons. preg_quote() plus a Unicode-mode preg_replace() is comparatively expensive — allocation, compilation, and matching overhead on every call. In real-world usage such as URL joining, file path normalization, and PSR-4 namespace building (Str::finish($namespace, '\\')), strings either lack the cap entirely or already have exactly one, so the fast path fires nearly all of the time.
The PR author benchmarked 200,000 iterations per scenario on PHP 8.4:
| Scenario | Before | After | Speedup |
|---|---|---|---|
Str::finish (not ending) |
56.0 ms | 20.3 ms | 2.75x |
Str::finish (already ending once) |
106.8 ms | 25.5 ms | 4.18x |
Str::start (not starting) |
52.2 ms | 18.9 ms | 2.76x |
Str::start (already starting once) |
63.3 ms | 22.8 ms | 2.78x |
| Typical mixed workload | ~60 ms | ~31 ms | ~1.9x |
Notice the