Issues (3)

src/GUID.php (3 issues)

1
<?php
2
declare(strict_types=1);
3
4
namespace Unicity;
5
6
use Unicity\Errors\GUIDInvariantsViolationError;
7
use Unicity\Errors\UnserializationError;
8
use Unicity\Interfaces\GUID as GUIDInterface;
9
10
class GUID implements GUIDInterface
11
{
12
    const DEFAULT_GUID_SIZE = 16;
13
14
    /** @var string */
15
    private $bytes;
16
17
    private function __construct(string $bytes)
18
    {
19
        $this->bytes = $bytes;
20
    }
21
22
    public static function create(int $nTimeBytes = 7, int $nRandomBytes = 9): GUID
23
    {
24
        $nTimeBytes = \max(4, \min(7, $nTimeBytes));
25
        $nRandomBytes = \max(2, \min(9, $nRandomBytes));
26
27
        return self::fromBinaryString(
28
            self::getTimeBytes($nTimeBytes) . \random_bytes($nRandomBytes),
29
            $nTimeBytes + $nRandomBytes
30
        );
31
    }
32
33
    public static function fromBinaryString(string $binStr, int $expectedLength = self::DEFAULT_GUID_SIZE): GUID
34
    {
35
        $strLen = \strlen($binStr);
36
        if (6 > $expectedLength) {
37
            throw new GUIDInvariantsViolationError('IDs must have at least 6 bytes of entropy');
38
        }
39
        if ($expectedLength !== $strLen) {
40
            throw new UnserializationError(
41
                'The passed string has an unexpected length ' .
42
                \json_encode(['expected' => $expectedLength, 'given' => $strLen])
43
            );
44
        }
45
46
        return new GUID($binStr);
47
    }
48
49 View Code Duplication
    public static function fromHexString(string $hexStr, int $expectedLength = self::DEFAULT_GUID_SIZE): GUID
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
50
    {
51
        if (0 === \preg_match('/^(([0-9A-F]{2})+|([0-9a-f]{2})+)$/', $hexStr)) {
52
            throw new UnserializationError('Invalid hexadecimal string');
53
        }
54
55
        return self::fromBinaryString(
56
            \hex2bin($hexStr),
57
            $expectedLength
58
        );
59
    }
60
61 View Code Duplication
    public static function fromBase64String(string $b64Str, int $expectedLength = self::DEFAULT_GUID_SIZE): GUID
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
62
    {
63
        if (0 === \preg_match('#^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$#', $b64Str)) {
64
            throw new UnserializationError('Invalid base64 string');
65
        }
66
67
        return self::fromBinaryString(
68
            \base64_decode($b64Str, true),
69
            $expectedLength
70
        );
71
    }
72
73 View Code Duplication
    public static function fromBase64UrlString(string $b64Str, int $expectedLength = self::DEFAULT_GUID_SIZE): GUID
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
74
    {
75
        if (0 === \preg_match('#^(?:[A-Za-z0-9\-_]{4})*(?:[A-Za-z0-9\-_]{2}\.\.|[A-Za-z0-9\-_]{3}\.)?$#', $b64Str)) {
76
            throw new UnserializationError('Invalid base64 string');
77
        }
78
79
        return self::fromBase64String(
80
            \strtr($b64Str, '-_.', '+/='),
81
            $expectedLength
82
        );
83
    }
84
85
    public function asBinaryString(): string
86
    {
87
        return $this->bytes;
88
    }
89
90
    public function asHexString(): string
91
    {
92
        return \bin2hex($this->bytes);
93
    }
94
95
    public function asBase64String(): string
96
    {
97
        return \base64_encode($this->bytes);
98
    }
99
100
    public function asBase64UrlString(): string
101
    {
102
        return \strtr(\base64_encode($this->bytes), '+/=', '-_.');
103
    }
104
105
    public function numBits(): int
106
    {
107
        return (\strlen($this->bytes) << 3);
108
    }
109
110
    public function equals(GUIDInterface $guid): bool
111
    {
112
        return $guid->asBinaryString() === $this->bytes;
113
    }
114
115
    public function __toString(): string
116
    {
117
        return $this->asBase64UrlString();
118
    }
119
120
    private static function getTimeBytes(int $nTimeBytes): string
121
    {
122
        $bytesPool = self::getAdjustedTimeBytes($nTimeBytes);
123
124
        $timeBytes = \str_pad('', $nTimeBytes, \chr(0));
125
126
        for ($i = $nTimeBytes - 1; $i >= 0; $i--) {
127
            $timeByte = $bytesPool & 0xff;
128
            $timeBytes[$i] = \chr($timeByte);
129
            $bytesPool = ($bytesPool - $timeByte) >> 8;
130
        }
131
132
        return $timeBytes;
133
    }
134
135
    private static function getAdjustedTimeBytes(int $nTimeBytes): int
136
    {
137
        $ts_parts = \explode(' ', microtime());
138
        $micros = (int)\round($ts_parts[0] * 1000000) + ((int)$ts_parts[1]) * 1000000;
139
140
        switch ($nTimeBytes) {
141
            case 7: return $micros;
142
            case 6: return (int)($micros / 1000);
143
            default: return (int)($micros / 1000000);
144
        }
145
    }
146
147
    private function __clone()
148
    {
149
        throw new \BadMethodCallException('GUID objects must not be cloned');
150
    }
151
}
152