Completed
Push — master ( 1f8d58...093904 )
by Michael
01:48
created

GeneratesUuid::uuidColumn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
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
        'ordered',
38
    ];
39
40
    /**
41
     * Determine whether an attribute should be cast to a native type.
42
     *
43
     * @param  string  $key
44
     * @param  array|string|null  $types
45
     * @return bool
46
     */
47
    abstract public function hasCast($key, $types = null);
48
49
    /**
50
     * Boot the trait, adding a creating observer.
51
     *
52
     * When persisting a new model instance, we resolve the UUID field, then set
53
     * a fresh UUID, taking into account if we need to cast to binary or not.
54
     *
55
     * @return void
56
     */
57
    public static function bootGeneratesUuid(): void
58
    {
59
        static::creating(function ($model) {
60
            foreach ($model->uuidColumns() as $item) {
61
                /* @var \Illuminate\Database\Eloquent\Model|static $model */
62
                $uuid = $model->resolveUuid();
63
64
                if (isset($model->attributes[$item]) && ! is_null($model->attributes[$item])) {
65
                    /* @var \Ramsey\Uuid\Uuid $uuid */
66
                    try {
67
                        $uuid = $uuid->fromString(strtolower($model->attributes[$item]));
68
                    } catch (InvalidUuidStringException $e) {
69
                        $uuid = $uuid->fromBytes($model->attributes[$item]);
70
                    }
71
                }
72
73
                $model->{$item} = strtolower($uuid->toString());
74
            }
75
        });
76
    }
77
78
    /**
79
     * The name of the column that should be used for the UUID.
80
     *
81
     * @return string
82
     */
83
    public function uuidColumn(): string
84
    {
85
        return 'uuid';
86
    }
87
88
    /**
89
     * The names of the columns that should be used for the UUID.
90
     *
91
     * @return array
92
     */
93
    public function uuidColumns(): array
94
    {
95
        return [$this->uuidColumn()];
96
    }
97
98
    /**
99
     * Resolve a UUID instance for the configured version.
100
     *
101
     * @return \Ramsey\Uuid\Uuid
102
     */
103
    public function resolveUuid(): Uuid
104
    {
105
        if (($version = $this->resolveUuidVersion()) == 'ordered') {
106
            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...
107
        }
108
109
        return call_user_func([Uuid::class, $version]);
110
    }
111
112
    /**
113
     * Resolve the UUID version to use when setting the UUID value. Default to uuid4.
114
     *
115
     * @return string
116
     */
117 6
    public function resolveUuidVersion(): string
118
    {
119 6
        if (property_exists($this, 'uuidVersion') && in_array($this->uuidVersion, $this->uuidVersions)) {
120 5
            return $this->uuidVersion;
121
        }
122
123 1
        return 'uuid4';
124
    }
125
126
    /**
127
     * Scope queries to find by UUID.
128
     *
129
     * @param  \Illuminate\Database\Eloquent\Builder  $query
130
     * @param  string  $uuid
131
     * @param  string  $uuidColumn
132
     *
133
     * @return \Illuminate\Database\Eloquent\Builder
134
     */
135
    public function scopeWhereUuid($query, $uuid, $uuidColumn = null): Builder
136
    {
137
        $uuidColumn = ! is_null($uuidColumn) && in_array($uuidColumn, $this->uuidColumns())
138
            ? $uuidColumn
139
            : $this->uuidColumns()[0];
140
141
        $uuid = array_map(function ($uuid) {
142
            return Str::lower($uuid);
143
        }, Arr::wrap($uuid));
144
145
        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

145
        if ($this->/** @scrutinizer ignore-call */ isClassCastable($uuidColumn)) {
Loading history...
146
            $uuid = $this->bytesFromUuid($uuid);
147
        }
148
149
        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...
150
    }
151
152
    /**
153
     * Convert a single UUID or array of UUIDs to bytes.
154
     *
155
     * @param  \Illuminate\Contracts\Support\Arrayable|array|string  $uuid
156
     * @return array
157
     */
158
    protected function bytesFromUuid($uuid): array
159
    {
160
        if (is_array($uuid) || $uuid instanceof Arrayable) {
161
            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

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