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

CrudPanel   B
last analyzed

Complexity

Total Complexity 52

Size/Duplication

Total Lines 464
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 117
dl 0
loc 464
rs 7.44
c 0
b 0
f 0
wmc 52

23 Methods

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

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\HasViewNamespaces;
18
use Backpack\CRUD\app\Library\CrudPanel\Traits\HeadingsAndTitles;
19
use Backpack\CRUD\app\Library\CrudPanel\Traits\Input;
20
use Backpack\CRUD\app\Library\CrudPanel\Traits\Macroable;
21
use Backpack\CRUD\app\Library\CrudPanel\Traits\MorphRelationships;
22
use Backpack\CRUD\app\Library\CrudPanel\Traits\Operations;
23
use Backpack\CRUD\app\Library\CrudPanel\Traits\Query;
24
use Backpack\CRUD\app\Library\CrudPanel\Traits\Read;
25
use Backpack\CRUD\app\Library\CrudPanel\Traits\Relationships;
26
use Backpack\CRUD\app\Library\CrudPanel\Traits\Reorder;
27
use Backpack\CRUD\app\Library\CrudPanel\Traits\SaveActions;
28
use Backpack\CRUD\app\Library\CrudPanel\Traits\Search;
29
use Backpack\CRUD\app\Library\CrudPanel\Traits\Settings;
30
use Backpack\CRUD\app\Library\CrudPanel\Traits\Tabs;
31
use Backpack\CRUD\app\Library\CrudPanel\Traits\Update;
32
use Backpack\CRUD\app\Library\CrudPanel\Traits\Validation;
33
use Backpack\CRUD\app\Library\CrudPanel\Traits\Views;
34
use Exception;
35
use Illuminate\Database\Eloquent\Collection;
36
use Illuminate\Database\Eloquent\Model;
37
use Illuminate\Database\Eloquent\Relations\Relation;
38
use Illuminate\Support\Arr;
39
40
class CrudPanel
41
{
42
    // load all the default CrudPanel features
43
    use Create, Read, Search, Update, Delete, Input, Errors, Reorder, Access, Columns, Fields, Query, Buttons, AutoSet, FakeFields, FakeColumns, AutoFocus, Filters, Tabs, Views, Validation, HeadingsAndTitles, Operations, SaveActions, Settings, Relationships, HasViewNamespaces, MorphRelationships;
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...
Bug introduced by
The trait Backpack\CRUD\app\Library\CrudPanel\Traits\Create requires the property $each which is not provided by Backpack\CRUD\app\Library\CrudPanel\CrudPanel.
Loading history...
introduced by
The trait Backpack\CRUD\app\Library\CrudPanel\Traits\Query requires some properties which are not provided by Backpack\CRUD\app\Library\CrudPanel\CrudPanel: $havings, $groups, $total_rows, $columns, $joins
Loading history...
44
45
    // allow developers to add their own closures to this object
46
    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...
47
48
    // --------------
49
    // CRUD variables
50
    // --------------
51
    // These variables are passed to the CRUD views, inside the $crud variable.
52
    // All variables are public, so they can be modified from your EntityCrudController.
53
    // All functions and methods are also public, so they can be used in your EntityCrudController to modify these variables.
54
55
    public $model = "\App\Models\Entity"; // what's the namespace for your entity's model
56
57
    public $route; // what route have you defined for your entity? used for links.
58
59
    public $entity_name = 'entry'; // what name will show up on the buttons, in singural (ex: Add entity)
60
61
    public $entity_name_plural = 'entries'; // what name will show up on the buttons, in plural (ex: Delete 5 entities)
62
63
    public $entry;
64
65
    protected $request;
66
67
    // The following methods are used in CrudController or your EntityCrudController to manipulate the variables above.
68
69
    public function __construct()
70
    {
71
        $this->setRequest();
72
73
        if ($this->getCurrentOperation()) {
74
            $this->setOperation($this->getCurrentOperation());
75
        }
76
    }
77
78
    /**
79
     * Set the request instance for this CRUD.
80
     *
81
     * @param  \Illuminate\Http\Request  $request
82
     */
83
    public function setRequest($request = null)
84
    {
85
        $this->request = $request ?? \Request::instance();
86
    }
87
88
    /**
89
     * Get the request instance for this CRUD.
90
     *
91
     * @return \Illuminate\Http\Request
92
     */
93
    public function getRequest()
94
    {
95
        return $this->request;
96
    }
97
98
    // ------------------------------------------------------
99
    // BASICS - model, route, entity_name, entity_name_plural
100
    // ------------------------------------------------------
101
102
    /**
103
     * This function binds the CRUD to its corresponding Model (which extends Eloquent).
104
     * All Create-Read-Update-Delete operations are done using that Eloquent Collection.
105
     *
106
     * @param  string  $model_namespace  Full model namespace. Ex: App\Models\Article
107
     *
108
     * @throws \Exception in case the model does not exist
109
     */
110
    public function setModel($model_namespace)
111
    {
112
        if (! class_exists($model_namespace)) {
113
            throw new \Exception('The model does not exist.', 500);
114
        }
115
116
        if (! method_exists($model_namespace, 'hasCrudTrait')) {
117
            throw new \Exception('Please use CrudTrait on the model.', 500);
118
        }
119
120
        $this->model = new $model_namespace();
121
        $this->query = clone $this->totalQuery = $this->model->select('*');
122
        $this->entry = null;
123
    }
124
125
    /**
126
     * Get the corresponding Eloquent Model for the CrudController, as defined with the setModel() function.
127
     *
128
     * @return string|\Illuminate\Database\Eloquent\Model
129
     */
130
    public function getModel()
131
    {
132
        return $this->model;
133
    }
134
135
    /**
136
     * Get the database connection, as specified in the .env file or overwritten by the property on the model.
137
     *
138
     * @return \Illuminate\Database\Schema\Builder
139
     */
140
    private function getSchema()
141
    {
142
        return $this->getModel()->getConnection()->getSchemaBuilder();
143
    }
144
145
    /**
146
     * Check if the database connection driver is using mongodb.
147
     *
148
     * DEPRECATION NOTICE: This method is no longer used and will be removed in future versions of Backpack
149
     *
150
     * @deprecated
151
     *
152
     * @codeCoverageIgnore
153
     *
154
     * @return bool
155
     */
156
    private function driverIsMongoDb()
157
    {
158
        return $this->getSchema()->getConnection()->getConfig()['driver'] === 'mongodb';
159
    }
160
161
    /**
162
     * Check if the database connection is any sql driver.
163
     *
164
     * @return bool
165
     */
166
    private function driverIsSql()
167
    {
168
        $driver = $this->getSchema()->getConnection()->getConfig('driver');
169
170
        return in_array($driver, $this->getSqlDriverList());
171
    }
172
173
    /**
174
     * Get SQL driver list.
175
     *
176
     * @return array
177
     */
178
    public function getSqlDriverList()
179
    {
180
        return ['mysql', 'sqlsrv', 'sqlite', 'pgsql', 'mariadb'];
181
    }
182
183
    /**
184
     * Set the route for this CRUD.
185
     * Ex: admin/article.
186
     *
187
     * @param  string  $route  Route name.
188
     */
189
    public function setRoute($route)
190
    {
191
        $this->route = ltrim($route, '/');
192
    }
193
194
    /**
195
     * Set the route for this CRUD using the route name.
196
     * Ex: admin.article.
197
     *
198
     * @param  string  $route  Route name.
199
     * @param  array  $parameters  Parameters.
200
     *
201
     * @throws \Exception
202
     */
203
    public function setRouteName($route, $parameters = [])
204
    {
205
        $route = ltrim($route, '.');
206
207
        $complete_route = $route.'.index';
208
209
        if (! \Route::has($complete_route)) {
210
            throw new \Exception('There are no routes for this route name.', 404);
211
        }
212
213
        $this->route = route($complete_route, $parameters);
214
    }
215
216
    /**
217
     * Get the current CrudController route.
218
     *
219
     * Can be defined in the CrudController with:
220
     * - $this->crud->setRoute(config('backpack.base.route_prefix').'/article')
221
     * - $this->crud->setRouteName(config('backpack.base.route_prefix').'.article')
222
     * - $this->crud->route = config('backpack.base.route_prefix')."/article"
223
     *
224
     * @return string
225
     */
226
    public function getRoute()
227
    {
228
        return $this->route;
229
    }
230
231
    /**
232
     * Set the entity name in singular and plural.
233
     * Used all over the CRUD interface (header, add button, reorder button, breadcrumbs).
234
     *
235
     * @param  string  $singular  Entity name, in singular. Ex: article
236
     * @param  string  $plural  Entity name, in plural. Ex: articles
237
     */
238
    public function setEntityNameStrings($singular, $plural)
239
    {
240
        $this->entity_name = $singular;
241
        $this->entity_name_plural = $plural;
242
    }
243
244
    // -----------------------------------------------
245
    // ACTIONS - the current operation being processed
246
    // -----------------------------------------------
247
248
    /**
249
     * Get the action being performed by the controller,
250
     * including middleware names, route name, method name,
251
     * namespace, prefix, etc.
252
     *
253
     * @return string The EntityCrudController route action array.
254
     */
255
    public function getAction()
256
    {
257
        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...
258
    }
259
260
    /**
261
     * Get the full name of the controller method
262
     * currently being called (including namespace).
263
     *
264
     * @return string The EntityCrudController full method name with namespace.
265
     */
266
    public function getActionName()
267
    {
268
        return $this->getRequest()->route()->getActionName();
269
    }
270
271
    /**
272
     * Get the name of the controller method
273
     * currently being called.
274
     *
275
     * @return string The EntityCrudController method name.
276
     */
277
    public function getActionMethod()
278
    {
279
        return $this->getRequest()->route()->getActionMethod();
280
    }
281
282
    /**
283
     * Check if the controller method being called
284
     * matches a given string.
285
     *
286
     * @param  string  $methodName  Name of the method (ex: index, create, update)
287
     * @return bool Whether the condition is met or not.
288
     */
289
    public function actionIs($methodName)
290
    {
291
        return $methodName === $this->getActionMethod();
292
    }
293
294
    // ----------------------------------
295
    // Miscellaneous functions or methods
296
    // ----------------------------------
297
298
    /**
299
     * Return the first element in an array that has the given 'type' attribute.
300
     *
301
     * @param  string  $type
302
     * @param  array  $array
303
     * @return array
304
     */
305
    public function getFirstOfItsTypeInArray($type, $array)
306
    {
307
        return Arr::first($array, function ($item) use ($type) {
308
            return $item['type'] == $type;
309
        });
310
    }
311
312
    /**
313
     * TONE FUNCTIONS - UNDOCUMENTED, UNTESTED, SOME MAY BE USED IN THIS FILE.
314
     *
315
     * TODO:
316
     * - figure out if they are really needed
317
     * - comments inside the function to explain how they work
318
     * - write docblock for them
319
     * - place in the correct section above (CREATE, READ, UPDATE, DELETE, ACCESS, MANIPULATION)
320
     *
321
     * @deprecated
322
     *
323
     * @codeCoverageIgnore
324
     */
325
    public function sync($type, $fields, $attributes)
326
    {
327
        if (! empty($this->{$type})) {
328
            $this->{$type} = array_map(function ($field) use ($fields, $attributes) {
329
                if (in_array($field['name'], (array) $fields)) {
330
                    $field = array_merge($field, $attributes);
331
                }
332
333
                return $field;
334
            }, $this->{$type});
335
        }
336
    }
337
338
    /**
339
     * Get the Eloquent Model name from the given relation definition string.
340
     *
341
     * @example For a given string 'company' and a relation between App/Models/User and App/Models/Company, defined by a
342
     *          company() method on the user model, the 'App/Models/Company' string will be returned.
343
     * @example For a given string 'company.address' and a relation between App/Models/User, App/Models/Company and
344
     *          App/Models/Address defined by a company() method on the user model and an address() method on the
345
     *          company model, the 'App/Models/Address' string will be returned.
346
     *
347
     * @param  string  $relationString  Relation string. A dot notation can be used to chain multiple relations.
348
     * @param  int  $length  Optionally specify the number of relations to omit from the start of the relation string. If
349
     *                       the provided length is negative, then that many relations will be omitted from the end of the relation
350
     *                       string.
351
     * @param  \Illuminate\Database\Eloquent\Model  $model  Optionally specify a different model than the one in the crud object.
352
     * @return string Relation model name.
353
     */
354
    public function getRelationModel($relationString, $length = null, $model = null)
355
    {
356
        $relationArray = explode('.', $relationString);
357
358
        if (! isset($length)) {
359
            $length = count($relationArray);
360
        }
361
362
        if (! isset($model)) {
363
            $model = $this->model;
364
        }
365
366
        $result = array_reduce(array_splice($relationArray, 0, $length), function ($obj, $method) {
367
            try {
368
                $result = $obj->$method();
369
370
                return $result->getRelated();
371
            } catch (Exception $e) {
372
                return $obj;
373
            }
374
        }, $model);
375
376
        return get_class($result);
377
    }
378
379
    /**
380
     * Get the given attribute from a model or models resulting from the specified relation string (eg: the list of streets from
381
     * the many addresses of the company of a given user).
382
     *
383
     * @param  \Illuminate\Database\Eloquent\Model  $model  Model (eg: user).
384
     * @param  string  $relationString  Model relation. Can be a string representing the name of a relation method in the given
385
     *                                  Model or one from a different Model through multiple relations. A dot notation can be used to specify
386
     *                                  multiple relations (eg: user.company.address).
387
     * @param  string  $attribute  The attribute from the relation model (eg: the street attribute from the address model).
388
     * @return array An array containing a list of attributes from the resulting model.
389
     */
390
    public function getRelatedEntriesAttributes($model, $relationString, $attribute)
391
    {
392
        $endModels = $this->getRelatedEntries($model, $relationString);
393
        $attributes = [];
394
        foreach ($endModels as $model => $entries) {
0 ignored issues
show
introduced by
$model is overwriting one of the parameters of this function.
Loading history...
395
            $model_instance = new $model();
396
            $modelKey = $model_instance->getKeyName();
397
398
            if (is_array($entries)) {
399
                //if attribute does not exist in main array we have more than one entry OR the attribute
400
                //is an acessor that is not in $appends property of model.
401
                if (! isset($entries[$attribute])) {
402
                    //we first check if we don't have the attribute because it's an acessor that is not in appends.
403
                    if ($model_instance->hasGetMutator($attribute) && isset($entries[$modelKey])) {
404
                        $entry_in_database = $model_instance->find($entries[$modelKey]);
405
                        $attributes[$entry_in_database->{$modelKey}] = $this->parseTranslatableAttributes($model_instance, $attribute, $entry_in_database->{$attribute});
406
                    } else {
407
                        //we have multiple entries
408
                        //for each entry we check if $attribute exists in array or try to check if it's an acessor.
409
                        foreach ($entries as $entry) {
410
                            if (isset($entry[$attribute])) {
411
                                $attributes[$entry[$modelKey]] = $this->parseTranslatableAttributes($model_instance, $attribute, $entry[$attribute]);
412
                            } else {
413
                                if ($model_instance->hasGetMutator($attribute)) {
414
                                    $entry_in_database = $model_instance->find($entry[$modelKey]);
415
                                    $attributes[$entry_in_database->{$modelKey}] = $this->parseTranslatableAttributes($model_instance, $attribute, $entry_in_database->{$attribute});
416
                                }
417
                            }
418
                        }
419
                    }
420
                } else {
421
                    //if we have the attribute we just return it, does not matter if it is direct attribute or an acessor added in $appends.
422
                    $attributes[$entries[$modelKey]] = $this->parseTranslatableAttributes($model_instance, $attribute, $entries[$attribute]);
423
                }
424
            }
425
        }
426
427
        return $attributes;
428
    }
429
430
    /**
431
     * Parse translatable attributes from a model or models resulting from the specified relation string.
432
     *
433
     * @param  \Illuminate\Database\Eloquent\Model  $model  Model (eg: user).
434
     * @param  string  $attribute  The attribute from the relation model (eg: the street attribute from the address model).
435
     * @param  string  $value  Attribute value translatable or not
436
     * @return string A string containing the translated attributed based on app()->getLocale()
437
     */
438
    public function parseTranslatableAttributes($model, $attribute, $value)
439
    {
440
        if (! method_exists($model, 'isTranslatableAttribute')) {
441
            return $value;
442
        }
443
444
        if (! $model->isTranslatableAttribute($attribute)) {
445
            return $value;
446
        }
447
448
        if (! is_array($value)) {
0 ignored issues
show
introduced by
The condition is_array($value) is always false.
Loading history...
449
            $decodedAttribute = json_decode($value, true);
450
        } else {
451
            $decodedAttribute = $value;
452
        }
453
454
        if (is_array($decodedAttribute) && ! empty($decodedAttribute)) {
455
            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

455
            if (isset($decodedAttribute[app()->/** @scrutinizer ignore-call */ getLocale()])) {
Loading history...
456
                return $decodedAttribute[app()->getLocale()];
457
            } else {
458
                return Arr::first($decodedAttribute);
459
            }
460
        }
461
462
        return $value;
463
    }
464
465
    /**
466
     * Traverse the tree of relations for the given model, defined by the given relation string, and return the ending
467
     * associated model instance or instances.
468
     *
469
     * @param  \Illuminate\Database\Eloquent\Model  $model  The CRUD model.
470
     * @param  string  $relationString  Relation string. A dot notation can be used to chain multiple relations.
471
     * @return array An array of the associated model instances defined by the relation string.
472
     */
473
    private function getRelatedEntries($model, $relationString)
474
    {
475
        $relationArray = explode('.', $this->getOnlyRelationEntity(['entity' => $relationString]));
476
        $firstRelationName = Arr::first($relationArray);
477
        $relation = $model->{$firstRelationName};
478
479
        $results = [];
480
        if (! is_null($relation)) {
481
            if ($relation instanceof Collection) {
482
                $currentResults = $relation->all();
483
            } elseif (is_array($relation)) {
484
                $currentResults = $relation;
485
            } elseif ($relation instanceof Model) {
486
                $currentResults = [$relation];
487
            } else {
488
                $currentResults = [];
489
            }
490
491
            array_shift($relationArray);
492
493
            if (! empty($relationArray)) {
494
                foreach ($currentResults as $currentResult) {
495
                    $results = array_merge_recursive($results, $this->getRelatedEntries($currentResult, implode('.', $relationArray)));
496
                }
497
            } else {
498
                $relatedClass = get_class($model->{$firstRelationName}()->getRelated());
499
                $results[$relatedClass] = $currentResults;
500
            }
501
        }
502
503
        return $results;
504
    }
505
}
506