Completed
Push — master ( bb971d...3b3979 )
by Michael
02:03
created

GeneratesUuid::resolveUuid()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 7
ccs 4
cts 4
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Dyrynda\Database\Support;
4
5
use Ramsey\Uuid\Uuid;
6
use Illuminate\Support\Arr;
7
use Illuminate\Support\Str;
8
use Illuminate\Database\Eloquent\Builder;
9
use Illuminate\Contracts\Support\Arrayable;
10
11
/**
12
 * UUID generation trait.
13
 *
14
 * Include this trait in any Eloquent model where you wish to automatically set
15
 * a UUID field. When saving, if the UUID field has not been set, generate a
16
 * new UUID value, which will be set on the model and saved by Eloquent.
17
 *
18
 * @copyright 2017 Michael Dyrynda
19
 * @author    Michael Dyrynda <[email protected]>
20
 * @license   MIT
21
 *
22
 * @property  string  $uuidVersion
23
 */
24
trait GeneratesUuid
25
{
26
    /**
27
     * The UUID versions.
28
     *
29
     * @var array
30
     */
31
    protected $uuidVersions = [
32
        'uuid1',
33
        'uuid3',
34
        'uuid4',
35
        'uuid5',
36
        'ordered',
37
    ];
38
39
    /**
40
     * Determine whether an attribute should be cast to a native type.
41
     *
42
     * @param  string  $key
43
     * @param  array|string|null  $types
44
     * @return bool
45
     */
46
    abstract public function hasCast($key, $types = null);
47
48
    /**
49
     * Boot the trait, adding a creating observer.
50
     *
51
     * When persisting a new model instance, we resolve the UUID field, then set
52
     * a fresh UUID, taking into account if we need to cast to binary or not.
53
     *
54
     * @return void
55
     */
56 12
    public static function bootGeneratesUuid(): void
57
    {
58
        static::creating(function ($model) {
59
            /* @var \Illuminate\Database\Eloquent\Model|static $model */
60 12
            $uuid = $model->resolveUuid();
61 12
            foreach ($model->uuidColumns() as $item) {
62 12
                if (isset($model->attributes[$item]) && ! is_null($model->attributes[$item])) {
63
                    /* @var \Ramsey\Uuid\Uuid $uuid */
64 7
                    $uuid = $uuid->fromString(strtolower($model->attributes[$item]));
65
                }
66 12
                $model->attributes[$item] = $model->hasCast($item, 'uuid') ? $uuid->getBytes() : $uuid->toString();
0 ignored issues
show
Bug Best Practice introduced by
The property attributes does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
67
            }
68 12
        });
69 6
    }
70
71
    /**
72
     * The name of the column that should be used for the UUID.
73
     *
74
     * @return string
75
     */
76 8
    public function uuidColumn(): string
77
    {
78 8
        return 'uuid';
79
    }
80
81
    /**
82
     * The names of the columns that should be used for the UUID.
83
     *
84
     * @return array
85
     */
86 11
    public function uuidColumns(): array
87
    {
88 11
        return [$this->uuidColumn()];
89
    }
90
91
    /**
92
     * Resolve a UUID instance for the configured version.
93
     *
94
     * @return \Ramsey\Uuid\Uuid
95
     */
96 12
    public function resolveUuid(): Uuid
97
    {
98 12
        if (($version = $this->resolveUuidVersion()) == 'ordered') {
99 1
            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...
100
        }
101
102 11
        return call_user_func([Uuid::class, $version]);
103
    }
104
105
    /**
106
     * Resolve the UUID version to use when setting the UUID value. Default to uuid4.
107
     *
108
     * @return string
109
     */
110 18
    public function resolveUuidVersion(): string
111
    {
112 18
        if (property_exists($this, 'uuidVersion') && in_array($this->uuidVersion, $this->uuidVersions)) {
113 6
            return $this->uuidVersion;
114
        }
115
116 12
        return 'uuid4';
117
    }
118
119
    /**
120
     * Scope queries to find by UUID.
121
     *
122
     * @param  \Illuminate\Database\Eloquent\Builder  $query
123
     * @param  string  $uuid
124
     * @param  string  $uuidColumn
125
     *
126
     * @return \Illuminate\Database\Eloquent\Builder
127
     */
128 5
    public function scopeWhereUuid($query, $uuid, $uuidColumn = null): Builder
129
    {
130 5
        $uuidColumn = ! is_null($uuidColumn) && in_array($uuidColumn, $this->uuidColumns())
131 2
            ? $uuidColumn
132 5
            : $this->uuidColumns()[0];
133
134 5
        if ($this->hasCast($uuidColumn)) {
135 4
            $uuid = $this->bytesFromUuid($uuid);
136
        }
137
138 5
        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...
139
    }
140
141
    /**
142
     * Convert a single UUID or array of UUIDs to bytes
143
     *
144
     * @param  \Illuminate\Contracts\Support\Arrayable|array|string  $uuid
145
     * @return array
146
     */
147 4
    protected function bytesFromUuid($uuid): array
148
    {
149 4
        if (is_array($uuid) || $uuid instanceof Arrayable) {
150
            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

150
            array_walk(/** @scrutinizer ignore-type */ $uuid, function (&$uuid) {
Loading history...
151 2
                $uuid = $this->resolveUuid()->fromString($uuid)->getBytes();
152 2
            });
153
154 2
            return $uuid;
155
        }
156
157 2
        return Arr::wrap($this->resolveUuid()->fromString($uuid)->getBytes());
158
    }
159
160
    /**
161
     * Cast an attribute to a native PHP type.
162
     *
163
     * @param  string  $key
164
     * @param  mixed  $value
165
     * @return mixed
166
     */
167 5
    protected function castAttribute($key, $value)
168
    {
169 5
        if (in_array($key, $this->uuidColumns()) && ! empty($value)) {
170 5
            return $this->resolveUuid()->fromBytes($value)->toString();
171
        }
172
173
        return parent::castAttribute($key, $value);
174
    }
175
}
176