1 | <?php |
||
2 | |||
3 | /* |
||
0 ignored issues
–
show
Coding Style
introduced
by
![]() |
|||
4 | * This file is part of SoUuid. |
||
5 | * (c) Fabrice de Stefanis / https://github.com/fab2s/SoUuid |
||
6 | * This source file is licensed under the MIT license which you will |
||
7 | * find in the LICENSE file or at https://opensource.org/licenses/MIT |
||
8 | */ |
||
9 | |||
10 | namespace fab2s\SoUuid; |
||
11 | |||
12 | /** |
||
13 | * class SoUuid |
||
14 | */ |
||
0 ignored issues
–
show
|
|||
15 | class SoUuid implements SoUuidInterface, SoUuidFactoryInterface |
||
16 | { |
||
17 | /** |
||
18 | * The identifier separator, used to handle variable length |
||
19 | */ |
||
20 | const IDENTIFIER_SEPARATOR = "\0"; |
||
21 | |||
22 | /** |
||
23 | * String format |
||
24 | */ |
||
25 | const UUID_REGEX = '`^[0-9a-f]{14}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{6}$`i'; |
||
26 | |||
27 | /** |
||
28 | * @var string |
||
29 | */ |
||
30 | protected $uuid; |
||
0 ignored issues
–
show
|
|||
31 | |||
32 | /** |
||
33 | * @var \DateTimeImmutable |
||
34 | */ |
||
35 | protected $dateTime; |
||
0 ignored issues
–
show
|
|||
36 | |||
37 | /** |
||
38 | * @var array |
||
39 | */ |
||
40 | protected $decoded; |
||
0 ignored issues
–
show
|
|||
41 | |||
42 | /** |
||
43 | * @var string |
||
44 | */ |
||
45 | protected $identifier; |
||
0 ignored issues
–
show
|
|||
46 | |||
47 | /** |
||
48 | * @var string |
||
49 | */ |
||
50 | protected $string; |
||
0 ignored issues
–
show
|
|||
51 | |||
52 | /** |
||
53 | * @var string |
||
54 | */ |
||
55 | protected $microTime; |
||
0 ignored issues
–
show
|
|||
56 | |||
57 | /** |
||
58 | * @var string |
||
59 | */ |
||
60 | protected $base62; |
||
0 ignored issues
–
show
|
|||
61 | |||
62 | /** |
||
63 | * @var string |
||
64 | */ |
||
65 | protected $base36; |
||
0 ignored issues
–
show
|
|||
66 | |||
67 | /** |
||
68 | * SoUuid constructor. |
||
69 | * |
||
70 | * @param string $uuid |
||
0 ignored issues
–
show
|
|||
71 | */ |
||
72 | protected function __construct(string $uuid) |
||
0 ignored issues
–
show
|
|||
73 | { |
||
74 | $this->uuid = (string) $uuid; |
||
75 | } |
||
0 ignored issues
–
show
|
|||
76 | |||
77 | /** |
||
78 | * @param string|int|null $identifier |
||
0 ignored issues
–
show
|
|||
79 | * |
||
80 | * @throws \Exception |
||
0 ignored issues
–
show
|
|||
81 | * |
||
82 | * @return SoUuidInterface |
||
83 | */ |
||
84 | public static function generate($identifier = null): SoUuidInterface |
||
0 ignored issues
–
show
|
|||
85 | { |
||
86 | // 7 bit micro-time |
||
87 | $uuid = static::microTimeBin(); |
||
88 | // 6 bytes identifier |
||
89 | $uuid .= static::encodeIdentifier($identifier); |
||
90 | // 3 random bytes (2^24 = 16 777 216 combinations) |
||
91 | $uuid .= random_bytes(3); |
||
92 | |||
93 | return new static($uuid); |
||
94 | } |
||
0 ignored issues
–
show
|
|||
95 | |||
96 | /** |
||
97 | * @param string $uuidString |
||
0 ignored issues
–
show
|
|||
98 | * |
||
99 | * @throws \InvalidArgumentException |
||
0 ignored issues
–
show
|
|||
100 | * |
||
101 | * @return SoUuidInterface |
||
102 | */ |
||
103 | public static function fromString(string $uuidString): SoUuidInterface |
||
104 | { |
||
105 | if (!preg_match(static::UUID_REGEX, $uuidString)) { |
||
106 | throw new \InvalidArgumentException('Uuid String is not valid'); |
||
107 | } |
||
108 | |||
109 | return new static(hex2bin(str_replace('-', '', $uuidString))); |
||
110 | } |
||
0 ignored issues
–
show
|
|||
111 | |||
112 | /** |
||
113 | * @param string $uuidString |
||
0 ignored issues
–
show
|
|||
114 | * |
||
115 | * @throws \InvalidArgumentException |
||
0 ignored issues
–
show
|
|||
116 | * |
||
117 | * @return SoUuidInterface |
||
118 | */ |
||
119 | public static function fromHex(string $uuidString): SoUuidInterface |
||
120 | { |
||
121 | if (!preg_match('`^[0-9a-f]{32}$`i', $uuidString)) { |
||
122 | throw new \InvalidArgumentException('Uuid Hex String is not valid'); |
||
123 | } |
||
124 | |||
125 | return new static(hex2bin($uuidString)); |
||
126 | } |
||
0 ignored issues
–
show
|
|||
127 | |||
128 | /** |
||
129 | * @param string $uuidString |
||
0 ignored issues
–
show
|
|||
130 | * |
||
131 | * @throws \InvalidArgumentException |
||
0 ignored issues
–
show
|
|||
132 | * |
||
133 | * @return SoUuidInterface |
||
134 | */ |
||
135 | public static function fromBytes(string $uuidString): SoUuidInterface |
||
136 | { |
||
137 | if (strlen($uuidString) !== 16) { |
||
138 | throw new \InvalidArgumentException('Uuid Binary String must be of length 16'); |
||
139 | } |
||
140 | |||
141 | return new static($uuidString); |
||
142 | } |
||
0 ignored issues
–
show
|
|||
143 | |||
144 | /** |
||
145 | * @param string $uuidString |
||
0 ignored issues
–
show
|
|||
146 | * |
||
147 | * @throws \InvalidArgumentException |
||
0 ignored issues
–
show
|
|||
148 | * |
||
149 | * @return SoUuidInterface |
||
150 | */ |
||
151 | public static function fromBase62(string $uuidString): SoUuidInterface |
||
152 | { |
||
153 | if (!ctype_alnum($uuidString)) { |
||
154 | throw new \InvalidArgumentException('Uuid Base62 String must composed of a-zA-z0-9 exclusively'); |
||
0 ignored issues
–
show
|
|||
155 | } |
||
156 | |||
157 | $hex = gmp_strval(gmp_init($uuidString, 62), 16); |
||
158 | |||
159 | return new static(hex2bin(str_pad($hex, 32, '0', STR_PAD_LEFT))); |
||
160 | } |
||
0 ignored issues
–
show
|
|||
161 | |||
162 | /** |
||
163 | * @param string $uuidString |
||
0 ignored issues
–
show
|
|||
164 | * |
||
165 | * @throws \InvalidArgumentException |
||
0 ignored issues
–
show
|
|||
166 | * |
||
167 | * @return SoUuidInterface |
||
168 | */ |
||
169 | public static function fromBase36(string $uuidString): SoUuidInterface |
||
170 | { |
||
171 | if (!ctype_alnum($uuidString)) { |
||
172 | throw new \InvalidArgumentException('Uuid Base36 String must composed of a-z0-9 exclusively'); |
||
0 ignored issues
–
show
|
|||
173 | } |
||
174 | |||
175 | $hex = gmp_strval(gmp_init($uuidString, 36), 16); |
||
176 | |||
177 | return new static(hex2bin(str_pad($hex, 32, '0', STR_PAD_LEFT))); |
||
178 | } |
||
0 ignored issues
–
show
|
|||
179 | |||
180 | /** |
||
181 | * @throws \Exception |
||
0 ignored issues
–
show
|
|||
182 | * |
||
183 | * @return array |
||
184 | */ |
||
185 | public function decode(): array |
||
186 | { |
||
187 | if ($this->decoded === null) { |
||
188 | $idLen = strlen($this->getIdentifier()); |
||
189 | $this->decoded = [ |
||
190 | 'microTime' => $this->getMicroTime(), |
||
0 ignored issues
–
show
|
|||
191 | 'dateTime' => $this->getDateTime(), |
||
0 ignored issues
–
show
|
|||
192 | 'identifier' => $this->getIdentifier(), |
||
0 ignored issues
–
show
|
|||
193 | 'rand' => bin2hex(substr($this->uuid, $idLen ? 7 + $idLen : 8)), |
||
0 ignored issues
–
show
|
|||
194 | ]; |
||
0 ignored issues
–
show
|
|||
195 | } |
||
196 | |||
197 | return $this->decoded; |
||
198 | } |
||
0 ignored issues
–
show
|
|||
199 | |||
200 | /** |
||
201 | * @return string |
||
202 | */ |
||
203 | public function getBytes(): string |
||
204 | { |
||
205 | return $this->uuid; |
||
206 | } |
||
0 ignored issues
–
show
|
|||
207 | |||
208 | /** |
||
209 | * @return string |
||
210 | */ |
||
211 | public function getHex(): string |
||
212 | { |
||
213 | return bin2hex($this->uuid); |
||
214 | } |
||
0 ignored issues
–
show
|
|||
215 | |||
216 | /** |
||
217 | * @return string |
||
218 | */ |
||
219 | public function getIdentifier(): string |
||
220 | { |
||
221 | if ($this->identifier === null) { |
||
222 | $this->identifier = substr($this->uuid, 7, 6); |
||
223 | $identifierNullPos = strpos($this->identifier, static::IDENTIFIER_SEPARATOR); |
||
224 | if ($identifierNullPos !== false) { |
||
225 | // set to empty string if the identifier was random |
||
226 | // as it starts with static::IDENTIFIER_SEPARATOR |
||
227 | $this->identifier = substr($this->identifier, 0, $identifierNullPos); |
||
228 | } |
||
229 | } |
||
230 | |||
231 | return $this->identifier; |
||
232 | } |
||
0 ignored issues
–
show
|
|||
233 | |||
234 | /** |
||
235 | * The string format does not match RFC pattern to prevent any confusion in this form. |
||
236 | * It's still mimicking the 36 char length to match the storage requirement of the RFC |
||
237 | * in every way : 36 char string or 16 bytes binary string, also being the most efficient |
||
238 | * |
||
239 | * @return string |
||
240 | */ |
||
241 | public function getString(): string |
||
242 | { |
||
243 | if ($this->string === null) { |
||
244 | // microsecond epoch - 2/6 id bytes - 4/6 id bytes - 6/6 id bytes - 3 random bytes |
||
245 | $hex = $this->getHex(); |
||
246 | $this->string = substr($hex, 0, 14) . '-' . |
||
247 | substr($hex, 14, 4) . '-' . |
||
248 | substr($hex, 18, 4) . '-' . |
||
249 | substr($hex, 22, 4) . '-' . |
||
250 | substr($hex, 26); |
||
251 | } |
||
252 | |||
253 | return $this->string; |
||
254 | } |
||
0 ignored issues
–
show
|
|||
255 | |||
256 | /** |
||
257 | * @return string |
||
258 | */ |
||
259 | public function getMicroTime(): string |
||
260 | { |
||
261 | if ($this->microTime === null) { |
||
262 | $timeBin = substr($this->uuid, 0, 7); |
||
263 | $this->microTime = base_convert(bin2hex($timeBin), 16, 10); |
||
264 | } |
||
265 | |||
266 | return $this->microTime; |
||
267 | } |
||
0 ignored issues
–
show
|
|||
268 | |||
269 | /** |
||
270 | * @throws \Exception |
||
0 ignored issues
–
show
|
|||
271 | * |
||
272 | * @return \DateTimeImmutable |
||
273 | */ |
||
274 | public function getDateTime(): \DateTimeImmutable |
||
275 | { |
||
276 | if ($this->dateTime === null) { |
||
277 | $this->dateTime = new \DateTimeImmutable('@' . substr($this->getMicroTime(), 0, -6)); |
||
278 | } |
||
279 | |||
280 | return $this->dateTime; |
||
281 | } |
||
0 ignored issues
–
show
|
|||
282 | |||
283 | /** |
||
284 | * @return string |
||
285 | */ |
||
286 | public function getBase62(): string |
||
287 | { |
||
288 | if ($this->base62 === null) { |
||
0 ignored issues
–
show
|
|||
289 | // max SoUuid = max microtime . max rem bits = 2^56 . 2^72 = 72057594037927936 . 4722366482869645213696 |
||
0 ignored issues
–
show
|
|||
290 | // max SoUuid = 720575940379279364722366482869645213696 = GUvfO1q6dEMruD35q5aZKi in base 62 (22 chars) |
||
0 ignored issues
–
show
|
|||
291 | $this->base62 = gmp_strval(gmp_init(bin2hex($this->uuid), 16), 62); |
||
0 ignored issues
–
show
|
|||
292 | } |
||
293 | |||
294 | return $this->base62; |
||
0 ignored issues
–
show
|
|||
295 | } |
||
0 ignored issues
–
show
|
|||
296 | |||
297 | /** |
||
298 | * @return string |
||
299 | */ |
||
300 | public function getBase36(): string |
||
301 | { |
||
302 | if ($this->base36 === null) { |
||
0 ignored issues
–
show
|
|||
303 | // max SoUuid = 720575940379279364722366482869645213696 = w3dfhtoz4u26q89wgfzwnz94w in base 36 (25 chars) |
||
0 ignored issues
–
show
|
|||
304 | $this->base36 = gmp_strval(gmp_init(bin2hex($this->uuid), 16), 36); |
||
0 ignored issues
–
show
|
|||
305 | } |
||
306 | |||
307 | return $this->base36; |
||
0 ignored issues
–
show
|
|||
308 | } |
||
0 ignored issues
–
show
|
|||
309 | |||
310 | /** |
||
311 | * @return string |
||
312 | */ |
||
313 | public static function microTimeBin(): string |
||
314 | { |
||
315 | // get real microsecond precision, as both microtime(1) and array_sum(explode(' ', microtime())) |
||
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
41% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. ![]() |
|||
316 | // are limited by php.ini precision |
||
317 | $timeParts = explode(' ', microtime(false)); |
||
318 | $timeMicroSec = $timeParts[1] . substr($timeParts[0], 2, 6); |
||
319 | // convert to 56-bit integer (7 bytes), enough to store micro time is enough up to 4253-05-31 22:20:37 |
||
0 ignored issues
–
show
|
|||
320 | $time = base_convert($timeMicroSec, 10, 16); |
||
321 | // left pad the eventual gap |
||
322 | return hex2bin(str_pad($time, 14, '0', STR_PAD_LEFT)); |
||
323 | } |
||
0 ignored issues
–
show
|
|||
324 | |||
325 | /** |
||
326 | * @param string|int|null $identifier |
||
0 ignored issues
–
show
|
|||
327 | * |
||
328 | * @throws \Exception |
||
0 ignored issues
–
show
|
|||
329 | * @throws \InvalidArgumentException |
||
0 ignored issues
–
show
|
|||
330 | * |
||
331 | * @return string |
||
332 | */ |
||
333 | public static function encodeIdentifier($identifier = null): string |
||
0 ignored issues
–
show
|
|||
334 | { |
||
335 | if ($identifier !== null) { |
||
336 | if (strpos($identifier, static::IDENTIFIER_SEPARATOR) !== false) { |
||
337 | throw new \InvalidArgumentException('SoUuid identifiers cannot contain ' . bin2hex(static::IDENTIFIER_SEPARATOR)); |
||
0 ignored issues
–
show
|
|||
338 | } |
||
339 | |||
340 | $len = strlen($identifier); |
||
341 | $identifier = substr($identifier, 0, 6) . ($len <= 4 ? static::IDENTIFIER_SEPARATOR . random_bytes(5 - $len) : ''); |
||
0 ignored issues
–
show
|
|||
342 | |||
343 | return str_pad($identifier, 6, static::IDENTIFIER_SEPARATOR, STR_PAD_RIGHT); |
||
344 | } |
||
345 | |||
346 | return static::IDENTIFIER_SEPARATOR . random_bytes(5); |
||
347 | } |
||
0 ignored issues
–
show
|
|||
348 | } |
||
349 |