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
|
|
|
} |