Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <fabien@symfony.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
namespace Symfony\Component\Uid;
13
 
14
/**
15
 * A ULID is lexicographically sortable and contains a 48-bit timestamp and 80-bit of crypto-random entropy.
16
 *
17
 * @see https://github.com/ulid/spec
18
 *
19
 * @author Nicolas Grekas <p@tchwork.com>
20
 */
21
class Ulid extends AbstractUid implements TimeBasedUidInterface
22
{
23
    protected const NIL = '00000000000000000000000000';
24
    protected const MAX = '7ZZZZZZZZZZZZZZZZZZZZZZZZZ';
25
 
26
    private static string $time = '';
27
    private static array $rand = [];
28
 
29
    public function __construct(string $ulid = null)
30
    {
31
        if (null === $ulid) {
32
            $this->uid = static::generate();
33
        } elseif (self::NIL === $ulid) {
34
            $this->uid = $ulid;
35
        } elseif (self::MAX === strtr($ulid, 'z', 'Z')) {
36
            $this->uid = $ulid;
37
        } else {
38
            if (!self::isValid($ulid)) {
39
                throw new \InvalidArgumentException(sprintf('Invalid ULID: "%s".', $ulid));
40
            }
41
 
42
            $this->uid = strtoupper($ulid);
43
        }
44
    }
45
 
46
    public static function isValid(string $ulid): bool
47
    {
48
        if (26 !== \strlen($ulid)) {
49
            return false;
50
        }
51
 
52
        if (26 !== strspn($ulid, '0123456789ABCDEFGHJKMNPQRSTVWXYZabcdefghjkmnpqrstvwxyz')) {
53
            return false;
54
        }
55
 
56
        return $ulid[0] <= '7';
57
    }
58
 
59
    public static function fromString(string $ulid): static
60
    {
61
        if (36 === \strlen($ulid) && preg_match('{^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$}Di', $ulid)) {
62
            $ulid = uuid_parse($ulid);
63
        } elseif (22 === \strlen($ulid) && 22 === strspn($ulid, BinaryUtil::BASE58[''])) {
64
            $ulid = str_pad(BinaryUtil::fromBase($ulid, BinaryUtil::BASE58), 16, "\0", \STR_PAD_LEFT);
65
        }
66
 
67
        if (16 !== \strlen($ulid)) {
68
            return match (strtr($ulid, 'z', 'Z')) {
69
                self::NIL => new NilUlid(),
70
                self::MAX => new MaxUlid(),
71
                default => new static($ulid),
72
            };
73
        }
74
 
75
        $ulid = bin2hex($ulid);
76
        $ulid = sprintf('%02s%04s%04s%04s%04s%04s%04s',
77
            base_convert(substr($ulid, 0, 2), 16, 32),
78
            base_convert(substr($ulid, 2, 5), 16, 32),
79
            base_convert(substr($ulid, 7, 5), 16, 32),
80
            base_convert(substr($ulid, 12, 5), 16, 32),
81
            base_convert(substr($ulid, 17, 5), 16, 32),
82
            base_convert(substr($ulid, 22, 5), 16, 32),
83
            base_convert(substr($ulid, 27, 5), 16, 32)
84
        );
85
 
86
        if (self::NIL === $ulid) {
87
            return new NilUlid();
88
        }
89
 
90
        if (self::MAX === $ulid = strtr($ulid, 'abcdefghijklmnopqrstuv', 'ABCDEFGHJKMNPQRSTVWXYZ')) {
91
            return new MaxUlid();
92
        }
93
 
94
        $u = new static(self::NIL);
95
        $u->uid = $ulid;
96
 
97
        return $u;
98
    }
99
 
100
    public function toBinary(): string
101
    {
102
        $ulid = strtr($this->uid, 'ABCDEFGHJKMNPQRSTVWXYZ', 'abcdefghijklmnopqrstuv');
103
 
104
        $ulid = sprintf('%02s%05s%05s%05s%05s%05s%05s',
105
            base_convert(substr($ulid, 0, 2), 32, 16),
106
            base_convert(substr($ulid, 2, 4), 32, 16),
107
            base_convert(substr($ulid, 6, 4), 32, 16),
108
            base_convert(substr($ulid, 10, 4), 32, 16),
109
            base_convert(substr($ulid, 14, 4), 32, 16),
110
            base_convert(substr($ulid, 18, 4), 32, 16),
111
            base_convert(substr($ulid, 22, 4), 32, 16)
112
        );
113
 
114
        return hex2bin($ulid);
115
    }
116
 
117
    public function toBase32(): string
118
    {
119
        return $this->uid;
120
    }
121
 
122
    public function getDateTime(): \DateTimeImmutable
123
    {
124
        $time = strtr(substr($this->uid, 0, 10), 'ABCDEFGHJKMNPQRSTVWXYZ', 'abcdefghijklmnopqrstuv');
125
 
126
        if (\PHP_INT_SIZE >= 8) {
127
            $time = (string) hexdec(base_convert($time, 32, 16));
128
        } else {
129
            $time = sprintf('%02s%05s%05s',
130
                base_convert(substr($time, 0, 2), 32, 16),
131
                base_convert(substr($time, 2, 4), 32, 16),
132
                base_convert(substr($time, 6, 4), 32, 16)
133
            );
134
            $time = BinaryUtil::toBase(hex2bin($time), BinaryUtil::BASE10);
135
        }
136
 
137
        if (4 > \strlen($time)) {
138
            $time = '000'.$time;
139
        }
140
 
141
        return \DateTimeImmutable::createFromFormat('U.u', substr_replace($time, '.', -3, 0));
142
    }
143
 
144
    public static function generate(\DateTimeInterface $time = null): string
145
    {
146
        if (null === $mtime = $time) {
147
            $time = microtime(false);
148
            $time = substr($time, 11).substr($time, 2, 3);
149
        } elseif (0 > $time = $time->format('Uv')) {
150
            throw new \InvalidArgumentException('The timestamp must be positive.');
151
        }
152
 
153
        if ($time > self::$time || (null !== $mtime && $time !== self::$time)) {
154
            randomize:
155
            $r = unpack('n*', random_bytes(10));
156
            $r[1] |= ($r[5] <<= 4) & 0xF0000;
157
            $r[2] |= ($r[5] <<= 4) & 0xF0000;
158
            $r[3] |= ($r[5] <<= 4) & 0xF0000;
159
            $r[4] |= ($r[5] <<= 4) & 0xF0000;
160
            unset($r[5]);
161
            self::$rand = $r;
162
            self::$time = $time;
163
        } elseif ([1 => 0xFFFFF, 0xFFFFF, 0xFFFFF, 0xFFFFF] === self::$rand) {
164
            if (\PHP_INT_SIZE >= 8 || 10 > \strlen($time = self::$time)) {
165
                $time = (string) (1 + $time);
166
            } elseif ('999999999' === $mtime = substr($time, -9)) {
167
                $time = (1 + substr($time, 0, -9)).'000000000';
168
            } else {
169
                $time = substr_replace($time, str_pad(++$mtime, 9, '0', \STR_PAD_LEFT), -9);
170
            }
171
 
172
            goto randomize;
173
        } else {
174
            for ($i = 4; $i > 0 && 0xFFFFF === self::$rand[$i]; --$i) {
175
                self::$rand[$i] = 0;
176
            }
177
 
178
            ++self::$rand[$i];
179
            $time = self::$time;
180
        }
181
 
182
        if (\PHP_INT_SIZE >= 8) {
183
            $time = base_convert($time, 10, 32);
184
        } else {
185
            $time = str_pad(bin2hex(BinaryUtil::fromBase($time, BinaryUtil::BASE10)), 12, '0', \STR_PAD_LEFT);
186
            $time = sprintf('%s%04s%04s',
187
                base_convert(substr($time, 0, 2), 16, 32),
188
                base_convert(substr($time, 2, 5), 16, 32),
189
                base_convert(substr($time, 7, 5), 16, 32)
190
            );
191
        }
192
 
193
        return strtr(sprintf('%010s%04s%04s%04s%04s',
194
            $time,
195
            base_convert(self::$rand[1], 10, 32),
196
            base_convert(self::$rand[2], 10, 32),
197
            base_convert(self::$rand[3], 10, 32),
198
            base_convert(self::$rand[4], 10, 32)
199
        ), 'abcdefghijklmnopqrstuv', 'ABCDEFGHJKMNPQRSTVWXYZ');
200
    }
201
}