Completed
Pull Request — master (#1984)
by Maciej
22:01
created

UuidGenerator::isValid()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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 2
    public function setSalt(string $salt) : void
36
    {
37 2
        $this->salt = $salt;
38 2
    }
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 2
    public function isValid(string $uuid) : bool
54
    {
55 2
        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 2
            === 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 2
    public function generate(DocumentManager $dm, object $document)
70
    {
71 2
        $uuid = $this->generateV4();
72 2
        return $this->generateV5($uuid, $this->salt ?: php_uname('n'));
73
    }
74
75
    /**
76
     * Generates a v4 UUID
77
     */
78 4
    public function generateV4() : string
79
    {
80 4
        return sprintf(
81 4
            '%04x%04x%04x%04x%04x%04x%04x%04x',
82
            // 32 bits for "time_low"
83 4
            mt_rand(0, 0xffff),
84 4
            mt_rand(0, 0xffff),
85
            // 16 bits for "time_mid"
86 4
            mt_rand(0, 0xffff),
87
            // 16 bits for "time_hi_and_version",
88
            // four most significant bits holds version number 4
89 4
            mt_rand(0, 0x0fff) | 0x4000,
90
            // 16 bits, 8 bits for "clk_seq_hi_res",
91
            // 8 bits for "clk_seq_low",
92
            // two most significant bits holds zero and one for variant DCE1.1
93 4
            mt_rand(0, 0x3fff) | 0x8000,
94
            // 48 bits for "node"
95 4
            mt_rand(0, 0xffff),
96 4
            mt_rand(0, 0xffff),
97 4
            mt_rand(0, 0xffff)
98
        );
99
    }
100
101
    /**
102
     * Generates a v5 UUID
103
     *
104
     * @throws Exception When the provided namespace is invalid.
105
     */
106 2
    public function generateV5(string $namespace, string $salt) : string
107
    {
108 2
        if (! $this->isValid($namespace)) {
109
            throw new Exception('Provided $namespace is invalid: ' . $namespace);
110
        }
111
112
        // Get hexadecimal components of namespace
113 2
        $nhex = str_replace(['-', '{', '}'], '', $namespace);
114
115
        // Binary Value
116 2
        $nstr = '';
117
118
        // Convert Namespace UUID to bits
119 2
        for ($i = 0; $i < strlen($nhex); $i += 2) {
120 2
            $nstr .= chr((int) hexdec($nhex[$i] . $nhex[$i + 1]));
121
        }
122
123
        // Calculate hash value
124 2
        $hash = sha1($nstr . $salt);
125
126 2
        return sprintf(
127 2
            '%08s%04s%04x%04x%12s',
128
            // 32 bits for "time_low"
129 2
            substr($hash, 0, 8),
130
            // 16 bits for "time_mid"
131 2
            substr($hash, 8, 4),
132
            // 16 bits for "time_hi_and_version",
133
            // four most significant bits holds version number 3
134 2
            (hexdec(substr($hash, 12, 4)) & 0x0fff) | 0x3000,
135
            // 16 bits, 8 bits for "clk_seq_hi_res",
136
            // 8 bits for "clk_seq_low",
137
            // two most significant bits holds zero and one for variant DCE1.1
138 2
            (hexdec(substr($hash, 16, 4)) & 0x3fff) | 0x8000,
139
            // 48 bits for "node"
140 2
            substr($hash, 20, 12)
141
        );
142
    }
143
}
144