Completed
Push — master ( eecd8e...8b8777 )
by Adrian
05:13
created

Model::getNestedRelation()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.0987

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 2
dl 0
loc 16
ccs 7
cts 9
cp 0.7778
crap 3.0987
rs 9.4285
c 0
b 0
f 0
1
<?php
2
namespace Anavel\Crud\Abstractor\Eloquent;
3
4
use Anavel\Crud\Abstractor\Eloquent\Traits\HandleFiles;
5
use Anavel\Crud\Abstractor\Eloquent\Traits\ModelFields;
6
use Anavel\Crud\Contracts\Abstractor\Field as FieldContract;
7
use Anavel\Crud\Contracts\Abstractor\Model as ModelAbstractorContract;
8
use Anavel\Crud\Abstractor\ConfigurationReader;
9
use Anavel\Crud\Contracts\Abstractor\Relation;
10
use Anavel\Crud\Contracts\Abstractor\RelationFactory as RelationFactoryContract;
11
use Anavel\Crud\Contracts\Abstractor\FieldFactory as FieldFactoryContract;
12
use ANavallaSuiza\Laravel\Database\Contracts\Dbal\AbstractionLayer;
13
use FormManager\ElementInterface;
14
use Illuminate\Database\Eloquent\Model as LaravelModel;
15
use App;
16
use Anavel\Crud\Contracts\Form\Generator as FormGenerator;
17
use Anavel\Crud\Abstractor\Exceptions\AbstractorException;
18
use Illuminate\Http\Request;
19
use Illuminate\Support\Collection;
20
use League\Flysystem\Adapter\Local;
21
use League\Flysystem\Filesystem;
22
23
class Model implements ModelAbstractorContract
24
{
25
    use ConfigurationReader;
26
    use ModelFields;
27
    use HandleFiles;
28
29
    protected $dbal;
30
    protected $relationFactory;
31
    protected $fieldFactory;
32
    protected $generator;
33
34
    protected $model;
35
    protected $config;
36
37
    protected $slug;
38
    protected $name;
39
    protected $instance;
40
41 17
    public function __construct(
42
        $config,
43
        AbstractionLayer $dbal,
44
        RelationFactoryContract $relationFactory,
45
        FieldFactoryContract $fieldFactory,
46
        FormGenerator $generator
47
    ) {
48 17
        if (is_array($config)) {
49 17
            $this->model = $config['model'];
50 17
            $this->config = $config;
51 17
        } else {
52
            $this->model = $config;
53
            $this->config = [];
54
        }
55
56 17
        $this->dbal = $dbal;
57 17
        $this->relationFactory = $relationFactory;
58 17
        $this->fieldFactory = $fieldFactory;
59 17
        $this->generator = $generator;
60 17
    }
61
62 3
    public function setSlug($slug)
63
    {
64 3
        $this->slug = $slug;
65
66 3
        return $this;
67
    }
68
69 2
    public function setName($name)
70
    {
71 2
        $this->name = $name;
72
73 2
        return $this;
74
    }
75
76 5
    public function setInstance($instance)
77
    {
78 5
        $this->instance = $instance;
79
80 5
        return $this;
81
    }
82
83
    public function getSlug()
84
    {
85
        return $this->slug;
86
    }
87
88
    public function getName()
89
    {
90
        return transcrud($this->name);
91
    }
92
93 1
    public function getModel()
94
    {
95 1
        return $this->model;
96
    }
97
98
    public function getInstance()
99
    {
100
        return $this->instance;
101
    }
102
103
    /**
104
     * @return array
105
     */
106
    public function getConfig()
107
    {
108
        return $this->config;
109
    }
110
111
    public function isSoftDeletes()
112
    {
113
        return $this->getConfigValue('soft_deletes') ? true : false;
114
    }
115
116 8
    public function getColumns($action, $withForeignKeys = false)
117
    {
118 8
        $tableColumns = $this->dbal->getTableColumns();
119
120 8
        $filteredColumns = [];
121 8
        foreach ($tableColumns as $name => $column) {
122 8
            $filteredColumns[str_replace('`', '', $name)] = $column;
123 8
        }
124 8
        $tableColumns = $filteredColumns;
125
126
127 8
        $foreignKeysName = [];
128 8
        if ($withForeignKeys === false) {
129 8
            $foreignKeys = $this->dbal->getTableForeignKeys();
130
131 8
            foreach ($foreignKeys as $foreignKey) {
132
                foreach ($foreignKey->getColumns() as $columnName) {
133
                    $foreignKeysName[] = $columnName;
134
                }
135 8
            }
136 8
        }
137
138 8
        $customDisplayedColumns = $this->getConfigValue($action, 'display');
139 8
        $customHiddenColumns = $this->getConfigValue($action, 'hide') ? : [];
140
141 8
        $columns = array();
142
        if (! empty($customDisplayedColumns) && is_array($customDisplayedColumns)) {
143 8
            foreach ($customDisplayedColumns as $customColumn) {
144 8
                if (strpos($customColumn, '.')) {
145 8
                    $customColumnRelation = explode('.', $customColumn);
146 8
147
                    $customColumnRelationFieldName = array_pop($customColumnRelation);
148
149
                    $modelAbstractor = $this;
150
                    foreach ($customColumnRelation as $relationName) {
151
                        $nestedRelation = $this->getNestedRelation($modelAbstractor, $relationName);
152
                        $modelAbstractor = $nestedRelation->getModelAbstractor();
153
                    }
154
155
                    $relationColumns = $nestedRelation->getModelAbstractor()->getColumns($action);
0 ignored issues
show
Bug introduced by
The variable $nestedRelation does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
156
157
                    if (! array_key_exists($customColumnRelationFieldName, $relationColumns)) {
158
                        throw new AbstractorException("Column " . $customColumnRelationFieldName . " does not exist on relation " . implode('.', $customColumnRelation) . " of model " . $this->getModel());
159
                    }
160
161
                    $columns[$customColumn] = $relationColumns[$customColumnRelationFieldName];
162
                } else {
163 8
                    if (! array_key_exists($customColumn, $tableColumns)) {
164
                        throw new AbstractorException("Column " . $customColumn . " does not exist on " . $this->getModel());
165
                    }
166
167 8
                    $columns[$customColumn] = $tableColumns[$customColumn];
168
                }
169 8
            }
170 8
        } else {
171
            foreach ($tableColumns as $name => $column) {
172
                if (in_array($name, $customHiddenColumns)) {
173
                    continue;
174
                }
175
176
                if (in_array($name, $foreignKeysName)) {
177
                    continue;
178
                }
179
180
                $columns[$name] = $column;
181
            }
182
        }
183
184 8
        return $columns;
185
    }
186
187
    protected function getNestedRelation(Model $modelAbstractor, $relationName)
188
    {
189
        $relations = $modelAbstractor->getRelations();
190 10
191
        if (! $relations->has($relationName)) {
192 10
            throw new AbstractorException("Relation " . $relationName . " not configured on " . $modelAbstractor->getModel());
193
        }
194 10
195
        $relation = $relations->get($relationName);
196 10
197 10
        if ($relation instanceof Relation) {
198 10
            return $relation;
199
        } else {
200
            return $relation['relation'];
201
        }
202 10
    }
203 10
204 10
    /**
205
     * @return \Illuminate\Support\Collection
206
     */
207 10
    public function getRelations()
208
    {
209 10
        $configRelations = $this->getConfigValue('relations');
210
211
        $relations = collect();
212 10
213 10
        if (! empty($configRelations)) {
214 10
            foreach ($configRelations as $relationName => $configRelation) {
215
                if (is_int($relationName)) {
216 10
                    $relationName = $configRelation;
217
                }
218
219 10
                $config = [];
220 1
                if ($configRelation !== $relationName) {
221 1
                    if (! is_array($configRelation)) {
222 1
                        $config['type'] = $configRelation;
223 1
                    } else {
224 1
                        $config = $configRelation;
225 9
                    }
226
                }
227 10
228 10
                /** @var Relation $relation */
229
                $relation = $this->relationFactory->setModel($this->instance)
230 10
                    ->setConfig($config)
231
                    ->get($relationName);
232
233
                $secondaryRelations = $relation->getSecondaryRelations();
234
235
236
                if (! $secondaryRelations->isEmpty()) {
237
                    $relations->put(
238 2
                        $relationName,
239
                        collect(['relation' => $relation, 'secondaryRelations' => $secondaryRelations])
240 2
                    );
241
                } else {
242 2
                    $relations->put($relationName, $relation);
243
                }
244 2
            }
245 2
        }
246 2
247 2
        return $relations;
248 2
    }
249 2
250
    /**
251
     * @param string|null $arrayKey
252 2
     * @return array
253 2
     * @throws AbstractorException
254 2
     */
255 2 View Code Duplication
    public function getListFields($arrayKey = 'main')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
256
    {
257 2
        $columns = $this->getColumns('list');
258
259 2
        $fieldsPresentation = $this->getConfigValue('fields_presentation') ? : [];
260 2
261 2
        $fields = array();
262 2
        foreach ($columns as $name => $column) {
263 2
            $presentation = null;
264
            if (array_key_exists($name, $fieldsPresentation)) {
265 2
                $presentation = $fieldsPresentation[$name];
266
            }
267
268
            $config = [
269
                'name'         => $name,
270
                'presentation' => $presentation,
271
                'form_type'    => null,
272
                'validation'   => null,
273 2
                'functions'    => null
274
            ];
275 2
276
            $fields[$arrayKey][] = $this->fieldFactory
277 2
                ->setColumn($column)
278
                ->setConfig($config)
279 2
                ->get();
280 2
        }
281 2
282 2
        return $fields;
283 2
    }
284 2
285
    /**
286
     * @param string|null $arrayKey
287 2
     * @return array
288 2
     * @throws AbstractorException
289 2
     */
290 2 View Code Duplication
    public function getDetailFields($arrayKey = 'main')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
291
    {
292 2
        $columns = $this->getColumns('detail');
293
294 2
        $fieldsPresentation = $this->getConfigValue('fields_presentation') ? : [];
295 2
296 2
        $fields = array();
297 2
        foreach ($columns as $name => $column) {
298 2
            $presentation = null;
299
            if (array_key_exists($name, $fieldsPresentation)) {
300 2
                $presentation = $fieldsPresentation[$name];
301
            }
302
303
            $config = [
304
                'name'         => $name,
305
                'presentation' => $presentation,
306
                'form_type'    => null,
307
                'validation'   => null,
308
                'functions'    => null
309 4
            ];
310
311 4
            $fields[$arrayKey][] = $this->fieldFactory
312
                ->setColumn($column)
313 4
                ->setConfig($config)
314
                ->get();
315 4
        }
316 4
317 4
        return $fields;
318 4
    }
319 4
320
    /**
321
     * @param bool|null $withForeignKeys
322
     * @param string|null $arrayKey
323
     * @return array
324 4
     * @throws AbstractorException
325 4
     */
326 4
    public function getEditFields($withForeignKeys = false, $arrayKey = 'main')
327 4
    {
328
        $columns = $this->getColumns('edit', $withForeignKeys);
0 ignored issues
show
Bug introduced by
It seems like $withForeignKeys defined by parameter $withForeignKeys on line 326 can also be of type null; however, Anavel\Crud\Abstractor\E...ent\Model::getColumns() does only seem to accept boolean, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
329 4
330
        $this->readConfig('edit');
331 4
332
        $fields = array();
333 4
        foreach ($columns as $name => $column) {
334 4
            if (! in_array($name, $this->getReadOnlyColumns())) {
335 4
                $presentation = null;
336 4
                if (array_key_exists($name, $this->fieldsPresentation)) {
337
                    $presentation = $this->fieldsPresentation[$name];
338 4
                }
339
340
                $config = [
341
                    'name'         => $name,
342 4
                    'presentation' => $presentation,
343
                    'form_type'    => null,
344 4
                    'validation'   => null,
345 4
                    'functions'    => null
346 4
                ];
347 4
348 4
                $config = $this->setConfig($config, $name);
349 4
350 4
                $field = $this->fieldFactory
351 4
                    ->setColumn($column)
352
                    ->setConfig($config)
353 4
                    ->get();
354 4
355 4
                if (! empty($this->instance) && ! empty($this->instance->getAttribute($name))) {
356 4
                    $field->setValue($this->instance->getAttribute($name));
357 4
                }
358 4
359
                $fields[$arrayKey][$name] = $field;
360 4
361 View Code Duplication
                if (! empty($config['form_type']) && $config['form_type'] === 'file') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
362
                    $field = $this->fieldFactory
363 4
                        ->setColumn($column)
364
                        ->setConfig([
365 4
                            'name'         => $name . '__delete',
366
                            'presentation' => null,
367 4
                            'form_type'    => 'checkbox',
368
                            'no_validate'  => true,
369 4
                            'functions'    => null
370
                        ])
371
                        ->get();
372
                    $fields[$arrayKey][$name . '__delete'] = $field;
373
                }
374
            }
375
        }
376 2
377
        return $fields;
378 2
    }
379 2
380
    protected function getReadOnlyColumns()
381 2
    {
382
        $columns = [LaravelModel::CREATED_AT, LaravelModel::UPDATED_AT];
383
384
        $columns[] = $this->dbal->getModel()->getKeyName();
385
386
        return $columns;
387
    }
388 1
389
    /**
390
     * @param string $action
391 1
     * @return ElementInterface
392 1
     */
393
    public function getForm($action)
394
    {
395 1
        $this->generator->setModelFields($this->getEditFields());
396
        $this->generator->setRelatedModelFields($this->getRelations());
397
398
        return $this->generator->getForm($action);
399 1
    }
400 1
401
    /**
402
     * @param array $requestForm
0 ignored issues
show
Documentation introduced by
There is no parameter named $requestForm. Did you maybe mean $request?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
403
     * @return mixed
404 1
     */
405 1
    public function persist(Request $request)
406 1
    {
407
        /** @var \ANavallaSuiza\Laravel\Database\Contracts\Manager\ModelManager $modelManager */
408 1
        $modelManager = App::make('ANavallaSuiza\Laravel\Database\Contracts\Manager\ModelManager');
409
        if (! empty($this->instance)) {
410
            $item = $this->instance;
411
        } else {
412 1
            $item = $modelManager->getModelInstance($this->getModel());
413 1
        }
414
415 1
416
        $fields = $this->getEditFields(true);
417
        if (empty($fields['main']) && $this->getRelations()->isEmpty()) {
418
            return;
419
        }
420
421
        if (! empty($fields['main'])) {
422
            $skip = null;
423
            foreach ($fields['main'] as $key => $field) {
424
                /** @var FieldContract $field */
425
                if ($skip === $key) {
426
                    $skip = null;
427 1
                    continue;
428
                }
429
                $fieldName = $field->getName();
430
                $requestValue = $request->input("main.{$fieldName}");
431
432
                if (get_class($field->getFormField()) === \FormManager\Fields\Checkbox::class) {
433
                    if (empty($requestValue)) {
434
                        // Unchecked checkboxes are not sent, so we force setting them to false
435
                        $item->setAttribute(
436
                            $fieldName,
437
                            $field->applyFunctions(null)
438 1
                        );
439
                    } else {
440
                        $requestValue = true;
441
                    }
442 1
                }
443 1
444 1
                if (get_class($field->getFormField()) === \FormManager\Fields\File::class) {
445 1
                    $handleResult = $this->handleField($request, $item, $fields['main'], 'main', $fieldName);
446 1
                    if (! empty($handleResult['skip'])) {
447 1
                        $skip = $handleResult['skip'];
448 1
                    }
449 1
                    if (! empty($handleResult['requestValue'])) {
450
                        $requestValue = $handleResult['requestValue'];
451 1
                    }
452
                }
453 1
454
455
                if (! $field->saveIfEmpty() && empty($requestValue)) {
456 1
                    continue;
457 1
                }
458 1
459
                if (! empty($requestValue) || (empty($requestValue) && ! empty($item->getAttribute($fieldName)))) {
460
                    $item->setAttribute(
461
                        $fieldName,
462 1
                        $field->applyFunctions($requestValue)
463
                    );
464 1
                }
465 1
            }
466
        }
467 1
468
        $item->save();
469
470
        $this->setInstance($item);
471
472
473 1
        if (! empty($relations = $this->getRelations())) {
474
            foreach ($relations as $relationKey => $relation) {
475 1
                if ($relation instanceof Collection) {
476
                    $input = $request->input($relationKey);
477
                    $relation->get('relation')->persist($input, $request);
478
                } else {
479
                    $relation->persist($request->input($relationKey), $request);
480
                }
481
            }
482
        }
483
484
        return $item;
485
    }
486
487
    /**
488
     * @return array
489
     */
490
    public function getValidationRules()
491
    {
492
        return $this->generator->getValidationRules();
493
    }
494
495
    public function getFieldValue($item, $fieldName)
496
    {
497
        $value = null;
0 ignored issues
show
Unused Code introduced by
$value is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
498
499
        if (strpos($fieldName, '.')) {
500
            $customColumnRelation = explode('.', $fieldName);
501
502
            $customColumnRelationFieldName = array_pop($customColumnRelation);
503
504
            $entity = $item;
505
            $relation = null;
506
            $relationName = '';
507
            foreach ($customColumnRelation as $relationName) {
508
                $relation = $entity->{$relationName};
509
                if ($relation instanceof Collection) {
510
                    $entity = $relation->first();
511
                } else {
512
                    $entity = $relation;
513
                }
514
            }
515
516
            $lastRelationName = $relationName;
517
518
            array_pop($customColumnRelation);
519
520
            $prevModelAbtractor = $this;
521
            foreach ($customColumnRelation as $relationName) {
522
                $nestedRelation = $this->getNestedRelation($prevModelAbtractor, $relationName);
523
                $prevModelAbtractor = $nestedRelation->getModelAbstractor();
524
            }
525
526
            if (empty($relation)) {
527
                return null;
528
            }
529
530
            if ($relation instanceof Collection) {
531
                $relations = $prevModelAbtractor->getRelations();
532
533
                $relationAbstractor = $relations->get($lastRelationName);
534
535
                if ($relationAbstractor instanceof \Anavel\Crud\Abstractor\Eloquent\Relation\Translation) {
536
                    $value = $entity->getAttribute($customColumnRelationFieldName);
537
                } else {
538
                    $value = $relation->implode($customColumnRelationFieldName, ', ');
539
                }
540
            } else {
541
                $value = $relation->getAttribute($customColumnRelationFieldName);
542
            }
543
        } else {
544
            $value = $item->getAttribute($fieldName);
545
        }
546
547
        return $value;
548
    }
549
}
550