Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Passed
Pull Request — main (#5478)
by Pedro
15:02
created

Uploader   F

Complexity

Total Complexity 66

Size/Duplication

Total Lines 332
Duplicated Lines 0 %

Importance

Changes 25
Bugs 15 Features 1
Metric Value
eloc 118
c 25
b 15
f 1
dl 0
loc 332
rs 3.12
wmc 66

29 Methods

Rating   Name   Duplication   Size   Complexity  
A getAttributeName() 0 3 1
A retrieveUploadedFiles() 0 7 2
A getExpirationTimeInMinutes() 0 3 1
A for() 0 3 1
A getName() 0 3 1
A getPreviousFiles() 0 10 3
A getDisk() 0 3 1
A isRelationship() 0 3 1
A getPath() 0 3 1
A useTemporaryUrl() 0 3 1
A canHandleMultipleFiles() 0 3 1
A getIdentifier() 0 7 2
A deleteUploadedFiles() 0 11 4
A getNameForRequest() 0 3 1
A storeUploadedFiles() 0 19 5
A shouldDeleteFiles() 0 3 1
A __construct() 0 10 4
A getValueWithoutPath() 0 3 2
A uploadFiles() 0 2 1
A performFileDeletion() 0 9 2
A isFake() 0 3 1
A fake() 0 5 1
A getFakeAttribute() 0 3 1
A getPathFromConfiguration() 0 5 2
B deleteFiles() 0 33 9
A multiple() 0 5 1
A getOriginalValue() 0 23 6
A relationship() 0 5 1
B retrieveFiles() 0 26 8

How to fix   Complexity   

Complex Class

Complex classes like Uploader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Uploader, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Backpack\CRUD\app\Library\Uploaders;
4
5
use Backpack\CRUD\app\Library\Uploaders\Support\Interfaces\UploaderInterface;
6
use Backpack\CRUD\app\Library\Uploaders\Support\Traits\HandleFileNaming;
7
use Backpack\CRUD\app\Library\Uploaders\Support\Traits\HandleRepeatableUploads;
8
use Illuminate\Database\Eloquent\Model;
9
use Illuminate\Database\Eloquent\SoftDeletes;
10
use Illuminate\Support\Facades\Storage;
11
use Illuminate\Support\Str;
12
13
abstract class Uploader implements UploaderInterface
14
{
15
    use HandleFileNaming;
16
    use HandleRepeatableUploads;
0 ignored issues
show
introduced by
The trait Backpack\CRUD\app\Librar...HandleRepeatableUploads requires some properties which are not provided by Backpack\CRUD\app\Library\Uploaders\Uploader: $pivot, $exists, $pivotParent
Loading history...
17
18
    private string $name;
19
20
    private string $disk = 'public';
21
22
    private string $path = '';
23
24
    private bool $handleMultipleFiles = false;
25
26
    private bool $deleteWhenEntryIsDeleted = true;
27
28
    private bool|string $attachedToFakeField = false;
29
30
    /**
31
     * Cloud disks have the ability to generate temporary URLs to files, should we do it?
32
     */
33
    private bool $useTemporaryUrl = false;
34
35
    /**
36
     * When using temporary urls, define the time that the url will be valid.
37
     */
38
    private int $temporaryUrlExpirationTimeInMinutes = 1;
39
40
    /**
41
     * Indicates if the upload is relative to a relationship field/column.
42
     */
43
    private bool $isRelationship = false;
44
45
    /**
46
     * When previous files are updated, we need to keep track of them so that we don't add deleted files to the new list.
47
     */
48
    private $updatedPreviousFiles = null;
49
50
    public function __construct(array $crudObject, array $configuration)
51
    {
52
        $this->name = $crudObject['name'];
53
        $this->disk = $configuration['disk'] ?? $crudObject['disk'] ?? $this->disk;
54
        $this->path = $this->getPathFromConfiguration($crudObject, $configuration);
55
        $this->attachedToFakeField = isset($crudObject['fake']) && $crudObject['fake'] ? ($crudObject['store_in'] ?? 'extras') : ($crudObject['store_in'] ?? false);
56
        $this->useTemporaryUrl = $configuration['temporaryUrl'] ?? $this->useTemporaryUrl;
57
        $this->temporaryUrlExpirationTimeInMinutes = $configuration['temporaryUrlExpirationTime'] ?? $this->temporaryUrlExpirationTimeInMinutes;
58
        $this->deleteWhenEntryIsDeleted = $configuration['deleteWhenEntryIsDeleted'] ?? $this->deleteWhenEntryIsDeleted;
59
        $this->fileNamer = is_callable($configuration['fileNamer'] ?? null) ? $configuration['fileNamer'] : $this->getFileNameGeneratorInstance($configuration['fileNamer'] ?? null);
60
    }
61
62
    /*******************************
63
     * Static methods
64
     *******************************/
65
    public static function for(array $crudObject, array $definition): UploaderInterface
66
    {
67
        return new static($crudObject, $definition);
68
    }
69
70
    /*******************************
71
     * public methods - event handler methods
72
     *******************************/
73
    public function storeUploadedFiles(Model $entry): Model
74
    {
75
        if ($this->handleRepeatableFiles) {
76
            return $this->handleRepeatableFiles($entry);
77
        }
78
79
        if ($this->attachedToFakeField) {
80
            $fakeFieldValue = $entry->{$this->attachedToFakeField};
81
            $fakeFieldValue = is_string($fakeFieldValue) ? json_decode($fakeFieldValue, true) : (array) $fakeFieldValue;
82
            $fakeFieldValue[$this->getAttributeName()] = $this->uploadFiles($entry);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $fakeFieldValue[$this->getAttributeName()] is correct as $this->uploadFiles($entry) targeting Backpack\CRUD\app\Librar...Uploader::uploadFiles() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
83
84
            $entry->{$this->attachedToFakeField} = isset($entry->getCasts()[$this->attachedToFakeField]) ? $fakeFieldValue : json_encode($fakeFieldValue);
85
86
            return $entry;
87
        }
88
89
        $entry->{$this->getAttributeName()} = $this->uploadFiles($entry);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $entry->$this->getAttributeName() is correct as $this->uploadFiles($entry) targeting Backpack\CRUD\app\Librar...Uploader::uploadFiles() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
90
91
        return $entry;
92
    }
93
94
    public function retrieveUploadedFiles(Model $entry): Model
95
    {
96
        if ($this->handleRepeatableFiles) {
97
            return $this->retrieveRepeatableFiles($entry);
98
        }
99
100
        return $this->retrieveFiles($entry);
101
    }
102
103
    public function deleteUploadedFiles(Model $entry): void
104
    {
105
        if ($this->deleteWhenEntryIsDeleted) {
106
            if (! in_array(SoftDeletes::class, class_uses_recursive($entry), true)) {
107
                $this->performFileDeletion($entry);
108
109
                return;
110
            }
111
112
            if ($entry->isForceDeleting() === true) {
113
                $this->performFileDeletion($entry);
114
            }
115
        }
116
    }
117
118
    /*******************************
119
     * Getters
120
     *******************************/
121
    public function getName(): string
122
    {
123
        return $this->name;
124
    }
125
126
    public function getAttributeName(): string
127
    {
128
        return Str::afterLast($this->name, '.');
129
    }
130
131
    public function getDisk(): string
132
    {
133
        return $this->disk;
134
    }
135
136
    public function getPath(): string
137
    {
138
        return $this->path;
139
    }
140
141
    public function useTemporaryUrl(): bool
142
    {
143
        return $this->useTemporaryUrl;
144
    }
145
146
    public function getExpirationTimeInMinutes(): int
147
    {
148
        return $this->temporaryUrlExpirationTimeInMinutes;
149
    }
150
151
    public function shouldDeleteFiles(): bool
152
    {
153
        return $this->deleteWhenEntryIsDeleted;
154
    }
155
156
    public function getIdentifier(): string
157
    {
158
        if ($this->handleRepeatableFiles) {
159
            return $this->repeatableContainerName.'_'.$this->name;
160
        }
161
162
        return $this->name;
163
    }
164
165
    public function getNameForRequest(): string
166
    {
167
        return $this->repeatableContainerName ?? $this->name;
168
    }
169
170
    public function canHandleMultipleFiles(): bool
171
    {
172
        return $this->handleMultipleFiles;
173
    }
174
175
    public function isRelationship(): bool
176
    {
177
        return $this->isRelationship;
178
    }
179
180
    public function getPreviousFiles(Model $entry): mixed
181
    {
182
        if (! $this->attachedToFakeField) {
183
            return $this->getOriginalValue($entry);
184
        }
185
186
        $value = $this->getOriginalValue($entry, $this->attachedToFakeField);
187
        $value = is_string($value) ? json_decode($value, true) : (array) $value;
188
189
        return $value[$this->getAttributeName()] ?? null;
190
    }
191
192
    public function getValueWithoutPath(?string $value = null): ?string
193
    {
194
        return $value ? Str::after($value, $this->path) : null;
195
    }
196
197
    public function isFake(): bool
198
    {
199
        return $this->attachedToFakeField !== false;
200
    }
201
202
    public function getFakeAttribute(): bool|string
203
    {
204
        return $this->attachedToFakeField;
205
    }
206
207
    /*******************************
208
     * Setters - fluently configure the uploader
209
     *******************************/
210
    public function multiple(): self
211
    {
212
        $this->handleMultipleFiles = true;
213
214
        return $this;
215
    }
216
217
    public function relationship(bool $isRelationship): self
218
    {
219
        $this->isRelationship = $isRelationship;
220
221
        return $this;
222
    }
223
224
    public function fake(bool|string $isFake): self
225
    {
226
        $this->attachedToFakeField = $isFake;
227
228
        return $this;
229
    }
230
231
    /*******************************
232
     * Default implementation functions
233
     *******************************/
234
    public function uploadFiles(Model $entry, $values = null)
235
    {
236
    }
237
238
    private function retrieveFiles(Model $entry): Model
239
    {
240
        $value = $entry->{$this->getAttributeName()};
241
242
        if ($this->attachedToFakeField) {
243
            $values = $entry->{$this->attachedToFakeField};
244
245
            $values = is_string($values) ? json_decode($values, true) : $values;
246
247
            $values[$this->getAttributeName()] = isset($values[$this->getAttributeName()]) ? $this->getValueWithoutPath($values[$this->getAttributeName()]) : null;
248
            $entry->{$this->attachedToFakeField} = isset($entry->getCasts()[$this->attachedToFakeField]) ? $values : json_encode($values);
249
250
            return $entry;
251
        }
252
253
        if ($this->handleMultipleFiles) {
254
            if (! isset($entry->getCasts()[$this->getName()]) && is_string($value)) {
255
                $entry->{$this->getAttributeName()} = json_decode($value, true);
256
            }
257
258
            return $entry;
259
        }
260
261
        $entry->{$this->getAttributeName()} = $this->getValueWithoutPath($value);
262
263
        return $entry;
264
    }
265
266
    private function deleteFiles(Model $entry)
267
    {
268
        if (! $this->shouldDeleteFiles()) {
269
            return;
270
        }
271
272
        if ($this->attachedToFakeField) {
273
            $values = $entry->{$this->attachedToFakeField};
274
            $values = is_string($values) ? json_decode($values, true) : $values;
275
            $values = $values[$this->getAttributeName()] ?? null;
276
        }
277
278
        $values ??= $entry->{$this->getAttributeName()};
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $values does not seem to be defined for all execution paths leading up to this point.
Loading history...
279
280
        if ($values === null) {
281
            return;
282
        }
283
284
        if ($this->handleMultipleFiles) {
285
            // ensure we have an array of values when field is not casted in model.
286
            if (! isset($entry->getCasts()[$this->name]) && is_string($values)) {
287
                $values = json_decode($values, true);
288
            }
289
            foreach ($values ?? [] as $value) {
290
                $value = Str::start($value, $this->path);
291
                Storage::disk($this->disk)->delete($value);
292
            }
293
294
            return;
295
        }
296
297
        $values = Str::start($values, $this->path);
298
        Storage::disk($this->disk)->delete($values);
299
    }
300
301
    private function performFileDeletion(Model $entry)
302
    {
303
        if (! $this->handleRepeatableFiles) {
304
            $this->deleteFiles($entry);
305
306
            return;
307
        }
308
309
        $this->deleteRepeatableFiles($entry);
310
    }
311
312
    /*******************************
313
     * helper methods
314
     *******************************/
315
    private function getPathFromConfiguration(array $crudObject, array $configuration): string
316
    {
317
        $this->path = $configuration['path'] ?? $crudObject['prefix'] ?? $this->path;
318
319
        return empty($this->path) ? $this->path : Str::of($this->path)->finish('/')->value();
320
    }
321
322
    private function getOriginalValue(Model $entry, $field = null)
323
    {
324
        $field ??= $this->getAttributeName();
325
326
        if ($this->updatedPreviousFiles !== null) {
327
            return $this->updatedPreviousFiles;
328
        }
329
330
        $previousValue = $entry->getOriginal($field);
331
332
        if (! $previousValue) {
333
            return $previousValue;
334
        }
335
336
        if (
337
            method_exists($entry, 'translationEnabled') &&
338
            $entry->translationEnabled() &&
339
            $entry->isTranslatableAttribute($field)
340
        ) {
341
            return $previousValue[$entry->getLocale()] ?? null;
342
        }
343
344
        return $previousValue;
345
    }
346
}
347