SoUuid::getBase62()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
0 ignored issues
show
Coding Style introduced by
You must use "/**" style comments for a file comment
Loading history...
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
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @package tag in class comment
Loading history...
Coding Style introduced by
Missing @author tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
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
Coding Style introduced by
Protected member variable "uuid" must contain a leading underscore
Loading history...
31
32
    /**
33
     * @var \DateTimeImmutable
34
     */
35
    protected $dateTime;
0 ignored issues
show
Coding Style introduced by
Protected member variable "dateTime" must contain a leading underscore
Loading history...
36
37
    /**
38
     * @var array
39
     */
40
    protected $decoded;
0 ignored issues
show
Coding Style introduced by
Protected member variable "decoded" must contain a leading underscore
Loading history...
41
42
    /**
43
     * @var string
44
     */
45
    protected $identifier;
0 ignored issues
show
Coding Style introduced by
Protected member variable "identifier" must contain a leading underscore
Loading history...
46
47
    /**
48
     * @var string
49
     */
50
    protected $string;
0 ignored issues
show
Coding Style introduced by
Protected member variable "string" must contain a leading underscore
Loading history...
51
52
    /**
53
     * @var string
54
     */
55
    protected $microTime;
0 ignored issues
show
Coding Style introduced by
Protected member variable "microTime" must contain a leading underscore
Loading history...
56
57
    /**
58
     * @var string
59
     */
60
    protected $base62;
0 ignored issues
show
Coding Style introduced by
Protected member variable "base62" must contain a leading underscore
Loading history...
61
62
    /**
63
     * @var string
64
     */
65
    protected $base36;
0 ignored issues
show
Coding Style introduced by
Protected member variable "base36" must contain a leading underscore
Loading history...
66
67
    /**
68
     * SoUuid constructor.
69
     *
70
     * @param string $uuid
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter comment
Loading history...
71
     */
72
    protected function __construct(string $uuid)
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines before function; 1 found
Loading history...
73
    {
74
        $this->uuid = (string) $uuid;
75
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
76
77
    /**
78
     * @param string|int|null $identifier
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected "string|integer|null" but found "string|int|null" for parameter type
Loading history...
79
     *
80
     * @throws \Exception
0 ignored issues
show
Coding Style introduced by
Comment missing for @throws tag in function comment
Loading history...
81
     *
82
     * @return SoUuidInterface
83
     */
84
    public static function generate($identifier = null): SoUuidInterface
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$identifier" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$identifier"; expected 0 but found 1
Loading history...
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
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
95
96
    /**
97
     * @param string $uuidString
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter comment
Loading history...
98
     *
99
     * @throws \InvalidArgumentException
0 ignored issues
show
Coding Style introduced by
Comment missing for @throws tag in function comment
Loading history...
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
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
111
112
    /**
113
     * @param string $uuidString
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter comment
Loading history...
114
     *
115
     * @throws \InvalidArgumentException
0 ignored issues
show
Coding Style introduced by
Comment missing for @throws tag in function comment
Loading history...
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
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
127
128
    /**
129
     * @param string $uuidString
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter comment
Loading history...
130
     *
131
     * @throws \InvalidArgumentException
0 ignored issues
show
Coding Style introduced by
Comment missing for @throws tag in function comment
Loading history...
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
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
143
144
    /**
145
     * @param string $uuidString
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter comment
Loading history...
146
     *
147
     * @throws \InvalidArgumentException
0 ignored issues
show
Coding Style introduced by
Comment missing for @throws tag in function comment
Loading history...
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
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 109 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
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
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
161
162
    /**
163
     * @param string $uuidString
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter comment
Loading history...
164
     *
165
     * @throws \InvalidArgumentException
0 ignored issues
show
Coding Style introduced by
Comment missing for @throws tag in function comment
Loading history...
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
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 106 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
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
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
179
180
    /**
181
     * @throws \Exception
0 ignored issues
show
Coding Style introduced by
Comment missing for @throws tag in function comment
Loading history...
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
Coding Style introduced by
This array key does not seem to be aligned correctly; expected 30 spaces, but found 16.
Loading history...
191
                'dateTime'   => $this->getDateTime(),
0 ignored issues
show
Coding Style introduced by
This array key does not seem to be aligned correctly; expected 30 spaces, but found 16.
Loading history...
192
                'identifier' => $this->getIdentifier(),
0 ignored issues
show
Coding Style introduced by
This array key does not seem to be aligned correctly; expected 30 spaces, but found 16.
Loading history...
193
                'rand'       => bin2hex(substr($this->uuid, $idLen ? 7 + $idLen : 8)),
0 ignored issues
show
Coding Style introduced by
This array key does not seem to be aligned correctly; expected 30 spaces, but found 16.
Loading history...
194
            ];
0 ignored issues
show
Coding Style introduced by
The closing parenthesis does not seem to be aligned correctly; expected 29 space(s), but found 12.
Loading history...
195
        }
196
197
        return $this->decoded;
198
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
199
200
    /**
201
     * @return string
202
     */
203
    public function getBytes(): string
204
    {
205
        return $this->uuid;
206
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
207
208
    /**
209
     * @return string
210
     */
211
    public function getHex(): string
212
    {
213
        return bin2hex($this->uuid);
214
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
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
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
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
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
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
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
268
269
    /**
270
     * @throws \Exception
0 ignored issues
show
Coding Style introduced by
Comment missing for @throws tag in function comment
Loading history...
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
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
282
283
    /**
284
     * @return string
285
     */
286
    public function getBase62(): string
287
    {
288
        if ($this->base62 === null) {
0 ignored issues
show
Coding Style introduced by
Variable "base62" contains numbers but this is discouraged
Loading history...
289
            // max SoUuid = max microtime . max rem bits = 2^56 . 2^72 = 72057594037927936 . 4722366482869645213696
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 115 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
290
            // max SoUuid = 720575940379279364722366482869645213696 = GUvfO1q6dEMruD35q5aZKi in base 62 (22 chars)
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 114 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
291
            $this->base62 = gmp_strval(gmp_init(bin2hex($this->uuid), 16), 62);
0 ignored issues
show
Coding Style introduced by
Variable "base62" contains numbers but this is discouraged
Loading history...
292
        }
293
294
        return $this->base62;
0 ignored issues
show
Coding Style introduced by
Variable "base62" contains numbers but this is discouraged
Loading history...
295
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
296
297
    /**
298
     * @return string
299
     */
300
    public function getBase36(): string
301
    {
302
        if ($this->base36 === null) {
0 ignored issues
show
Coding Style introduced by
Variable "base36" contains numbers but this is discouraged
Loading history...
303
            // max SoUuid = 720575940379279364722366482869645213696 = w3dfhtoz4u26q89wgfzwnz94w in base 36 (25 chars)
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 117 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
304
            $this->base36 = gmp_strval(gmp_init(bin2hex($this->uuid), 16), 36);
0 ignored issues
show
Coding Style introduced by
Variable "base36" contains numbers but this is discouraged
Loading history...
305
        }
306
307
        return $this->base36;
0 ignored issues
show
Coding Style introduced by
Variable "base36" contains numbers but this is discouraged
Loading history...
308
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
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.

Loading history...
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 104 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
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
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 110 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
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
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
324
325
    /**
326
     * @param string|int|null $identifier
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected "string|integer|null" but found "string|int|null" for parameter type
Loading history...
327
     *
328
     * @throws \Exception
0 ignored issues
show
Coding Style introduced by
Comment missing for @throws tag in function comment
Loading history...
329
     * @throws \InvalidArgumentException
0 ignored issues
show
Coding Style introduced by
Comment missing for @throws tag in function comment
Loading history...
330
     *
331
     * @return string
332
     */
333
    public static function encodeIdentifier($identifier = null): string
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$identifier" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$identifier"; expected 0 but found 1
Loading history...
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
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 130 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
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
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 127 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
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
Coding Style introduced by
Expected 2 blank lines after function; 0 found
Loading history...
348
}
349