Passed
Push — master ( 481611...24b609 )
by Thomas
10:55
created

UuidExtension::getByUuid()   B

Complexity

Conditions 7
Paths 15

Size

Total Lines 33
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

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