Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Increase Short URL key entropy to decrease likelihood of key collisions #291

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
27 changes: 24 additions & 3 deletions src/Classes/KeyGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,24 @@ public function __construct(Hashids $hashids)
*/
public function generateRandom(): string
{
$ID = $this->getLastInsertedID();
$id = $this->getLastInsertedID();

$minKeyLength = $this->getKeyLength();

$currentLength = strlen((string) $id);

if ($currentLength > $minKeyLength) {
$minKeyLength = $currentLength;
}

do {
$ID++;
$key = $this->hashids->encode($ID);
$id++;

$key = $this->hashids->encodeHex(
str_pad((string) $id, $minKeyLength - 1, uniqid())
);

$minKeyLength++;
} while (ShortURL::where('url_key', $key)->exists());

return $key;
Expand All @@ -52,6 +65,14 @@ public function generateKeyUsing(int $seed = null): string
: $this->generateRandom();
}

/**
* Get the minimum length of the Short URL key to generate.
*/
protected function getKeyLength(): int
{
return config('short-url.key_length');
}

/**
* Get the ID of the last inserted ShortURL. This is done so that we can predict
* what the ID of the ShortURL that will be inserted will be called. From doing
Expand Down
12 changes: 12 additions & 0 deletions tests/Unit/Classes/BuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,18 @@ public function random_url_key_is_generated_if_one_is_not_explicitly_defined():
$this->assertSame(5, strlen($shortURL->url_key));
}

#[Test]
public function short_url_key_length_is_used(): void
{
Config::set('short-url.key_length', 3);

$builder = app(Builder::class);
$shortURL = $builder->destinationUrl('https://domain.com')->make();

$this->assertNotNull($shortURL->url_key);
$this->assertSame(3, strlen($shortURL->url_key));
}

#[Test]
public function short_url_can_be_created_and_stored_in_the_database(): void
{
Expand Down
42 changes: 42 additions & 0 deletions tests/Unit/Classes/KeyGeneratorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace AshAllenDesign\ShortURL\Tests\Unit\Classes;

use AshAllenDesign\ShortURL\Classes\KeyGenerator;
use AshAllenDesign\ShortURL\Models\ShortURL;
use AshAllenDesign\ShortURL\Tests\Unit\TestCase;
use PHPUnit\Framework\Attributes\Test;

final class KeyGeneratorTest extends TestCase
{
#[Test]
public function generates_expected_random_keys_based_on_last_inserted_id(): void
{
$generator = app(KeyGenerator::class);

$first = $generator->generateRandom();
$second = $generator->generateRandom();

ShortURL::factory()->create();

$third = $generator->generateRandom();

$this->assertEquals('NKYWm', $first);
$this->assertEquals('NKYWm', $second);
$this->assertEquals('N3LMR', $third);
}

#[Test]
public function generates_expected_key_using_seed(): void
{
$generator = app(KeyGenerator::class);

$first = $generator->generateKeyUsing(123);
$second = $generator->generateKeyUsing(123);
$third = $generator->generateKeyUsing(1234);

$this->assertEquals('4ZRw4', $first);
$this->assertEquals('4ZRw4', $second);
$this->assertEquals('4Ow94', $third);
}
}
Loading