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
Push — avoid-repetitive-calls-when-co... ( 2b861a )
by Pedro
11:16
created

CrudField::save()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 2
eloc 7
c 5
b 0
f 0
nc 2
nop 0
dl 0
loc 12
rs 10
1
<?php
2
3
namespace Backpack\CRUD\app\Library\CrudPanel;
4
5
use Backpack\CRUD\app\Library\CrudPanel\Traits\Support\MacroableWithAttributes;
6
use Illuminate\Support\Traits\Conditionable;
7
8
/**
9
 * Adds fluent syntax to Backpack CRUD Fields.
10
 *
11
 * In addition to the existing:
12
 * - CRUD::addField(['name' => 'price', 'type' => 'number']);
13
 *
14
 * Developers can also do:
15
 * - CRUD::field('price')->type('number');
16
 *
17
 * And if the developer uses CrudField as Field in their CrudController:
18
 * - Field::name('price')->type('number');
19
 *
20
 * @method self type(string $value)
21
 * @method self label(string $value)
22
 * @method self tab(string $value)
23
 * @method self prefix(string $value)
24
 * @method self suffix(string $value)
25
 * @method self default(mixed $value)
26
 * @method self hint(string $value)
27
 * @method self attributes(array $value)
28
 * @method self wrapper(array $value)
29
 * @method self fake(bool $value)
30
 * @method self store_in(string $value)
31
 * @method self validationRules(string $value)
32
 * @method self validationMessages(array $value)
33
 * @method self entity(string $value)
34
 * @method self addMorphOption(string $key, string $label, array $options)
35
 * @method self morphTypeField(array $value)
36
 * @method self morphIdField(array $value)
37
 * @method self upload(bool $value)
38
 */
39
class CrudField
40
{
41
    use MacroableWithAttributes;
0 ignored issues
show
Bug introduced by
The trait Backpack\CRUD\app\Librar...MacroableWithAttributes requires the property $name which is not provided by Backpack\CRUD\app\Library\CrudPanel\CrudField.
Loading history...
42
    use Conditionable;
43
44
    protected $attributes;
45
46
    public function __construct($nameOrDefinitionArray)
47
    {
48
        if (empty($nameOrDefinitionArray)) {
49
            abort(500, 'Field name can\'t be empty.');
50
        }
51
52
        if (is_array($nameOrDefinitionArray)) {
53
            $this->crud()->addField($nameOrDefinitionArray);
54
            $name = $nameOrDefinitionArray['name'];
55
        } else {
56
            $name = $nameOrDefinitionArray;
57
        }
58
59
        $field = $this->crud()->firstFieldWhere('name', $name);
60
61
        // if field exists
62
        if ((bool) $field) {
63
            // use all existing attributes
64
            $this->setAllAttributeValues($field);
0 ignored issues
show
Bug introduced by
$field of type boolean is incompatible with the type array expected by parameter $array of Backpack\CRUD\app\Librar...setAllAttributeValues(). ( Ignorable by Annotation )

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

64
            $this->setAllAttributeValues(/** @scrutinizer ignore-type */ $field);
Loading history...
65
        } else {
66
            // it means we're creating the field now,
67
            // so at the very least set the name attribute
68
            $this->setAttributeValue('name', $name);
69
        }
70
71
        $this->save();
72
    }
73
74
    public function crud()
75
    {
76
        return app()->make('crud');
77
    }
78
79
    /**
80
     * Create a CrudField object with the parameter as its name.
81
     *
82
     * @param  string  $name  Name of the column in the db, or model attribute.
83
     * @return CrudField
84
     */
85
    public static function name($name)
86
    {
87
        return new static($name);
88
    }
89
90
    /**
91
     * When defining the entity, make sure Backpack guesses the relationship attributes if needed.
92
     *
93
     * @param  string|bool  $entity
94
     * @return self
95
     */
96
    public function entity($entity)
97
    {
98
        $this->attributes['entity'] = $entity;
99
100
        if ($entity !== false) {
101
            $this->attributes = $this->crud()->makeSureFieldHasRelationshipAttributes($this->attributes);
102
        }
103
104
        return $this->save();
105
    }
106
107
    /**
108
     * Remove the current field from the current operation.
109
     *
110
     * @return void
111
     */
112
    public function remove()
113
    {
114
        $this->crud()->removeField($this->attributes['name']);
115
    }
116
117
    /**
118
     * Remove an attribute from the current field definition array.
119
     *
120
     * @param  string  $attribute  Name of the attribute being removed.
121
     * @return CrudField
122
     */
123
    public function forget($attribute)
124
    {
125
        $this->crud()->removeFieldAttribute($this->attributes['name'], $attribute);
126
127
        return $this;
128
    }
129
130
    /**
131
     * Move the current field after another field.
132
     *
133
     * @param  string  $destinationField  Name of the destination field.
134
     * @return CrudField
135
     */
136
    public function after($destinationField)
137
    {
138
        $this->crud()->removeField($this->attributes['name']);
139
        $this->crud()->addField($this->attributes)->afterField($destinationField);
140
141
        return $this;
142
    }
143
144
    /**
145
     * Move the current field before another field.
146
     *
147
     * @param  string  $destinationField  Name of the destination field.
148
     * @return CrudField
149
     */
150
    public function before($destinationField)
151
    {
152
        $this->crud()->removeField($this->attributes['name']);
153
        $this->crud()->addField($this->attributes)->beforeField($destinationField);
154
155
        return $this;
156
    }
157
158
    /**
159
     * Make the current field the first one in the fields list.
160
     *
161
     * @return CrudField
162
     */
163
    public function makeFirst()
164
    {
165
        $this->crud()->removeField($this->attributes['name']);
166
        $this->crud()->addField($this->attributes)->makeFirstField();
167
168
        return $this;
169
    }
170
171
    /**
172
     * Make the current field the last one in the fields list.
173
     *
174
     * @return CrudField
175
     */
176
    public function makeLast()
177
    {
178
        $this->crud()->removeField($this->attributes['name']);
179
        $this->crud()->addField($this->attributes);
180
181
        return $this;
182
    }
183
184
    // -------------------
185
    // CONVENIENCE METHODS
186
    // -------------------
187
    // These methods don't do exactly what advertised by their name.
188
    // They exist because the original syntax was too long.
189
190
    /**
191
     * Set the wrapper width at this many number of columns.
192
     * For example, to set a field wrapper to span across 6 columns, you can do both:
193
     * ->wrapper(['class' => 'form-group col-md-6'])
194
     * ->size(6).
195
     *
196
     * @param  int  $numberOfColumns  How many columns should this field span across (1-12)?
197
     * @return CrudField
198
     */
199
    public function size($numberOfColumns)
200
    {
201
        $this->attributes['wrapper']['class'] = 'form-group col-md-'.$numberOfColumns;
202
203
        return $this->save();
204
    }
205
206
    /**
207
     * Set an event to a certain closure. Will overwrite if existing.
208
     *
209
     * @param  string  $event  Name of Eloquent Model event
210
     * @param  \Closure  $closure  The function aka callback aka closure to run.
211
     * @return CrudField
212
     */
213
    public function on(string $event, \Closure $closure)
214
    {
215
        $this->attributes['events'][$event] = $closure;
216
217
        return $this->save();
218
    }
219
220
    /**
221
     * When subfields are defined, pass them through the guessing function
222
     * so that they have label, relationship attributes, etc.
223
     *
224
     * @param  array  $subfields  Subfield definition array
225
     * @return self
226
     */
227
    public function subfields($subfields)
228
    {
229
        $callAttributeMacro = ! isset($this->attributes['subfields']);
230
        $this->attributes['subfields'] = $subfields;
231
        $this->attributes = $this->crud()->makeSureFieldHasNecessaryAttributes($this->attributes);
232
        if ($callAttributeMacro) {
233
            $this->callRegisteredAttributeMacros();
234
        }
235
236
        return $this->save();
237
    }
238
239
    /**
240
     * Mark the field has having upload functionality, so that the form would become multipart.
241
     *
242
     * @param  bool  $upload
243
     * @return self
244
     */
245
    public function upload($upload = true)
246
    {
247
        $this->attributes['upload'] = $upload;
248
249
        return $this->save();
250
    }
251
252
    /**
253
     * Save the validation rules on the CrudPanel per field basis.
254
     *
255
     * @param  string  $rules  the field rules: required|min:1|max:5
256
     * @return self
257
     */
258
    public function validationRules(string $rules)
259
    {
260
        $this->attributes['validationRules'] = $rules;
261
        $this->crud()->setValidationFromArray([$this->attributes['name'] => $rules]);
262
263
        return $this;
264
    }
265
266
    /**
267
     * Save the validation messages on the CrudPanel per field basis.
268
     *
269
     * @param  array  $messages  the messages for field rules: [required => please input something, min => the minimum allowed is 1]
270
     * @return self
271
     */
272
    public function validationMessages(array $messages)
273
    {
274
        $this->attributes['validationMessages'] = $messages;
275
276
        // append the field name to the rule name of validationMessages array.
277
        // eg: ['required => 'This field is required']
278
        // will be transformed into: ['field_name.required' => 'This field is required]
279
        $this->crud()->setValidationFromArray([], array_merge(...array_map(function ($rule, $message) {
280
            return [$this->attributes['name'].'.'.$rule => $message];
281
        }, array_keys($messages), $messages)));
282
283
        return $this;
284
    }
285
286
    /**
287
     * This function is responsible for setting up the morph fields structure.
288
     * Developer can define the morph structure as follows:
289
     *  'morphOptions => [
290
     *       ['nameOnAMorphMap', 'label', [options]],
291
     *       ['App\Models\Model'], // display the name of the model
292
     *       ['App\Models\Model', 'label', ['data_source' => backpack_url('smt')]
293
     *  ]
294
     * OR
295
     * ->addMorphOption('App\Models\Model', 'label', ['data_source' => backpack_url('smt')]).
296
     *
297
     * @param  string  $key  - the morph option key, usually a \Model\Class or a string for the morphMap
298
     * @param  string|null  $label  - the displayed text for this option
299
     * @param  array  $options  - options for the corresponding morphable_id field (usually ajax options)
300
     * @return self
301
     *
302
     * @throws \Exception
303
     */
304
    public function addMorphOption(string $key, $label = null, array $options = [])
305
    {
306
        $this->crud()->addMorphOption($this->attributes['name'], $key, $label, $options);
307
308
        return $this;
309
    }
310
311
    /**
312
     * Allow developer to configure the morph type field.
313
     *
314
     * @param  array  $configs
315
     * @return self
316
     *
317
     * @throws \Exception
318
     */
319
    public function morphTypeField(array $configs)
320
    {
321
        $morphField = $this->crud()->fields()[$this->attributes['name']];
322
323
        if (empty($morphField) || ($morphField['relation_type'] ?? '') !== 'MorphTo') {
324
            throw new \Exception('Trying to configure the morphType on a non-morphTo field. Check if field and relation name matches.');
325
        }
326
        [$morphTypeField, $morphIdField] = $morphField['subfields'];
327
328
        $morphTypeField = array_merge($morphTypeField, $configs);
329
330
        $morphField['subfields'] = [$morphTypeField, $morphIdField];
331
332
        $this->crud()->modifyField($this->attributes['name'], $morphField);
333
334
        return $this;
335
    }
336
337
    /**
338
     * Allow developer to configure the morph type id selector.
339
     *
340
     * @param  array  $configs
341
     * @return self
342
     *
343
     * @throws \Exception
344
     */
345
    public function morphIdField(array $configs)
346
    {
347
        $morphField = $this->crud()->fields()[$this->attributes['name']];
348
349
        if (empty($morphField) || ($morphField['relation_type'] ?? '') !== 'MorphTo') {
350
            throw new \Exception('Trying to configure the morphType on a non-morphTo field. Check if field and relation name matches.');
351
        }
352
353
        [$morphTypeField, $morphIdField] = $morphField['subfields'];
354
355
        $morphIdField = array_merge($morphIdField, $configs);
356
357
        $morphField['subfields'] = [$morphTypeField, $morphIdField];
358
359
        $this->crud()->modifyField($this->attributes['name'], $morphField);
360
361
        return $this;
362
    }
363
364
    public function getAttributes()
365
    {
366
        return $this->attributes;
367
    }
368
369
    // ---------------
370
    // PRIVATE METHODS
371
    // ---------------
372
373
    /**
374
     * Set the value for a certain attribute on the CrudField object.
375
     *
376
     * @param  string  $attribute  Name of the attribute.
377
     * @param  mixed  $value  Value of that attribute.
378
     */
379
    private function setAttributeValue($attribute, $value)
380
    {
381
        $this->attributes[$attribute] = $value;
382
    }
383
384
    /**
385
     * Replace all field attributes on the CrudField object
386
     * with the given array of attribute-value pairs.
387
     *
388
     * @param  array  $array  Array of attributes and their values.
389
     */
390
    private function setAllAttributeValues($array)
391
    {
392
        $this->attributes = $array;
393
    }
394
395
    /**
396
     * Update the global CrudPanel object with the current field attributes.
397
     *
398
     * @return CrudField
399
     */
400
    private function save()
401
    {
402
        $key = $this->attributes['name'];
403
404
        if ($this->crud()->hasFieldWhere('name', $key)) {
405
            $this->crud()->modifyField($key, $this->attributes);
406
        } else {
407
            $this->crud()->addField($this->attributes);
408
            $this->attributes = $this->getFreshAttributes();
409
        }
410
411
        return $this;
412
    }
413
414
    /**
415
     * Get the fresh attributes for the current field.
416
     *
417
     * @return array
418
     */
419
    private function getFreshAttributes()
420
    {
421
        $key = isset($this->attributes['key']) ? 'key' : 'name';
422
        $search = $this->attributes['key'] ?? $this->attributes['name'];
423
424
        return $this->crud()->firstFieldWhere($key, $search);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->crud()->fi...eldWhere($key, $search) returns the type boolean which is incompatible with the documented return type array.
Loading history...
425
    }
426
427
    // -----------------
428
    // DEBUGGING METHODS
429
    // -----------------
430
431
    /**
432
     * Dump the current object to the screen,
433
     * so that the developer can see its contents.
434
     *
435
     * @codeCoverageIgnore
436
     *
437
     * @return CrudField
438
     */
439
    public function dump()
440
    {
441
        dump($this);
442
443
        return $this;
444
    }
445
446
    /**
447
     * Dump and die. Dumps the current object to the screen,
448
     * so that the developer can see its contents, then stops
449
     * the execution.
450
     *
451
     * @codeCoverageIgnore
452
     *
453
     * @return CrudField
454
     */
455
    public function dd()
456
    {
457
        dd($this);
458
459
        return $this;
460
    }
461
462
    // -------------
463
    // MAGIC METHODS
464
    // -------------
465
466
    /**
467
     * If a developer calls a method that doesn't exist, assume they want:
468
     * - the CrudField object to have an attribute with that value;
469
     * - that field be updated inside the global CrudPanel object;.
470
     *
471
     * Eg: type('number') will set the "type" attribute to "number"
472
     *
473
     * @param  string  $method  The method being called that doesn't exist.
474
     * @param  array  $parameters  The arguments when that method was called.
475
     * @return CrudField
476
     */
477
    public function __call($method, $parameters)
478
    {
479
        if (static::hasMacro($method)) {
480
            return $this->macroCall($method, $parameters);
0 ignored issues
show
Bug introduced by
The method macroCall() does not exist on Backpack\CRUD\app\Library\CrudPanel\CrudField. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

480
            return $this->/** @scrutinizer ignore-call */ macroCall($method, $parameters);
Loading history...
481
        }
482
483
        $this->setAttributeValue($method, $parameters[0]);
484
485
        return $this->save();
486
    }
487
}
488