Completed
Push — custom-comb ( be736d...ea4943 )
by Marcel
03:33
created

CustomCombGenerator::__construct()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4.0961

Importance

Changes 0
Metric Value
cc 4
eloc 10
nc 4
nop 4
dl 0
loc 18
ccs 9
cts 11
cp 0.8182
crap 4.0961
rs 9.2
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace UMA\Uuid;
6
7
class CustomCombGenerator implements UuidGenerator
8
{
9
    /**
10
     * @var UuidGenerator
11
     */
12
    private $generator;
13
14
    /**
15
     * @var int
16
     */
17
    private $exponent;
18
19
    /**
20
     * @var int
21
     */
22
    private $epoch;
23
24
    /**
25
     * @var int
26
     */
27
    private $span;
28
29
    /**
30
     * @param UuidGenerator      $generator
31
     * @param \DateTimeImmutable $epoch
32
     * @param int                $span
33
     * @param int                $granularity
34
     *
35
     * @throws \InvalidArgumentException When $epoch is a date in the future.
36
     * @throws \InvalidArgumentException When $span is outside the [1, 16] range.
37
     * @throws \InvalidArgumentException When $granularity is outside the [0, 6] range.
38
     */
39 8
    public function __construct(UuidGenerator $generator, \DateTimeImmutable $epoch, int $span, int $granularity)
40
    {
41 8
        if (new \DateTimeImmutable('now') < $epoch) {
42
            throw new \InvalidArgumentException('$epoch must be in the past. Got timestamp: ' . $epoch->getTimestamp());
43
        }
44
45 8
        if (!\in_array($span, \range(1, 16), true)) {
46
            throw new \InvalidArgumentException('$span must be in the [1, 16] range. Got: ' . $span);
47
        }
48
49 8
        if (!\in_array($granularity, \range(0, 6), true)) {
50 1
            throw new \InvalidArgumentException('$granularity must be in the [0, 6] range. Got: ' . $granularity);
51
        }
52
53 7
        $this->generator = $generator;
54 7
        $this->exponent = 10 ** $granularity;
55 7
        $this->epoch = $epoch->getTimestamp() * $this->exponent;
56 7
        $this->span = 2 * $span;
57 7
    }
58
59
    /**
60
     * {@inheritdoc}
61
     */
62 1
    public function generate(string $name = null): Uuid
63
    {
64 1
        $head = $this->procrust($this->timestamp());
65 1
        $tail = \substr($this->generator->generate()->asBytes(), -16 + ($this->span / 2));
66
67 1
        return Uuid::fromBytes($head . $tail);
68
    }
69
70
    /**
71
     * Returns the exact date on which the 48 most significant bits of
72
     * the UUIDs will overflow for the chosen $granularity.
73
     *
74
     * The higher the granularity the better is the output of the
75
     * generator, but the overflow date also looms sooner.
76
     */
77 7
    public function getOverflowDate(): \DateTimeImmutable
78
    {
79 7
        $maxTimestamp = (int)(($this->maxTimestamp() - $this->epoch)/$this->exponent);
80
81 7
        return new \DateTimeImmutable("@$maxTimestamp UTC");
82
    }
83
84
    /**
85
     * Returns $timestamp "procrusted" to 6 bytes.
86
     *
87
     * If the timestamp is smaller than 6 bytes, leading 0 bits are appended.
88
     * If the timestamp is larger than 6 bytes, its least significant bits are chopped off.
89
     *
90
     * The returned string is raw binary (each character encodes 8 bits)
91
     * and has always the same size -- 6 bytes.
92
     *
93
     * @example '59b7d71f'      => 0x000059b7d71f
94
     * @example '3812e6738'     => 0x0003812e6738
95
     * @example '230bd00838'    => 0x00230bd00838
96
     * @example '15e76205236'   => 0x015e76205236
97
     * @example 'db09d433621'   => 0x0db09d433621
98
     * @example '88e624a01d4c'  => 0x88e624a01d4c
99
     * @example '558fd6e4124fb' => 0x558fd6e4124f
100
     */
101 1
    private function procrust(string $timestamp): string
102
    {
103 1
        return \pack("H{$this->span}", \str_pad(\substr($timestamp, 0, $this->span), $this->span, '0', STR_PAD_LEFT));
104
    }
105
106
    /**
107
     * Returns the current unix timestamp as a hex-encoded string (that is, each character
108
     * encodes 4 bits) with variable precision, ranging from second to microsecond.
109
     *
110
     * The length of the string varies depending on the $granularity chosen. This is how
111
     * the exact same reading from microtime() looks like for all 7 possible granularity
112
     * levels (0 through 6):
113
     *
114
     * @example '59b7d71f'
115
     * @example '3812e6738'
116
     * @example '230bd00838'
117
     * @example '15e76205236'
118
     * @example 'db09d433621'
119
     * @example '88e624a01d4c'
120
     * @example '558fd6e4124fb'
121
     */
122 8
    private function timestamp(): string
123
    {
124 8
        return \dechex((int)(\microtime(true) * $this->exponent) - $this->epoch);
125
    }
126
127 7
    private function maxTimestamp(): int
128
    {
129 7
        return \hexdec(\str_repeat('f', \max($this->span, \strlen($this->timestamp()))));
0 ignored issues
show
Bug Best Practice introduced by
The expression return hexdec(str_repeat...($this->timestamp())))) could return the type double which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
130
    }
131
}
132