1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace UMA\Uuid; |
6
|
|
|
|
7
|
|
|
class CustomCombGenerator implements UuidGenerator |
8
|
|
|
{ |
9
|
|
|
/** |
10
|
|
|
* @var UuidGenerator |
11
|
|
|
*/ |
12
|
|
|
private $generator; |
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* @var int |
16
|
|
|
*/ |
17
|
|
|
private $exponent; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* @var int |
21
|
|
|
*/ |
22
|
|
|
private $epoch; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* @var int |
26
|
|
|
*/ |
27
|
|
|
private $span; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @param UuidGenerator $generator |
31
|
|
|
* @param \DateTimeImmutable $epoch |
32
|
|
|
* @param int $span |
33
|
|
|
* @param int $granularity |
34
|
|
|
* |
35
|
|
|
* @throws \InvalidArgumentException When $epoch is a date in the future. |
36
|
|
|
* @throws \InvalidArgumentException When $span is outside the [1, 16] range. |
37
|
|
|
* @throws \InvalidArgumentException When $granularity is outside the [0, 6] range. |
38
|
|
|
*/ |
39
|
8 |
|
public function __construct(UuidGenerator $generator, \DateTimeImmutable $epoch, int $span, int $granularity) |
40
|
|
|
{ |
41
|
8 |
|
if (new \DateTimeImmutable('now') < $epoch) { |
42
|
|
|
throw new \InvalidArgumentException('$epoch must be in the past. Got timestamp: ' . $epoch->getTimestamp()); |
43
|
|
|
} |
44
|
|
|
|
45
|
8 |
|
if (!\in_array($span, \range(1, 16), true)) { |
46
|
|
|
throw new \InvalidArgumentException('$span must be in the [1, 16] range. Got: ' . $span); |
47
|
|
|
} |
48
|
|
|
|
49
|
8 |
|
if (!\in_array($granularity, \range(0, 6), true)) { |
50
|
1 |
|
throw new \InvalidArgumentException('$granularity must be in the [0, 6] range. Got: ' . $granularity); |
51
|
|
|
} |
52
|
|
|
|
53
|
7 |
|
$this->generator = $generator; |
54
|
7 |
|
$this->exponent = 10 ** $granularity; |
55
|
7 |
|
$this->epoch = $epoch->getTimestamp() * $this->exponent; |
|
|
|
|
56
|
7 |
|
$this->span = 2 * $span; |
57
|
7 |
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* {@inheritdoc} |
61
|
|
|
*/ |
62
|
1 |
|
public function generate(string $name = null): Uuid |
63
|
|
|
{ |
64
|
1 |
|
$head = $this->procrust($this->timestamp()); |
65
|
1 |
|
$tail = \substr($this->generator->generate()->asBytes(), -16 + ($this->span / 2)); |
66
|
|
|
|
67
|
1 |
|
return Uuid::fromBytes($head . $tail); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Returns the exact date on which the 48 most significant bits of |
72
|
|
|
* the UUIDs will overflow for the chosen $granularity. |
73
|
|
|
* |
74
|
|
|
* The higher the granularity the better is the output of the |
75
|
|
|
* generator, but the overflow date also looms sooner. |
76
|
|
|
*/ |
77
|
7 |
|
public function getOverflowDate(): \DateTimeImmutable |
78
|
|
|
{ |
79
|
7 |
|
$maxTimestamp = (int)(($this->maxTimestamp() - $this->epoch)/$this->exponent); |
80
|
|
|
|
81
|
7 |
|
return new \DateTimeImmutable("@$maxTimestamp UTC"); |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Returns $timestamp "procrusted" to 6 bytes. |
86
|
|
|
* |
87
|
|
|
* If the timestamp is smaller than 6 bytes, leading 0 bits are appended. |
88
|
|
|
* If the timestamp is larger than 6 bytes, its least significant bits are chopped off. |
89
|
|
|
* |
90
|
|
|
* The returned string is raw binary (each character encodes 8 bits) |
91
|
|
|
* and has always the same size -- 6 bytes. |
92
|
|
|
* |
93
|
|
|
* @example '59b7d71f' => 0x000059b7d71f |
94
|
|
|
* @example '3812e6738' => 0x0003812e6738 |
95
|
|
|
* @example '230bd00838' => 0x00230bd00838 |
96
|
|
|
* @example '15e76205236' => 0x015e76205236 |
97
|
|
|
* @example 'db09d433621' => 0x0db09d433621 |
98
|
|
|
* @example '88e624a01d4c' => 0x88e624a01d4c |
99
|
|
|
* @example '558fd6e4124fb' => 0x558fd6e4124f |
100
|
|
|
*/ |
101
|
1 |
|
private function procrust(string $timestamp): string |
102
|
|
|
{ |
103
|
1 |
|
return \pack("H{$this->span}", \str_pad(\substr($timestamp, 0, $this->span), $this->span, '0', STR_PAD_LEFT)); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Returns the current unix timestamp as a hex-encoded string (that is, each character |
108
|
|
|
* encodes 4 bits) with variable precision, ranging from second to microsecond. |
109
|
|
|
* |
110
|
|
|
* The length of the string varies depending on the $granularity chosen. This is how |
111
|
|
|
* the exact same reading from microtime() looks like for all 7 possible granularity |
112
|
|
|
* levels (0 through 6): |
113
|
|
|
* |
114
|
|
|
* @example '59b7d71f' |
115
|
|
|
* @example '3812e6738' |
116
|
|
|
* @example '230bd00838' |
117
|
|
|
* @example '15e76205236' |
118
|
|
|
* @example 'db09d433621' |
119
|
|
|
* @example '88e624a01d4c' |
120
|
|
|
* @example '558fd6e4124fb' |
121
|
|
|
*/ |
122
|
8 |
|
private function timestamp(): string |
123
|
|
|
{ |
124
|
8 |
|
return \dechex((int)(\microtime(true) * $this->exponent) - $this->epoch); |
125
|
|
|
} |
126
|
|
|
|
127
|
7 |
|
private function maxTimestamp(): int |
128
|
|
|
{ |
129
|
7 |
|
return \hexdec(\str_repeat('f', \max($this->span, \strlen($this->timestamp())))); |
130
|
|
|
} |
131
|
|
|
} |
132
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.