GeneratesUuid   A
last analyzed

Complexity

Total Complexity 19

Size/Duplication

Total Lines 145
Duplicated Lines 0 %

Test Coverage

Coverage 97.3%

Importance

Changes 9
Bugs 1 Features 1
Metric Value
eloc 39
c 9
b 1
f 1
dl 0
loc 145
ccs 36
cts 37
cp 0.973
rs 10
wmc 19

7 Methods

Rating   Name   Duplication   Size   Complexity  
A resolveUuidVersion() 0 7 3
A scopeWhereUuid() 0 15 4
A uuidColumn() 0 3 1
A bootGeneratesUuid() 0 17 5
A resolveUuid() 0 7 2
A bytesFromUuid() 0 11 3
A uuidColumns() 0 3 1
1
<?php
2
3
namespace Dyrynda\Database\Support;
4
5
use Illuminate\Contracts\Support\Arrayable;
6
use Illuminate\Database\Eloquent\Builder;
7
use Illuminate\Support\Arr;
8
use Illuminate\Support\Str;
9
use Ramsey\Uuid\Exception\InvalidUuidStringException;
10
use Ramsey\Uuid\Uuid;
11
12
/**
13
 * UUID generation trait.
14
 *
15
 * Include this trait in any Eloquent model where you wish to automatically set
16
 * a UUID field. When saving, if the UUID field has not been set, generate a
17
 * new UUID value, which will be set on the model and saved by Eloquent.
18
 *
19
 * @copyright 2017 Michael Dyrynda
20
 * @author    Michael Dyrynda <[email protected]>
21
 * @license   MIT
22
 *
23
 * @property  string  $uuidVersion
24
 */
25
trait GeneratesUuid
26
{
27
    /**
28
     * The UUID versions.
29
     *
30
     * @var array
31
     */
32
    protected $uuidVersions = [
33
        'uuid1',
34
        'uuid3',
35
        'uuid4',
36
        'uuid5',
37
        'uuid6',
38
        'ordered',
39
    ];
40
41
    /**
42
     * Determine whether an attribute should be cast to a native type.
43
     *
44
     * @param  string  $key
45
     * @param  array|string|null  $types
46
     * @return bool
47
     */
48
    abstract public function hasCast($key, $types = null);
49
50
    /**
51
     * Boot the trait, adding a creating observer.
52
     *
53
     * When persisting a new model instance, we resolve the UUID field, then set
54
     * a fresh UUID, taking into account if we need to cast to binary or not.
55
     *
56
     * @return void
57 18
     */
58
    public static function bootGeneratesUuid(): void
59
    {
60 18
        static::creating(function ($model) {
61
            foreach ($model->uuidColumns() as $item) {
62 18
                /* @var \Illuminate\Database\Eloquent\Model|static $model */
63
                $uuid = $model->resolveUuid();
64 18
65
                if (isset($model->attributes[$item]) && ! is_null($model->attributes[$item])) {
66
                    /* @var \Ramsey\Uuid\Uuid $uuid */
67 13
                    try {
68 4
                        $uuid = $uuid->fromString(strtolower($model->attributes[$item]));
69 4
                    } catch (InvalidUuidStringException $e) {
70
                        $uuid = $uuid->fromBytes($model->attributes[$item]);
0 ignored issues
show
Bug introduced by
The method fromBytes() does not exist on Ramsey\Uuid\UuidInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Ramsey\Uuid\Rfc4122\UuidInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

70
                        /** @scrutinizer ignore-call */ 
71
                        $uuid = $uuid->fromBytes($model->attributes[$item]);
Loading history...
71
                    }
72
                }
73 18
74
                $model->{$item} = strtolower($uuid->toString());
75 18
            }
76 18
        });
77
    }
78
79
    /**
80
     * The name of the column that should be used for the UUID.
81
     *
82
     * @return string
83 9
     */
84
    public function uuidColumn(): string
85 9
    {
86
        return 'uuid';
87
    }
88
89
    /**
90
     * The names of the columns that should be used for the UUID.
91
     *
92
     * @return array
93 17
     */
94
    public function uuidColumns(): array
95 17
    {
96
        return [$this->uuidColumn()];
97
    }
98
99
    /**
100
     * Resolve a UUID instance for the configured version.
101
     *
102
     * @return \Ramsey\Uuid\Uuid
103 18
     */
104
    public function resolveUuid(): Uuid
105 18
    {
106 1
        if (($version = $this->resolveUuidVersion()) == 'ordered') {
107
            return Str::orderedUuid();
0 ignored issues
show
Bug Best Practice introduced by
The expression return Illuminate\Support\Str::orderedUuid() returns the type Ramsey\Uuid\UuidInterface which includes types incompatible with the type-hinted return Ramsey\Uuid\Uuid.
Loading history...
108
        }
109 17
110
        return call_user_func([Uuid::class, $version]);
111
    }
112
113
    /**
114
     * Resolve the UUID version to use when setting the UUID value. Default to uuid4.
115
     *
116
     * @return string
117 24
     */
118
    public function resolveUuidVersion(): string
119 24
    {
120 6
        if (property_exists($this, 'uuidVersion') && in_array($this->uuidVersion, $this->uuidVersions)) {
121
            return $this->uuidVersion;
122
        }
123 18
124
        return 'uuid4';
125
    }
126
127
    /**
128
     * Scope queries to find by UUID.
129
     *
130
     * @param  \Illuminate\Database\Eloquent\Builder  $query
131
     * @param  string  $uuid
132
     * @param  string  $uuidColumn
133
     *
134
     * @return \Illuminate\Database\Eloquent\Builder
135 7
     */
136
    public function scopeWhereUuid($query, $uuid, $uuidColumn = null): Builder
137 7
    {
138 2
        $uuidColumn = ! is_null($uuidColumn) && in_array($uuidColumn, $this->uuidColumns())
139 7
            ? $uuidColumn
140
            : $this->uuidColumns()[0];
141
142 7
        $uuid = array_map(function ($uuid) {
143 7
            return Str::lower($uuid);
144
        }, Arr::wrap($uuid));
145 7
146 2
        if ($this->isClassCastable($uuidColumn)) {
0 ignored issues
show
Bug introduced by
It seems like isClassCastable() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

146
        if ($this->/** @scrutinizer ignore-call */ isClassCastable($uuidColumn)) {
Loading history...
147
            $uuid = $this->bytesFromUuid($uuid);
148
        }
149 7
150
        return $query->whereIn($uuidColumn, Arr::wrap($uuid));
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->whereIn($...pport\Arr::wrap($uuid)) could return the type Illuminate\Database\Query\Builder which is incompatible with the type-hinted return Illuminate\Database\Eloquent\Builder. Consider adding an additional type-check to rule them out.
Loading history...
151
    }
152
153
    /**
154
     * Convert a single UUID or array of UUIDs to bytes.
155
     *
156
     * @param  \Illuminate\Contracts\Support\Arrayable|array|string  $uuid
157
     * @return array
158 2
     */
159
    protected function bytesFromUuid($uuid): array
160 2
    {
161
        if (is_array($uuid) || $uuid instanceof Arrayable) {
162 2
            array_walk($uuid, function (&$uuid) {
0 ignored issues
show
Bug introduced by
It seems like $uuid can also be of type Illuminate\Contracts\Support\Arrayable; however, parameter $array of array_walk() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

162
            array_walk(/** @scrutinizer ignore-type */ $uuid, function (&$uuid) {
Loading history...
163 2
                $uuid = $this->resolveUuid()->fromString($uuid)->getBytes();
164
            });
165 2
166
            return $uuid;
167
        }
168
169
        return Arr::wrap($this->resolveUuid()->fromString($uuid)->getBytes());
170
    }
171
}
172