Completed
Push — master ( b90edc...95608f )
by Andreas
14:15 queued 11s
created

UuidGenerator::generateV5()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 37
ccs 0
cts 15
cp 0
rs 9.328
c 0
b 0
f 0
cc 3
nc 3
nop 2
crap 12
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ODM\MongoDB\Id;
6
7
use Doctrine\ODM\MongoDB\DocumentManager;
8
use Exception;
9
use function chr;
10
use function hexdec;
11
use function mt_rand;
12
use function php_uname;
13
use function preg_match;
14
use function sha1;
15
use function sprintf;
16
use function str_replace;
17
use function strlen;
18
use function substr;
19
20
/**
21
 * Generates UUIDs.
22
 */
23
final class UuidGenerator extends AbstractIdGenerator
24
{
25
    /**
26
     * A unique environment value to salt each UUID with.
27
     *
28
     * @var string
29
     */
30
    protected $salt = null;
31
32
    /**
33
     * Used to set the salt that will be applied to each id
34
     */
35 1
    public function setSalt(string $salt) : void
36
    {
37 1
        $this->salt = $salt;
38 1
    }
39
40
    /**
41
     * Returns the current salt value
42
     *
43
     * @return string $salt The current salt
44
     */
45 1
    public function getSalt() : string
46
    {
47 1
        return $this->salt;
48
    }
49
50
    /**
51
     * Checks that a given string is a valid uuid.
52
     */
53
    public function isValid(string $uuid) : bool
54
    {
55
        return preg_match('/^\{?[0-9a-f]{8}\-?[0-9a-f]{4}\-?[0-9a-f]{4}\-?[0-9a-f]{4}\-?[0-9a-f]{12}\}?$/i', $uuid)
56
            === 1;
57
    }
58
59
    /**
60
     * Generates a new UUID
61
     *
62
     * @param DocumentManager $dm       Not used.
63
     * @param object          $document Not used.
64
     *
65
     * @return string UUID
66
     *
67
     * @throws Exception
68
     */
69
    public function generate(DocumentManager $dm, object $document)
70
    {
71
        $uuid = $this->generateV4();
72
73
        return $this->generateV5($uuid, $this->salt ?: php_uname('n'));
74
    }
75
76
    /**
77
     * Generates a v4 UUID
78
     */
79
    public function generateV4() : string
80
    {
81
        return sprintf(
82
            '%04x%04x%04x%04x%04x%04x%04x%04x',
83
            // 32 bits for "time_low"
84
            mt_rand(0, 0xffff),
85
            mt_rand(0, 0xffff),
86
            // 16 bits for "time_mid"
87
            mt_rand(0, 0xffff),
88
            // 16 bits for "time_hi_and_version",
89
            // four most significant bits holds version number 4
90
            mt_rand(0, 0x0fff) | 0x4000,
91
            // 16 bits, 8 bits for "clk_seq_hi_res",
92
            // 8 bits for "clk_seq_low",
93
            // two most significant bits holds zero and one for variant DCE1.1
94
            mt_rand(0, 0x3fff) | 0x8000,
95
            // 48 bits for "node"
96
            mt_rand(0, 0xffff),
97
            mt_rand(0, 0xffff),
98
            mt_rand(0, 0xffff)
99
        );
100
    }
101
102
    /**
103
     * Generates a v5 UUID
104
     *
105
     * @throws Exception When the provided namespace is invalid.
106
     */
107
    public function generateV5(string $namespace, string $salt) : string
108
    {
109
        if (! $this->isValid($namespace)) {
110
            throw new Exception('Provided $namespace is invalid: ' . $namespace);
111
        }
112
113
        // Get hexadecimal components of namespace
114
        $nhex = str_replace(['-', '{', '}'], '', $namespace);
115
116
        // Binary Value
117
        $nstr = '';
118
119
        // Convert Namespace UUID to bits
120
        for ($i = 0; $i < strlen($nhex); $i += 2) {
121
            $nstr .= chr((int) hexdec($nhex[$i] . $nhex[$i + 1]));
122
        }
123
124
        // Calculate hash value
125
        $hash = sha1($nstr . $salt);
126
127
        return sprintf(
128
            '%08s%04s%04x%04x%12s',
129
            // 32 bits for "time_low"
130
            substr($hash, 0, 8),
131
            // 16 bits for "time_mid"
132
            substr($hash, 8, 4),
133
            // 16 bits for "time_hi_and_version",
134
            // four most significant bits holds version number 3
135
            (hexdec(substr($hash, 12, 4)) & 0x0fff) | 0x3000,
136
            // 16 bits, 8 bits for "clk_seq_hi_res",
137
            // 8 bits for "clk_seq_low",
138
            // two most significant bits holds zero and one for variant DCE1.1
139
            (hexdec(substr($hash, 16, 4)) & 0x3fff) | 0x8000,
140
            // 48 bits for "node"
141
            substr($hash, 20, 12)
142
        );
143
    }
144
}
145