Completed
Pull Request — master (#8)
by Peter
01:19
created

SnowflakeGenerator::generate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2.003

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 10
cts 11
cp 0.9091
rs 9.6666
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 2.003
1
<?php
2
3
/**
4
 * GpsLab component.
5
 *
6
 * @author    Peter Gribanov <[email protected]>
7
 * @copyright Copyright (c) 2011, Peter Gribanov
8
 * @license   http://opensource.org/licenses/MIT
9
 */
10
11
namespace GpsLab\Component\Base64UID\Generator\Binary;
12
13
use GpsLab\Component\Base64UID\Exception\ArgumentRangeException;
14
use GpsLab\Component\Base64UID\Exception\ArgumentTypeException;
15
use GpsLab\Component\Base64UID\Exception\ZeroArgumentException;
16
17
class SnowflakeGenerator implements BinaryGenerator
18
{
19
    /**
20
     * TODO use private const after drop PHP < 7.1.
21
     *
22
     * @var int
23
     */
24
    private static $DATA_CENTER_LENGTH = 5; // data center value 0-31
25
26
    /**
27
     * TODO use private const after drop PHP < 7.1.
28
     *
29
     * @var int
30
     */
31
    private static $MACHINE_LENGTH = 7; // machine value 0-127
32
33
    /**
34
     * TODO use private const after drop PHP < 7.1.
35
     *
36
     * @var int
37
     */
38
    private static $SEQUENCE_LENGTH = 6; // sequence value 0-63
39
40
    /**
41
     * @var int
42
     */
43
    private $data_center;
44
45
    /**
46
     * @var int
47
     */
48
    private $machine;
49
50
    /**
51
     * @var int
52
     */
53
    private $time_offset;
54
55
    /**
56
     * @var int
57
     */
58
    private $last_time = 0;
59
60
    /**
61
     * @var int
62
     */
63
    private $sequence = 0;
64
65
    /**
66
     * Snowflake.
67
     *
68
     * The time offset allows to move the starting point of time in microseconds,
69
     * which reduces the size of the stored time:
70
     *  0             = 1970-01-01 00:00:00 (UTC)
71
     *  1577833200000 = 2020-01-01 00:00:00 (UTC)
72
     *
73
     * @param int $data_center
74
     * @param int $machine
75
     * @param int $time_offset
76
     */
77 12
    public function __construct($data_center, $machine, $time_offset = 0)
78
    {
79 12
        if (!is_int($data_center)) {
80 1
            throw new ArgumentTypeException(sprintf('Data center should be integer, got "%s" instead.', gettype($data_center)));
81
        }
82
83 11
        if (!is_int($machine)) {
84 1
            throw new ArgumentTypeException(sprintf('Machine should be integer, got "%s" instead.', gettype($data_center)));
85
        }
86
87 10
        if (!is_int($time_offset)) {
88 1
            throw new ArgumentTypeException(sprintf('Time offset should be integer, got "%s" instead.', gettype($data_center)));
89
        }
90
91 9
        if ($data_center < 0) {
92 1
            throw new ZeroArgumentException(sprintf('Data center should be grate then "0", got "%d" instead.', $data_center));
93
        }
94
95 8
        if ($machine < 0) {
96 1
            throw new ZeroArgumentException(sprintf('Machine should be grate then "0", got "%d" instead.', $machine));
97
        }
98
99 7
        if ($time_offset < 0) {
100 1
            throw new ZeroArgumentException(sprintf('Time offset should be grate then "0", got "%d" instead.', $time_offset));
101
        }
102
103 6
        $max_data_center = bindec(str_repeat('1', self::$DATA_CENTER_LENGTH));
104
105 6
        if ($data_center > $max_data_center) {
106 1
            throw new ArgumentRangeException(sprintf('Data center number should be grate then or equal to "%d", got "%d" instead.', $max_data_center, $data_center));
107
        }
108
109 5
        $max_machine = bindec(str_repeat('1', self::$MACHINE_LENGTH));
110
111 5
        if ($machine > $max_machine) {
112 1
            throw new ArgumentRangeException(sprintf('Data center number should be grate then or equal to "%d", got "%d" instead.', $max_machine, $machine));
113
        }
114
115 4
        $now = (int) floor(microtime(true) * 1000);
116
117 4
        if ($time_offset > $now) {
118 1
            throw new ArgumentRangeException(sprintf('Time offset should be grate then or equal to current time "%d", got "%d" instead.', $now, $time_offset));
119
        }
120
121 3
        $this->data_center = $data_center;
122 3
        $this->machine = $machine;
123 3
        $this->time_offset = $time_offset;
124 3
    }
125
126
    /**
127
     * @return int
128
     */
129 3
    public function generate()
130
    {
131 3
        $time = ((int) floor(microtime(true) * 1000) - $this->time_offset);
132
133 3
        if ($this->last_time === $time) {
134
            ++$this->sequence;
135
        } else {
136 3
            $this->last_time = $time;
137
        }
138
139 3
        $uid = 1 << 64 - 1;
140 3
        $uid |= $time << self::$DATA_CENTER_LENGTH + self::$MACHINE_LENGTH + self::$SEQUENCE_LENGTH;
141 3
        $uid |= $this->data_center << self::$MACHINE_LENGTH + self::$SEQUENCE_LENGTH;
142 3
        $uid |= $this->machine << self::$SEQUENCE_LENGTH;
143 3
        $uid |= $this->sequence;
144
145 3
        return $uid;
146
    }
147
}
148