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 — master ( a03c4e...c1988a )
by Cristian
13:09 queued 13s
created

CrudPanel   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 433
Duplicated Lines 0 %

Importance

Changes 9
Bugs 3 Features 0
Metric Value
wmc 50
eloc 114
c 9
b 3
f 0
dl 0
loc 433
rs 8.4

21 Methods

Rating   Name   Duplication   Size   Complexity  
A setRouteName() 0 10 2
A getSchema() 0 3 1
A getActionName() 0 3 1
A getRoute() 0 3 1
A getAction() 0 3 1
A getModel() 0 3 1
A getFirstOfItsTypeInArray() 0 4 1
A getRelationModel() 0 23 4
A actionIs() 0 3 1
A setRoute() 0 3 1
A getRequest() 0 3 1
A getActionMethod() 0 3 1
A sync() 0 10 3
A driverIsMongoDb() 0 3 1
A setRequest() 0 3 1
A setModel() 0 13 3
A __construct() 0 6 2
A setEntityNameStrings() 0 4 1
B getRelatedEntriesAttributes() 0 38 9
B getRelatedEntries() 0 31 7
B parseTranslatableAttributes() 0 25 7

How to fix   Complexity   

Complex Class

Complex classes like CrudPanel 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 CrudPanel, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Backpack\CRUD\app\Library\CrudPanel;
4
5
use Backpack\CRUD\app\Library\CrudPanel\Traits\Access;
6
use Backpack\CRUD\app\Library\CrudPanel\Traits\AutoFocus;
7
use Backpack\CRUD\app\Library\CrudPanel\Traits\AutoSet;
8
use Backpack\CRUD\app\Library\CrudPanel\Traits\Buttons;
9
use Backpack\CRUD\app\Library\CrudPanel\Traits\Columns;
10
use Backpack\CRUD\app\Library\CrudPanel\Traits\Create;
11
use Backpack\CRUD\app\Library\CrudPanel\Traits\Delete;
12
use Backpack\CRUD\app\Library\CrudPanel\Traits\Errors;
13
use Backpack\CRUD\app\Library\CrudPanel\Traits\FakeColumns;
14
use Backpack\CRUD\app\Library\CrudPanel\Traits\FakeFields;
15
use Backpack\CRUD\app\Library\CrudPanel\Traits\Fields;
16
use Backpack\CRUD\app\Library\CrudPanel\Traits\Filters;
17
use Backpack\CRUD\app\Library\CrudPanel\Traits\HeadingsAndTitles;
18
use Backpack\CRUD\app\Library\CrudPanel\Traits\Macroable;
19
use Backpack\CRUD\app\Library\CrudPanel\Traits\Operations;
20
use Backpack\CRUD\app\Library\CrudPanel\Traits\Query;
21
use Backpack\CRUD\app\Library\CrudPanel\Traits\Read;
22
use Backpack\CRUD\app\Library\CrudPanel\Traits\Relationships;
23
use Backpack\CRUD\app\Library\CrudPanel\Traits\Reorder;
24
use Backpack\CRUD\app\Library\CrudPanel\Traits\SaveActions;
25
use Backpack\CRUD\app\Library\CrudPanel\Traits\Search;
26
use Backpack\CRUD\app\Library\CrudPanel\Traits\Settings;
27
use Backpack\CRUD\app\Library\CrudPanel\Traits\Tabs;
28
use Backpack\CRUD\app\Library\CrudPanel\Traits\Update;
29
use Backpack\CRUD\app\Library\CrudPanel\Traits\Validation;
30
use Backpack\CRUD\app\Library\CrudPanel\Traits\Views;
31
use Exception;
32
use Illuminate\Database\Eloquent\Collection;
33
use Illuminate\Database\Eloquent\Model;
34
use Illuminate\Database\Eloquent\Relations\Relation;
35
use Illuminate\Support\Arr;
36
37
class CrudPanel
38
{
39
    // load all the default CrudPanel features
40
    use Create, Read, Search, Update, Delete, Errors, Reorder, Access, Columns, Fields, Query, Buttons, AutoSet, FakeFields, FakeColumns, AutoFocus, Filters, Tabs, Views, Validation, HeadingsAndTitles, Operations, SaveActions, Settings, Relationships;
0 ignored issues
show
introduced by
The trait Backpack\CRUD\app\Library\CrudPanel\Traits\Buttons requires some properties which are not provided by Backpack\CRUD\app\Library\CrudPanel\CrudPanel: $stack, $name
Loading history...
Bug introduced by
The trait Backpack\CRUD\app\Librar...Panel\Traits\Operations requires the property $action which is not provided by Backpack\CRUD\app\Library\CrudPanel\CrudPanel.
Loading history...
introduced by
The trait Backpack\CRUD\app\Library\CrudPanel\Traits\Filters requires some properties which are not provided by Backpack\CRUD\app\Library\CrudPanel\CrudPanel: $values, $logic, $fallbackLogic, $name
Loading history...
41
    // allow developers to add their own closures to this object
42
    use Macroable;
0 ignored issues
show
Bug introduced by
The trait Backpack\CRUD\app\Librar...dPanel\Traits\Macroable requires the property $name which is not provided by Backpack\CRUD\app\Library\CrudPanel\CrudPanel.
Loading history...
43
44
    // --------------
45
    // CRUD variables
46
    // --------------
47
    // These variables are passed to the CRUD views, inside the $crud variable.
48
    // All variables are public, so they can be modified from your EntityCrudController.
49
    // All functions and methods are also public, so they can be used in your EntityCrudController to modify these variables.
50
51
    public $model = "\App\Models\Entity"; // what's the namespace for your entity's model
52
    public $route; // what route have you defined for your entity? used for links.
53
    public $entity_name = 'entry'; // what name will show up on the buttons, in singural (ex: Add entity)
54
    public $entity_name_plural = 'entries'; // what name will show up on the buttons, in plural (ex: Delete 5 entities)
55
56
    public $entry;
57
58
    protected $request;
59
60
    // The following methods are used in CrudController or your EntityCrudController to manipulate the variables above.
61
62
    public function __construct()
63
    {
64
        $this->setRequest();
65
66
        if ($this->getCurrentOperation()) {
67
            $this->setOperation($this->getCurrentOperation());
68
        }
69
    }
70
71
    /**
72
     * Set the request instance for this CRUD.
73
     *
74
     * @param \Illuminate\Http\Request $request
75
     */
76
    public function setRequest($request = null)
77
    {
78
        $this->request = $request ?? \Request::instance();
79
    }
80
81
    /**
82
     * [getRequest description].
83
     * @return [type] [description]
0 ignored issues
show
Documentation Bug introduced by
The doc comment [type] at position 0 could not be parsed: Unknown type name '[' at position 0 in [type].
Loading history...
84
     */
85
    public function getRequest()
86
    {
87
        return $this->request;
88
    }
89
90
    // ------------------------------------------------------
91
    // BASICS - model, route, entity_name, entity_name_plural
92
    // ------------------------------------------------------
93
94
    /**
95
     * This function binds the CRUD to its corresponding Model (which extends Eloquent).
96
     * All Create-Read-Update-Delete operations are done using that Eloquent Collection.
97
     *
98
     * @param string $model_namespace Full model namespace. Ex: App\Models\Article
99
     *
100
     * @throws \Exception in case the model does not exist
101
     */
102
    public function setModel($model_namespace)
103
    {
104
        if (! class_exists($model_namespace)) {
105
            throw new \Exception('The model does not exist.', 500);
106
        }
107
108
        if (! method_exists($model_namespace, 'hasCrudTrait')) {
109
            throw new \Exception('Please use CrudTrait on the model.', 500);
110
        }
111
112
        $this->model = new $model_namespace();
113
        $this->query = $this->model->select('*');
114
        $this->entry = null;
115
    }
116
117
    /**
118
     * Get the corresponding Eloquent Model for the CrudController, as defined with the setModel() function.
119
     *
120
     * @return string|\Illuminate\Database\Eloquent\Model
121
     */
122
    public function getModel()
123
    {
124
        return $this->model;
125
    }
126
127
    /**
128
     * Get the database connection, as specified in the .env file or overwritten by the property on the model.
129
     *
130
     * @return \Illuminate\Database\Schema\Builder
131
     */
132
    private function getSchema()
133
    {
134
        return $this->getModel()->getConnection()->getSchemaBuilder();
135
    }
136
137
    /**
138
     * Check if the database connection driver is using mongodb.
139
     *
140
     * @return bool
141
     */
142
    private function driverIsMongoDb()
143
    {
144
        return $this->getSchema()->getConnection()->getConfig()['driver'] === 'mongodb';
145
    }
146
147
    /**
148
     * Set the route for this CRUD.
149
     * Ex: admin/article.
150
     *
151
     * @param string $route Route name.
152
     */
153
    public function setRoute($route)
154
    {
155
        $this->route = $route;
156
    }
157
158
    /**
159
     * Set the route for this CRUD using the route name.
160
     * Ex: admin.article.
161
     *
162
     * @param string $route      Route name.
163
     * @param array  $parameters Parameters.
164
     *
165
     * @throws \Exception
166
     */
167
    public function setRouteName($route, $parameters = [])
168
    {
169
        $complete_route = $route.'.index';
170
171
        if (! \Route::has($complete_route)) {
172
            throw new \Exception('There are no routes for this route name.', 404);
173
        }
174
175
        $this->route = route($complete_route, $parameters);
176
        $this->initButtons();
0 ignored issues
show
Bug introduced by
The method initButtons() does not exist on Backpack\CRUD\app\Library\CrudPanel\CrudPanel. 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

176
        $this->/** @scrutinizer ignore-call */ 
177
               initButtons();
Loading history...
177
    }
178
179
    /**
180
     * Get the current CrudController route.
181
     *
182
     * Can be defined in the CrudController with:
183
     * - $this->crud->setRoute(config('backpack.base.route_prefix').'/article')
184
     * - $this->crud->setRouteName(config('backpack.base.route_prefix').'.article')
185
     * - $this->crud->route = config('backpack.base.route_prefix')."/article"
186
     *
187
     * @return string
188
     */
189
    public function getRoute()
190
    {
191
        return $this->route;
192
    }
193
194
    /**
195
     * Set the entity name in singular and plural.
196
     * Used all over the CRUD interface (header, add button, reorder button, breadcrumbs).
197
     *
198
     * @param string $singular Entity name, in singular. Ex: article
199
     * @param string $plural   Entity name, in plural. Ex: articles
200
     */
201
    public function setEntityNameStrings($singular, $plural)
202
    {
203
        $this->entity_name = $singular;
204
        $this->entity_name_plural = $plural;
205
    }
206
207
    // -----------------------------------------------
208
    // ACTIONS - the current operation being processed
209
    // -----------------------------------------------
210
211
    /**
212
     * Get the action being performed by the controller,
213
     * including middleware names, route name, method name,
214
     * namespace, prefix, etc.
215
     *
216
     * @return string The EntityCrudController route action array.
217
     */
218
    public function getAction()
219
    {
220
        return $this->getRequest()->route()->getAction();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getRequest()->route()->getAction() also could return the type array which is incompatible with the documented return type string.
Loading history...
221
    }
222
223
    /**
224
     * Get the full name of the controller method
225
     * currently being called (including namespace).
226
     *
227
     * @return string The EntityCrudController full method name with namespace.
228
     */
229
    public function getActionName()
230
    {
231
        return $this->getRequest()->route()->getActionName();
232
    }
233
234
    /**
235
     * Get the name of the controller method
236
     * currently being called.
237
     *
238
     * @return string The EntityCrudController method name.
239
     */
240
    public function getActionMethod()
241
    {
242
        return $this->getRequest()->route()->getActionMethod();
243
    }
244
245
    /**
246
     * Check if the controller method being called
247
     * matches a given string.
248
     *
249
     * @param string $methodName Name of the method (ex: index, create, update)
250
     *
251
     * @return bool Whether the condition is met or not.
252
     */
253
    public function actionIs($methodName)
254
    {
255
        return $methodName === $this->getActionMethod();
256
    }
257
258
    // ----------------------------------
259
    // Miscellaneous functions or methods
260
    // ----------------------------------
261
262
    /**
263
     * Return the first element in an array that has the given 'type' attribute.
264
     *
265
     * @param string $type
266
     * @param array  $array
267
     *
268
     * @return array
269
     */
270
    public function getFirstOfItsTypeInArray($type, $array)
271
    {
272
        return Arr::first($array, function ($item) use ($type) {
273
            return $item['type'] == $type;
274
        });
275
    }
276
277
    // ------------
278
    // TONE FUNCTIONS - UNDOCUMENTED, UNTESTED, SOME MAY BE USED IN THIS FILE
279
    // ------------
280
    //
281
    // TODO:
282
    // - figure out if they are really needed
283
    // - comments inside the function to explain how they work
284
    // - write docblock for them
285
    // - place in the correct section above (CREATE, READ, UPDATE, DELETE, ACCESS, MANIPULATION)
286
287
    public function sync($type, $fields, $attributes)
288
    {
289
        if (! empty($this->{$type})) {
290
            $this->{$type} = array_map(function ($field) use ($fields, $attributes) {
291
                if (in_array($field['name'], (array) $fields)) {
292
                    $field = array_merge($field, $attributes);
293
                }
294
295
                return $field;
296
            }, $this->{$type});
297
        }
298
    }
299
300
    /**
301
     * Get the Eloquent Model name from the given relation definition string.
302
     *
303
     * @example For a given string 'company' and a relation between App/Models/User and App/Models/Company, defined by a
304
     *          company() method on the user model, the 'App/Models/Company' string will be returned.
305
     * @example For a given string 'company.address' and a relation between App/Models/User, App/Models/Company and
306
     *          App/Models/Address defined by a company() method on the user model and an address() method on the
307
     *          company model, the 'App/Models/Address' string will be returned.
308
     *
309
     * @param string                              $relationString Relation string. A dot notation can be used to chain multiple relations.
310
     * @param int                                 $length         Optionally specify the number of relations to omit from the start of the relation string. If
311
     *                                                            the provided length is negative, then that many relations will be omitted from the end of the relation
312
     *                                                            string.
313
     * @param \Illuminate\Database\Eloquent\Model $model          Optionally specify a different model than the one in the crud object.
314
     *
315
     * @return string Relation model name.
316
     */
317
    public function getRelationModel($relationString, $length = null, $model = null)
318
    {
319
        $relationArray = explode('.', $relationString);
320
321
        if (! isset($length)) {
322
            $length = count($relationArray);
323
        }
324
325
        if (! isset($model)) {
326
            $model = $this->model;
327
        }
328
329
        $result = array_reduce(array_splice($relationArray, 0, $length), function ($obj, $method) {
330
            try {
331
                $result = $obj->$method();
332
333
                return $result->getRelated();
334
            } catch (Exception $e) {
335
                return $obj;
336
            }
337
        }, $model);
338
339
        return get_class($result);
340
    }
341
342
    /**
343
     * Get the given attribute from a model or models resulting from the specified relation string (eg: the list of streets from
344
     * the many addresses of the company of a given user).
345
     *
346
     * @param \Illuminate\Database\Eloquent\Model $model          Model (eg: user).
347
     * @param string                              $relationString Model relation. Can be a string representing the name of a relation method in the given
348
     *                                                            Model or one from a different Model through multiple relations. A dot notation can be used to specify
349
     *                                                            multiple relations (eg: user.company.address).
350
     * @param string                              $attribute      The attribute from the relation model (eg: the street attribute from the address model).
351
     *
352
     * @return array An array containing a list of attributes from the resulting model.
353
     */
354
    public function getRelatedEntriesAttributes($model, $relationString, $attribute)
355
    {
356
        $endModels = $this->getRelatedEntries($model, $relationString);
357
        $attributes = [];
358
        foreach ($endModels as $model => $entries) {
0 ignored issues
show
introduced by
$model is overwriting one of the parameters of this function.
Loading history...
359
            $model_instance = new $model();
360
            $modelKey = $model_instance->getKeyName();
361
362
            if (is_array($entries)) {
363
                //if attribute does not exist in main array we have more than one entry OR the attribute
364
                //is an acessor that is not in $appends property of model.
365
                if (! isset($entries[$attribute])) {
366
                    //we first check if we don't have the attribute because it's and acessor that is not in appends.
367
                    if ($model_instance->hasGetMutator($attribute) && isset($entries[$modelKey])) {
368
                        $entry_in_database = $model_instance->find($entries[$modelKey]);
369
                        $attributes[$entry_in_database->{$modelKey}] = $this->parseTranslatableAttributes($model_instance, $attribute, $entry_in_database->{$attribute});
370
                    } else {
371
                        //we have multiple entries
372
                        //for each entry we check if $attribute exists in array or try to check if it's an acessor.
373
                        foreach ($entries as $entry) {
374
                            if (isset($entry[$attribute])) {
375
                                $attributes[$entry[$modelKey]] = $this->parseTranslatableAttributes($model_instance, $attribute, $entry[$attribute]);
376
                            } else {
377
                                if ($model_instance->hasGetMutator($attribute)) {
378
                                    $entry_in_database = $model_instance->find($entry[$modelKey]);
379
                                    $attributes[$entry_in_database->{$modelKey}] = $this->parseTranslatableAttributes($model_instance, $attribute, $entry_in_database->{$attribute});
380
                                }
381
                            }
382
                        }
383
                    }
384
                } else {
385
                    //if we have the attribute we just return it, does not matter if it is direct attribute or an acessor added in $appends.
386
                    $attributes[$entries[$modelKey]] = $this->parseTranslatableAttributes($model_instance, $attribute, $entries[$attribute]);
387
                }
388
            }
389
        }
390
391
        return $attributes;
392
    }
393
394
    /**
395
     * Parse translatable attributes from a model or models resulting from the specified relation string.
396
     *
397
     * @param \Illuminate\Database\Eloquent\Model $model          Model (eg: user).
398
     * @param string                              $attribute      The attribute from the relation model (eg: the street attribute from the address model).
399
     * @param string                              $value          Attribute value translatable or not
400
     *
401
     * @return string A string containing the translated attributed based on app()->getLocale()
402
     */
403
    public function parseTranslatableAttributes($model, $attribute, $value)
404
    {
405
        if (! method_exists($model, 'isTranslatableAttribute')) {
406
            return $value;
407
        }
408
409
        if (! $model->isTranslatableAttribute($attribute)) {
410
            return $value;
411
        }
412
413
        if (! is_array($value)) {
0 ignored issues
show
introduced by
The condition is_array($value) is always false.
Loading history...
414
            $decodedAttribute = json_decode($value, true);
415
        } else {
416
            $decodedAttribute = $value;
417
        }
418
419
        if (is_array($decodedAttribute) && ! empty($decodedAttribute)) {
420
            if (isset($decodedAttribute[app()->getLocale()])) {
0 ignored issues
show
introduced by
The method getLocale() does not exist on Illuminate\Container\Container. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

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

420
            if (isset($decodedAttribute[app()->/** @scrutinizer ignore-call */ getLocale()])) {
Loading history...
421
                return $decodedAttribute[app()->getLocale()];
422
            } else {
423
                return Arr::first($decodedAttribute);
424
            }
425
        }
426
427
        return $value;
428
    }
429
430
    /**
431
     * Traverse the tree of relations for the given model, defined by the given relation string, and return the ending
432
     * associated model instance or instances.
433
     *
434
     * @param \Illuminate\Database\Eloquent\Model $model          The CRUD model.
435
     * @param string                              $relationString Relation string. A dot notation can be used to chain multiple relations.
436
     *
437
     * @return array An array of the associated model instances defined by the relation string.
438
     */
439
    private function getRelatedEntries($model, $relationString)
440
    {
441
        $relationArray = explode('.', $relationString);
442
        $firstRelationName = Arr::first($relationArray);
443
        $relation = $model->{$firstRelationName};
444
445
        $results = [];
446
        if (! is_null($relation)) {
447
            if ($relation instanceof Collection) {
448
                $currentResults = $relation->all();
449
            } elseif (is_array($relation)) {
450
                $currentResults = $relation;
451
            } elseif ($relation instanceof Model) {
452
                $currentResults = [$relation];
453
            } else {
454
                $currentResults = [];
455
            }
456
457
            array_shift($relationArray);
458
459
            if (! empty($relationArray)) {
460
                foreach ($currentResults as $currentResult) {
461
                    $results = array_merge_recursive($results, $this->getRelatedEntries($currentResult, implode('.', $relationArray)));
462
                }
463
            } else {
464
                $relatedClass = get_class($model->{$firstRelationName}()->getRelated());
465
                $results[$relatedClass] = $currentResults;
466
            }
467
        }
468
469
        return $results;
470
    }
471
}
472