Short-code generator with collision retry
Generate unambiguous random short codes and rely on the unique index to detect collisions, retrying and growing the length when a space fills up.
LaravelUtilitiesshort-codeuniqueretryrandom
Code
<?php
namespace App\Support;
final class CodeGenerator
{
/** 32 symbols, no 0/1/l/o so codes survive being read aloud or printed. */
private const ALPHABET = 'abcdefghijkmnpqrstuvwxyz23456789';
/** Words that must never be handed out because they are routes. */
private const RESERVED = ['admin', 'api', 'login', 'logout', 'app', 'static'];
public static function make(int $length = 6): string
{
do {
$code = '';
for ($i = 0; $i < $length; $i++) {
$code .= self::ALPHABET[random_int(0, 31)];
}
} while (in_array($code, self::RESERVED, true));
return $code;
}
}
<?php
namespace App\Services;
use App\Models\Link;
use App\Support\CodeGenerator;
use Illuminate\Database\UniqueConstraintViolationException;
class ShortLinkCreator
{
public function create(string $url, int $length = 6, int $maxAttempts = 12): Link
{
for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
try {
// No existence check: the unique index is the only authority.
return Link::create(['code' => CodeGenerator::make($length), 'url' => $url]);
} catch (UniqueConstraintViolationException) {
// Five clashes in a row suggests this length is crowded: grow it.
if ($attempt % 5 === 0) {
$length++;
}
}
}
throw new \RuntimeException('Could not allocate a unique short code.');
}
}
How it works
What it solves
Short URLs, invite codes and coupon codes all need a random token that is short, easy to read aloud and unique. Checking "does this code exist?" before inserting is a race; two requests can pick the same code between the check and the write.
How it works
CodeGenerator draws characters from a 32 symbol alphabet with the lookalikes removed (no 0, 1, l or o), using random_int so the output is cryptographically random. It also rejects reserved words, such as route names, before they ever reach the database.
ShortLinkCreator does not check for existence at all. It just inserts and lets the unique index decide. A duplicate raises UniqueConstraintViolationException, which the loop catches and retries with a fresh code. After a few failures in a row the length grows by one character, so a nearly full space heals itself instead of looping forever. A hard cap on attempts turns a truly exhausted space into a clear exception.
This keeps the happy path to a single query and makes correctness the database's job.
Adapting it
Change the alphabet or starting length, point the creator at your own model, and add case sensitivity by widening the alphabet and the column collation together.
Pitfalls
- The column needs a unique index with a case-insensitive collation if your alphabet is lowercase only, otherwise users can type variants.
- Do not wrap the insert in an outer transaction on PostgreSQL; the failed statement aborts it.
How to use it
- Create a
linkstable with a uniquecodecolumn and aurlcolumn. - Copy both classes into
app/Supportandapp/Services. - Create a link:
app(ShortLinkCreator::class)->create('https://example.com/long/path'). - Extend
CodeGenerator::RESERVEDwith your own route prefixes. - Resolve a code with
Link::where('code', $code)->firstOrFail()in your redirect route.
Licence: DEV3 Snippet Licence. Free to use, change and ship in personal and commercial projects. Attribution is appreciated, not required. Please do not redistribute snippets as a collection.