Str::finish
Appends a value to a string only if it does not already end with it.
Str::finish(string $value, string $cap): stringParameters
| Parameter | Type | Description |
|---|---|---|
$$value | string | The string to cap. An empty string returns just the cap. |
$$cap | string | The value the result must end with. It is regex-quoted internally, so punctuation is safe to pass. |
Return value
stringThe value guaranteed to end with exactly one instance of $cap. Any existing run of trailing $cap values is collapsed to one.
Errors
Nothing.
Str::finish is idempotent by construction: it strips every trailing occurrence of $cap with a regex and then appends exactly one. Calling it twice gives the same answer as calling it once.
When to reach for it
Reach for it whenever a value arrives from config or user input and downstream code assumes a specific ending. Base URLs and disk roots are the classic case: Str::finish($base, '/') . ltrim($path, '/') joins two segments without producing // or gluing them together, no matter how the operator typed the config value. The same applies to namespace prefixes ending in a backslash and to sentences that need a full stop.
What trips people up
It normalises rather than appends. Because it removes a whole run of trailing caps first, Str::finish('Loading...', '.') is 'Loading.' — the ellipsis is destroyed. Anyone using it to guarantee terminal punctuation on user-written text will eventually get a bug report about missing ellipses.
Str::finish('', '/') returns '/', so a blank config value becomes a root path rather than staying empty. Check for empty separately if that distinction matters. The counterpart is Str::start(), which does the same job at the front of the string.
Example
When to use it
- Normalise a base URL from config so joining a path never produces a double slash.
- Guarantee a directory prefix ends in a separator before concatenating a filename.
- Ensure a generated namespace prefix ends with a single backslash.
Frequently asked questions
How do I add a trailing slash only if it is missing in Laravel?
Str::finish($value, '/') appends a slash unless the value already ends with one, and collapses any run of trailing slashes to a single one.
Is Str::finish safe to call repeatedly?
Yes, it is idempotent. It strips every trailing copy of the cap before appending exactly one, so the second and subsequent calls return the same string.
Why did Str::finish turn my ellipsis into a single dot?
Because it removes all trailing occurrences of the cap before adding one back, so 'Loading...' with a '.' cap becomes 'Loading.'. Use a plain conditional concatenation for prose.
More examples
Joining a base URL to a path
All three config spellings converge on one URL. Str::finish handles the missing and the duplicated separator, and ltrim handles the leading slash on the path, so neither side needs to know how the other was written.
<?php
use Illuminate\Support\Str;
function joinUrl(string $base, string $path): string
{
return Str::finish($base, '/') . ltrim($path, '/');
}
$bases = [
'https://api.example.com',
'https://api.example.com/',
'https://api.example.com///',
];
foreach ($bases as $base) {
echo joinUrl($base, '/v1/users'), "\n";
}
// https://api.example.com/v1/users (three times)Idempotence, and where it bites
The first block shows the intended idempotence for path building. The second shows why it is the wrong tool for punctuating prose: it only collapses runs of the cap itself, so an ellipsis is flattened while an exclamation mark gets a full stop bolted on.
<?php
use Illuminate\Support\Str;
$path = 'uploads';
// Safe to call any number of times.
var_dump(Str::finish(Str::finish(Str::finish($path, '/'), '/'), '/'));
// string(8) "uploads/"
// But on prose, the collapse is destructive.
$sentences = ['Done', 'Done.', 'Loading...', 'Really?!'];
foreach ($sentences as $s) {
echo Str::finish($s, '.'), "\n";
}
// Done.
// Done.
// Loading. <- ellipsis destroyed
// Really?!. <- '!' is not the cap, so a stop is simply appended
Discussion