UuidExtension::getUuidFormat()   A
last analyzed

Complexity

Conditions 5
Paths 4

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 8
c 1
b 0
f 0
dl 0
loc 14
rs 9.6111
cc 5
nc 4
nop 1
1
<?php
2
3
namespace LeKoala\Uuid;
4
5
use Ramsey\Uuid\Uuid;
6
use SilverStripe\ORM\DB;
7
use InvalidArgumentException;
8
use SilverStripe\Forms\FieldList;
9
use SilverStripe\Forms\ReadonlyField;
10
use SilverStripe\ORM\DataObject;
11
use Tuupola\Base62Proxy as Base62;
12
use SilverStripe\ORM\DataExtension;
13
use SilverStripe\ORM\DataObjectSchema;
14
15
class UuidExtension extends DataExtension
16
{
17
    const UUID_FIELD = 'Uuid';
18
    const UUID_BINARY_FORMAT = 'binary';
19
    const UUID_STRING_FORMAT = 'string';
20
    const UUID_BASE62_FORMAT = 'base62';
21
22
    /**
23
     * @var array<string,string>
24
     */
25
    private static $db = [
0 ignored issues
show
introduced by
The private property $db is not used, and could be removed.
Loading history...
26
        self::UUID_FIELD => DBUuid::class,
27
    ];
28
29
    /**
30
     * @var array<string,mixed>
31
     */
32
    private static $indexes = [
0 ignored issues
show
introduced by
The private property $indexes is not used, and could be removed.
Loading history...
33
        self::UUID_FIELD => true,
34
    ];
35
36
    protected function getBaseTableName(): string
37
    {
38
        $schema = DataObjectSchema::create();
39
        $table = $schema->tableForField(
40
            get_class($this->getOwner()),
41
            self::UUID_FIELD
42
        );
43
44
        return $table;
45
    }
46
47
    /**
48
     * Assign a new uuid to this record. This will overwrite any existing uuid.
49
     *
50
     * @param bool $check Check if the uuid is already taken
51
     * @return string The new uuid
52
     */
53
    public function assignNewUuid($check = true)
54
    {
55
        $uuid = Uuid::uuid4();
56
        if ($check) {
57
            $table = $this->getBaseTableName();
58
            do {
59
                $this->owner->Uuid = $uuid->getBytes();
60
                // If we have something, keep checking
61
                $check = DB::prepared_query('SELECT count(ID) FROM ' . $table . ' WHERE Uuid = ?', [$this->owner->Uuid])->value() > 0;
62
            } while ($check);
63
        } else {
64
            $this->owner->Uuid = $uuid->getBytes();
65
        }
66
67
        return $this->owner->Uuid;
68
    }
69
70
    /**
71
     * Get a record by its uuid
72
     *
73
     * @template T
74
     * @param class-string<T> $class The class
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<T> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<T>.
Loading history...
75
     * @param string $value The uuid value
76
     * @param string $format Any UUID_XXXX_FORMAT constant or string
77
     * @return T|null The DataObject or null if no record is found or format invalid
78
     */
79
    public static function getByUuid($class, $value, $format = null)
80
    {
81
        // Guess format from value
82
        if ($format === null) {
83
            try {
84
                $format = self::getUuidFormat($value);
85
            } catch (InvalidArgumentException $ex) {
86
                $format = null;
87
            }
88
        }
89
        // Convert format to bytes for query
90
        switch ($format) {
91
            case self::UUID_BASE62_FORMAT:
92
                try {
93
                    $decodedValue = Base62::decode($value);
94
                } catch (InvalidArgumentException $ex) {
95
                    // Invalid arguments should not return anything
96
                    return null;
97
                }
98
                $uuid = Uuid::fromBytes($decodedValue);
99
                break;
100
            case self::UUID_STRING_FORMAT:
101
                $uuid = Uuid::fromString($value);
102
                break;
103
            case self::UUID_BINARY_FORMAT:
104
                $uuid = Uuid::fromBytes($value);
105
                break;
106
            default:
107
                return null;
108
        }
109
        // Fetch the first record and disable subsite filter in a similar way as asking by ID
110
        $q = $class::get()->filter(
111
            self::UUID_FIELD,
112
            $uuid->getBytes()
113
        )->setDataQueryParam('Subsite.filter', false);
114
        return $q->first();
115
    }
116
117
    /**
118
     * Guess uuid format based on strlen
119
     *
120
     * @param mixed $value
121
     * @return string
122
     * @throws InvalidArgumentException
123
     */
124
    public static function getUuidFormat($value)
125
    {
126
        $len = strlen((string)$value);
127
128
        if ($len == 36) {
129
            // d84560c8-134f-11e6-a1e2-34363bd26dae => 36 chars
130
            return self::UUID_STRING_FORMAT;
131
        } elseif ($len > 20 && $len < 24) {
132
            // 6a630O1jrtMjCrQDyG3D3O => 22 chars (in theory, because sometimes it's different)
133
            return self::UUID_BASE62_FORMAT;
134
        } elseif ($len == 16) {
135
            return self::UUID_BINARY_FORMAT;
136
        }
137
        throw new InvalidArgumentException("$value does not seem to be a valid uuid");
138
    }
139
140
    /**
141
     * Return a uuid suitable for an URL, like an URLSegment
142
     *
143
     * @return string
144
     */
145
    public function UuidSegment()
146
    {
147
        // assign on the fly
148
        if (!$this->owner->Uuid) {
149
            $uuid = $this->assignNewUuid();
150
            // Make a quick write without using orm
151
            if ($this->owner->ID) {
152
                $table = $this->getBaseTableName();
153
                DB::prepared_query("UPDATE $table SET Uuid = ? WHERE ID = ?", [$uuid, $this->owner->ID]);
154
            }
155
        }
156
        /** @var DBUuid $dbObject */
157
        $dbObject = $this->owner->dbObject(self::UUID_FIELD);
158
        return $dbObject->Base62();
159
    }
160
161
    /**
162
     * @param FieldList $fields
163
     * @return void
164
     */
165
    public function updateCMSFields(FieldList $fields)
166
    {
167
        if (DBUuid::config()->show_cms_field) {
168
            $firstField = $fields->dataFieldNames()[0] ?? null;
169
            /** @var DBUuid $dbObject */
170
            $dbObject = $this->owner->dbObject('Uuid');
171
            $uuidField = ReadonlyField::create('UuidNice', 'Uuid', $dbObject->Nice());
172
            $fields->addFieldToTab('Root.Main', $uuidField, $firstField);
173
        }
174
    }
175
176
    /**
177
     * @return void
178
     */
179
    public function onBeforeWrite()
180
    {
181
        parent::onBeforeWrite();
182
183
        if (!$this->owner->Uuid) {
184
            $this->assignNewUuid();
185
        }
186
    }
187
}
188