Str::finish

Appends a value to a string only if it does not already end with it.

Signature
Str::finish(string $value, string $cap): string

Parameters

ParameterTypeDescription
$$valuestringThe string to cap. An empty string returns just the cap.
$$capstringThe 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

Try it yourself
Loading editor…
Press Run to execute the code.

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.

Example · php
<?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.

Example · php
<?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

  • Be the first to comment on this lesson.