Completed
Pull Request — master (#71)
by
unknown
01:57
created

HasBinaryUuid::getUuidSuffix()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
3
namespace Spatie\BinaryUuid;
4
5
use Exception;
6
use Ramsey\Uuid\Uuid;
7
use Illuminate\Database\Eloquent\Model;
8
9
trait HasBinaryUuid
10
{
11
    protected static function bootHasBinaryUuid()
12
    {
13
        static::creating(function (Model $model)
14
        {
15
            $keyName = (array) $model->getKeyName();
16
            $uuidKeys = $model->getUuidKeys(); 
17
18
            foreach ($keyName as $key) {
19
                 if (! in_array($key, $uuidKeys) || $model->{$key}) {
20
                    continue;
21
                 }
22
23
                 $model->{$key} = static::encodeUuid(static::generateUuid());
24
            }
25
        });
26
    }
27
28
    public static function scopeWithUuid(Builder $builder, $uuid, $field = null): Builder
29
    {
30
        if ($field) {
31
            return static::scopeWithUuidRelation($builder, $uuid, $field);
32
        }
33
34
        if ($uuid instanceof Uuid) {
35
            $uuid = (string) $uuid;
36
        }
37
38
        $uuid = (array) $uuid;
39
40
        return $builder->whereKey(array_map(function (string $modelUuid) {
41
            return static::encodeUuid($modelUuid);
42
        }, $uuid));
43
    }
44
45
    public static function scopeWithUuidRelation(Builder $builder, $uuid, string $field): Builder
46
    {
47
        if ($uuid instanceof Uuid) {
48
            $uuid = (string) $uuid;
49
        }
50
51
        $uuid = (array) $uuid;
52
53
        return $builder->whereIn($field, array_map(function (string $modelUuid) {
0 ignored issues
show
Documentation Bug introduced by
The method whereIn does not exist on object<Spatie\BinaryUuid\Builder>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
54
            return static::encodeUuid($modelUuid);
55
        }, $uuid));
56
    }
57
58
    public static function generateUuid() : string
59
    {
60
        return Uuid::uuid1();
61
    }
62
63
    public static function encodeUuid($uuid): string
64
    {
65
        if (! Uuid::isValid($uuid)) {
66
            return $uuid;
67
        }
68
69
        if (! $uuid instanceof Uuid) {
70
            $uuid = Uuid::fromString($uuid);
71
        }
72
73
        return $uuid->getBytes();
74
    }
75
76
    public static function decodeUuid(string $binaryUuid): string
77
    {
78
        if (Uuid::isValid($binaryUuid)) {
79
            return $binaryUuid;
80
        }
81
82
        return Uuid::fromBytes($binaryUuid)->toString();
83
    }
84
85
    public function isBinary($uuidStr)
86
    {
87
        return preg_match('~[^\x20-\x7E\t\r\n]~', $uuidStr) > 0;
88
    }
89
90
    public function getUuidKeys()
91
    {
92
        return (property_exists($this, 'uuids') && is_array($this->uuids)) ? $this->uuids : [];
0 ignored issues
show
Bug introduced by
The property uuids does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
93
    }
94
95
    public function toArray()
96
    {
97
        $uuidAttributes = $this->getUuidAttributes();
98
99
        $array = parent::toArray();
100
        $pivotUuids = [];
0 ignored issues
show
Unused Code introduced by
$pivotUuids is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
101
102
        if (! $this->exists || ! is_array($uuidAttributes)) {
0 ignored issues
show
Bug introduced by
The property exists does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
103
            return $array;
104
        }
105
106
        foreach ($uuidAttributes as $attributeKey) {
107
            if (! array_key_exists($attributeKey, $array)) {
108
                continue;
109
            }
110
            $uuidKey = $this->getRelatedBinaryKeyName($attributeKey);
111
            $array[$attributeKey] = $this->{$uuidKey};
112
        }
113
114
        if (isset($array['pivot'])) {
115
            $pivotUuids = $array['pivot'];
116
            
117
            if (! is_array($pivotUuids)) {
118
                $pivotUuids = get_object_vars($pivotUuids);
119
            }
120
121
            foreach ($pivotUuids as $key => $uuid) {
122
                if ($this->isBinary($uuid)) {
123
                    $pivotUuids[$key] = $this->decodeUuid($uuid);
124
                }
125
            }
126
127
            $array['pivot'] = $pivotUuids;
128
        }
129
130
        return $array;
131
    }
132
133
    public function getRelatedBinaryKeyName($attribute): string
134
    {
135
        $suffix = $this->getUuidSuffix();
136
137
        return preg_match('/(?:uu)?id/i', $attribute) ? "{$attribute}{$suffix}" : $attribute;
138
    }
139
140
    public function getAttribute($key)
141
    {
142
        $uuidKey = $this->uuidTextAttribute($key);
143
144
        if ($uuidKey && $this->{$uuidKey} !== null) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $uuidKey of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
145
            return static::decodeUuid($this->{$uuidKey});
146
        }
147
148
        return parent::getAttribute($key);
149
    }
150
151
    public function setAttribute($key, $value)
152
    {
153
        if ($this->uuidTextAttribute($key)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->uuidTextAttribute($key) of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
154
            $value = static::encodeUuid($value);
155
        }
156
157
        return parent::setAttribute($key, $value);
158
    }
159
160
    protected function getUuidSuffix()
161
    {
162
        return (property_exists($this, 'uuidSuffix')) ? $this->uuidSuffix : '_text';
0 ignored issues
show
Bug introduced by
The property uuidSuffix does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
163
    }
164
165
    protected function uuidTextAttribute($key)
166
    {
167
        $uuidAttributes = $this->getUuidAttributes();
168
        $suffix = $this->getUuidSuffix();
169
        $offset = -(strlen($suffix));
170
171
        if (substr($key, $offset) == $suffix && in_array(($uuidKey = substr($key, 0, $offset)), $uuidAttributes)) {
172
            return $uuidKey;
173
        }
174
175
        return false;
176
    }
177
178
    public function getUuidAttributes()
179
    {
180
        $uuidKeys = $this->getUuidKeys();
181
        
182
        // non composite primary keys will return a string so casting required
183
        $key = (array) $this->getKeyName();
184
185
        $uuidAttributes = array_unique(array_merge($uuidKeys, $key));
186
187
        return $uuidAttributes;
188
    }
189
190
    public function getUuidTextAttribute(): ?string
191
    {
192
        $key = $this->getKeyName();
193
194
        if (! $this->exists || is_array($key)) {
195
            return null;
196
        }
197
198
        return static::decodeUuid($this->{$key});
199
    }
200
201
    public function setUuidTextAttribute(string $uuid)
202
    {
203
        $key = $this->getKeyName();
204
205
        if (is_array($key)) {
206
            throw new Exception("composite keys not allowed for attribute mutation");
207
        }
208
209
        $this->{$key} = static::encodeUuid($uuid);
210
    }
211
212
    public function getQueueableId()
213
    {
214
        return base64_encode($this->{$this->getKeyName()});
215
    }
216
217
    public function newQueryForRestoration($id)
218
    {
219
        return $this->newQueryWithoutScopes()->whereKey(base64_decode($id));
0 ignored issues
show
Bug introduced by
It seems like newQueryWithoutScopes() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
220
    }
221
222
    public function newEloquentBuilder($query)
223
    {
224
        return new Builder($query);
225
    }
226
227
    public function getRouteKeyName()
228
    {
229
        $suffix = $this->getUuidSuffix();
230
231
        return "uuid{$suffix}";
232
    }
233
234
    public function getKeyName()
235
    {
236
        return 'uuid';
237
    }
238
239
    public function getIncrementing()
240
    {
241
        return false;
242
    }
243
244
    public function resolveRouteBinding($value)
245
    {
246
        return $this->withUuid($value)->first();
0 ignored issues
show
Bug introduced by
The method withUuid() does not exist on Spatie\BinaryUuid\HasBinaryUuid. Did you maybe mean scopeWithUuid()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
247
    }
248
}
249