|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
declare(strict_types=1); |
|
4
|
|
|
|
|
5
|
|
|
namespace TinyID; |
|
6
|
|
|
|
|
7
|
|
|
use BCMathExtended\BC; |
|
8
|
|
|
use InvalidArgumentException; |
|
9
|
|
|
|
|
10
|
|
|
class TinyID |
|
11
|
|
|
{ |
|
12
|
|
|
private array $dictionary; |
|
13
|
|
|
private int $dictionaryLength; |
|
14
|
|
|
|
|
15
|
9 |
|
public function __construct(string $dictionary) |
|
16
|
|
|
{ |
|
17
|
9 |
|
$dictionaryLength = mb_strlen($dictionary, 'UTF-8'); |
|
18
|
9 |
|
if ($dictionaryLength <= 1) { |
|
19
|
1 |
|
throw new InvalidArgumentException('dictionary too short'); |
|
20
|
|
|
} |
|
21
|
|
|
|
|
22
|
8 |
|
$this->dictionary = $this->stringSplit($dictionary); |
|
23
|
8 |
|
$this->dictionaryLength = count(array_unique($this->dictionary)); |
|
24
|
|
|
|
|
25
|
8 |
|
if ($dictionaryLength !== $this->dictionaryLength) { |
|
26
|
1 |
|
throw new InvalidArgumentException('dictionary contains duplicated characters'); |
|
27
|
|
|
} |
|
28
|
7 |
|
} |
|
29
|
|
|
|
|
30
|
8 |
|
private function stringSplit(string $value): array |
|
31
|
|
|
{ |
|
32
|
8 |
|
return (array)preg_split('//u', $value, -1, PREG_SPLIT_NO_EMPTY); |
|
33
|
|
|
} |
|
34
|
|
|
|
|
35
|
6 |
|
public function encode(string $value): string |
|
36
|
|
|
{ |
|
37
|
6 |
|
if (BC::COMPARE_RIGHT_GRATER === BC::comp($value, '0')) { |
|
38
|
1 |
|
throw new InvalidArgumentException('cannot encode negative number'); |
|
39
|
|
|
} |
|
40
|
|
|
|
|
41
|
5 |
|
$encoded = ''; |
|
42
|
|
|
do { |
|
43
|
5 |
|
$encoded = $this->dictionary[BC::mod($value, (string)$this->dictionaryLength, 0)] . $encoded; |
|
44
|
5 |
|
$value = BC::div($value, (string)$this->dictionaryLength, 0); |
|
45
|
5 |
|
} while ($value); |
|
46
|
|
|
|
|
47
|
5 |
|
return $encoded; |
|
48
|
|
|
} |
|
49
|
|
|
|
|
50
|
6 |
|
public function decode(string $value): string |
|
51
|
|
|
{ |
|
52
|
6 |
|
$charsToPosition = array_flip($this->dictionary); |
|
53
|
6 |
|
$out = '0'; |
|
54
|
6 |
|
foreach (array_reverse($this->stringSplit($value)) as $pos => $tmp) { |
|
55
|
6 |
|
if (!isset($charsToPosition[$tmp])) { |
|
56
|
1 |
|
throw new InvalidArgumentException('cannot decode string with characters not in dictionary'); |
|
57
|
|
|
} |
|
58
|
5 |
|
$out = BC::add($out, BC::mul((string)$charsToPosition[$tmp], BC::pow((string)$this->dictionaryLength, (string)$pos, 0), 0), 0); |
|
59
|
|
|
} |
|
60
|
|
|
|
|
61
|
5 |
|
return $out; |
|
62
|
|
|
} |
|
63
|
|
|
} |