Test Failed
Push — master ( 6508c2...e5864c )
by Terzi
14:31
created

Saver::process()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 1
eloc 6
c 1
b 1
f 0
nc 1
nop 0
dl 0
loc 13
ccs 0
cts 6
cp 0
crap 2
rs 10
1
<?php
2
3
namespace Terranet\Administrator\Services;
4
5
use Illuminate\Database\Eloquent\Model;
0 ignored issues
show
Bug introduced by
The type Illuminate\Database\Eloquent\Model was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use Spatie\MediaLibrary\HasMedia\HasMedia;
0 ignored issues
show
Bug introduced by
The type Spatie\MediaLibrary\HasMedia\HasMedia was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7
use Terranet\Administrator\Contracts\Services\Saver as SaverContract;
8
use Terranet\Administrator\Field\BelongsTo;
9
use Terranet\Administrator\Field\BelongsToMany;
10
use Terranet\Administrator\Field\Boolean;
11
use Terranet\Administrator\Field\File;
12
use Terranet\Administrator\Field\HasMany;
13
use Terranet\Administrator\Field\HasOne;
14
use Terranet\Administrator\Field\Id;
15
use Terranet\Administrator\Field\Image;
16
use Terranet\Administrator\Field\Media;
17
use Terranet\Administrator\Field\Traits\HandlesRelation;
18
use Terranet\Administrator\Requests\UpdateRequest;
19
use function admin\db\scheme;
20
21
class Saver implements SaverContract
22
{
23
    use HandlesRelation;
0 ignored issues
show
introduced by
The trait Terranet\Administrator\F...\Traits\HandlesRelation requires some properties which are not provided by Terranet\Administrator\Services\Saver: $model, $id
Loading history...
24
25
    /**
26
     * Data collected during saving process.
27
     *
28
     * @var array
29
     */
30
    protected $data = [];
31
32
    /**
33
     * List of relations queued for saving.
34
     *
35
     * @var array
36
     */
37
    protected $relations = [];
38
39
    /**
40
     * Main module repository.
41
     *
42
     * @var Model
43
     */
44
    protected $repository;
45
46
    /**
47
     * @var UpdateRequest
48
     */
49
    protected $request;
50
51
    /**
52
     * Saver constructor.
53
     *
54
     * @param               $eloquent
55
     * @param UpdateRequest $request
56
     */
57
    public function __construct($eloquent, UpdateRequest $request)
58
    {
59
        $this->repository = $eloquent;
60
        $this->request = $request;
61
    }
62
63
    /**
64
     * Process request and persist data.
65
     *
66
     * @return mixed
67
     */
68
    public function sync()
69
    {
70
        $this->connection()->transaction(function () {
71
            foreach ($this->editable() as $field) {
72
                // get original HTML input
73
                $name = $field->id();
74
75
                if ($this->isKey($field) || $this->isMediaFile($field)) {
76
                    continue;
77
                }
78
79
                if ($isRelation = $this->isRelation($field)) {
0 ignored issues
show
Unused Code introduced by
The assignment to $isRelation is dead and can be removed.
Loading history...
80
                    $this->relations[$name] = $field;
81
                }
82
83
                $value = $this->isFile($field) ? $this->request->file($name) : $this->request->get($name);
84
85
                $value = $this->isBoolean($field) ? (bool) $value : $value;
86
87
                $value = $this->handleJsonType($name, $value);
88
89
                $this->data[$name] = $value;
90
            }
91
92
            $this->cleanData();
93
94
            $this->collectTranslatable();
95
96
            $this->appendTranslationsToRelations();
97
98
            Model::unguard();
99
100
            /*
101
            |-------------------------------------------------------
102
            | Save main data
103
            |-------------------------------------------------------
104
            */
105
            $this->process();
106
107
            Model::reguard();
108
        });
109
110
        return $this->repository;
111
    }
112
113
    /**
114
     * Fetch editable fields.
115
     *
116
     * @return mixed
117
     */
118
    protected function editable()
119
    {
120
        return app('scaffold.module')->form();
0 ignored issues
show
Bug introduced by
The function app was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

120
        return /** @scrutinizer ignore-call */ app('scaffold.module')->form();
Loading history...
121
    }
122
123
    /**
124
     * @param $field
125
     *
126
     * @return bool
127
     */
128
    protected function isKey($field)
129
    {
130
        return $field instanceof Id;
131
    }
132
133
    /**
134
     * @param $field
135
     *
136
     * @return bool
137
     */
138
    protected function isFile($field)
139
    {
140
        return $field instanceof File || $field instanceof Image;
141
    }
142
143
    /**
144
     * Protect request data against external data.
145
     */
146
    protected function cleanData()
147
    {
148
        $this->data = array_except($this->data, [
0 ignored issues
show
Deprecated Code introduced by
The function array_except() has been deprecated: Arr::except() should be used directly instead. Will be removed in Laravel 5.9. ( Ignorable by Annotation )

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

148
        $this->data = /** @scrutinizer ignore-deprecated */ array_except($this->data, [

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
149
            '_token',
150
            'save',
151
            'save_create',
152
            'save_return',
153
            $this->repository->getKeyName(),
154
        ]);
155
156
        // clean from relationable fields.
157
        $this->data = array_diff_key($this->data, $this->relations);
158
    }
159
160
    /**
161
     * Persist data.
162
     */
163
    protected function process()
164
    {
165
        $this->nullifyEmptyNullables($this->repository->getTable());
166
167
        $this->repository->fill(
168
            $this->protectAgainstNullPassword()
169
        );
170
171
        $this->saveRelations();
172
173
        $this->repository->save();
174
175
        $this->saveMedia();
176
    }
177
178
    /**
179
     * Save relations.
180
     */
181
    protected function saveRelations()
182
    {
183
        foreach ($this->relations as $name => $field) {
184
            $relation = \call_user_func([$this->repository, $name]);
185
186
            switch (\get_class($field)) {
187
                case BelongsTo::class:
188
                    // @var \Illuminate\Database\Eloquent\Relations\BelongsTo $relation
189
                    $relation->associate(
190
                        $this->request->get($this->getForeignKey($relation))
191
                    );
192
193
                    break;
194
                case HasOne::class:
195
                    /** @var \Illuminate\Database\Eloquent\Relations\HasOne $relation */
196
                    $related = $relation->getResults();
197
198
                    $related && $related->exists
199
                        ? $relation->update($this->request->get($name))
200
                        : $relation->create($this->request->get($name));
201
202
                    break;
203
                case BelongsToMany::class:
204
                    $values = array_map('intval', $this->request->get($name, []));
205
                    $relation->sync($values);
206
207
                    break;
208
                default:
209
                    break;
210
            }
211
        }
212
    }
213
214
    /**
215
     * Process Media.
216
     */
217
    protected function saveMedia()
218
    {
219
        if ($this->repository instanceof HasMedia) {
220
            $media = (array) $this->request['_media_'];
221
222
            if (!empty($trash = array_get($media, '_trash_', []))) {
0 ignored issues
show
Deprecated Code introduced by
The function array_get() has been deprecated: Arr::get() should be used directly instead. Will be removed in Laravel 5.9. ( Ignorable by Annotation )

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

222
            if (!empty($trash = /** @scrutinizer ignore-deprecated */ array_get($media, '_trash_', []))) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
223
                $this->repository->media()->whereIn(
224
                    'id',
225
                    $trash
226
                )->delete();
227
            }
228
229
            foreach (array_except($media, '_trash_') as $collection => $objects) {
0 ignored issues
show
Deprecated Code introduced by
The function array_except() has been deprecated: Arr::except() should be used directly instead. Will be removed in Laravel 5.9. ( Ignorable by Annotation )

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

229
            foreach (/** @scrutinizer ignore-deprecated */ array_except($media, '_trash_') as $collection => $objects) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
230
                foreach ($objects as $uploadedFile) {
231
                    $this->repository->addMedia($uploadedFile)->toMediaCollection($collection);
232
                }
233
            }
234
        }
235
    }
236
237
    /**
238
     * Remove null values from data.
239
     *
240
     * @param $relation
241
     * @param $values
242
     *
243
     * @return array
244
     */
245
    protected function forgetNullValues($relation, $values)
246
    {
247
        $keys = explode('.', $this->getQualifiedRelatedKeyName($relation));
0 ignored issues
show
Bug introduced by
The method getQualifiedRelatedKeyName() does not exist on Terranet\Administrator\Services\Saver. ( Ignorable by Annotation )

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

247
        $keys = explode('.', $this->/** @scrutinizer ignore-call */ getQualifiedRelatedKeyName($relation));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
248
        $key = array_pop($keys);
249
250
        return array_filter((array) $values[$key], function ($value) {
251
            return null !== $value;
252
        });
253
    }
254
255
    /**
256
     * Collect relations for saving.
257
     *
258
     * @param $field
259
     */
260
    protected function isRelation($field)
261
    {
262
        return ($field instanceof BelongsTo)
263
            || ($field instanceof HasOne)
264
            || ($field instanceof HasMany)
265
            || ($field instanceof BelongsToMany);
266
    }
267
268
    /**
269
     * @param $this
270
     */
271
    protected function appendTranslationsToRelations()
272
    {
273
        if (!empty($this->relations)) {
274
            foreach (array_keys((array) $this->relations) as $relation) {
275
                if ($translations = $this->input("{$relation}.translatable")) {
276
                    $this->relations[$relation] += $translations;
277
                }
278
            }
279
        }
280
    }
281
282
    /**
283
     * @param $name
284
     * @param $value
285
     *
286
     * @return mixed
287
     */
288
    protected function handleJsonType($name, $value)
289
    {
290
        if ($cast = array_get($this->repository->getCasts(), $name)) {
0 ignored issues
show
Deprecated Code introduced by
The function array_get() has been deprecated: Arr::get() should be used directly instead. Will be removed in Laravel 5.9. ( Ignorable by Annotation )

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

290
        if ($cast = /** @scrutinizer ignore-deprecated */ array_get($this->repository->getCasts(), $name)) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
291
            if (\in_array($cast, ['array', 'json'], true)) {
292
                $value = json_decode($value);
293
            }
294
        }
295
296
        return $value;
297
    }
298
299
    /**
300
     * Collect translations.
301
     */
302
    protected function collectTranslatable()
303
    {
304
        foreach ($this->request->get('translatable', []) as $key => $value) {
305
            $this->data[$key] = $value;
306
        }
307
    }
308
309
    /**
310
     * Get database connection.
311
     *
312
     * @return \Illuminate\Foundation\Application|mixed
0 ignored issues
show
Bug introduced by
The type Illuminate\Foundation\Application was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
313
     */
314
    protected function connection()
315
    {
316
        return app('db');
0 ignored issues
show
Bug introduced by
The function app was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

316
        return /** @scrutinizer ignore-call */ app('db');
Loading history...
317
    }
318
319
    /**
320
     * Retrieve request input value.
321
     *
322
     * @param $key
323
     * @param null $default
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $default is correct as it would always require null to be passed?
Loading history...
324
     *
325
     * @return mixed
326
     */
327
    protected function input($key, $default = null)
328
    {
329
        return app('request')->input($key, $default);
0 ignored issues
show
Bug introduced by
The function app was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

329
        return /** @scrutinizer ignore-call */ app('request')->input($key, $default);
Loading history...
330
    }
331
332
    /**
333
     * Set empty "nullable" values to null.
334
     *
335
     * @param $table
336
     */
337
    protected function nullifyEmptyNullables($table)
338
    {
339
        $columns = scheme()->columns($table);
340
341
        foreach ($this->data as $key => &$value) {
342
            if (!array_key_exists($key, $columns)) {
343
                continue;
344
            }
345
346
            if (!$columns[$key]->getNotnull() && empty($value)) {
347
                $value = null;
348
            }
349
        }
350
    }
351
352
    /**
353
     * Ignore empty password from being saved.
354
     *
355
     * @return array
356
     */
357
    protected function protectAgainstNullPassword(): array
358
    {
359
        if (array_has($this->data, 'password') && empty($this->data['password'])) {
0 ignored issues
show
Deprecated Code introduced by
The function array_has() has been deprecated: Arr::has() should be used directly instead. Will be removed in Laravel 5.9. ( Ignorable by Annotation )

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

359
        if (/** @scrutinizer ignore-deprecated */ array_has($this->data, 'password') && empty($this->data['password'])) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
360
            unset($this->data['password']);
361
        }
362
363
        return $this->data;
364
    }
365
366
    /**
367
     * @param $field
368
     *
369
     * @return bool
370
     */
371
    protected function isBoolean($field)
372
    {
373
        return $field instanceof Boolean;
374
    }
375
376
    /**
377
     * @param $field
378
     *
379
     * @return bool
380
     */
381
    protected function isMediaFile($field)
382
    {
383
        return $field instanceof Media;
384
    }
385
}
386