Completed
Push — master ( 89dae3...54cddc )
by Thomas
19s queued 12s
created

UuidExtension::getBaseTableName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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