Relations::syncRelations()   C
last analyzed

Complexity

Conditions 14
Paths 14

Size

Total Lines 70
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 210

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 14
eloc 52
c 3
b 0
f 0
nc 14
nop 2
dl 0
loc 70
rs 6.2666
ccs 0
cts 45
cp 0
crap 210

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Yaro\Jarboe\Table\Fields\Traits;
4
5
use Illuminate\Database\Eloquent\Model;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Yaro\Jarboe\Table\Fields\Traits\Model. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
6
use Illuminate\Database\Eloquent\Relations\BelongsTo;
7
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
8
use Illuminate\Database\Eloquent\Relations\HasMany;
9
use Illuminate\Database\Eloquent\Relations\HasOne;
10
use Illuminate\Database\Eloquent\Relations\MorphMany;
11
use Illuminate\Database\Eloquent\Relations\MorphTo;
12
use Illuminate\Database\Eloquent\Relations\MorphToMany;
13
use Illuminate\Support\Arr;
14
15
trait Relations
16
{
17
    use RelationSearchUrl;
18
19
    protected $perPage = 20;
20
21
    protected $options = [];
22
    protected $relations = [];
23
    protected $additionalCondition;
24
    protected $searchCallback;
25
26
    public function options(array $options)
27
    {
28
        $this->options = $options;
29
30
        return $this;
31
    }
32
33
    public function getRelationMethod($relationIndex = 0)
34
    {
35
        return Arr::get($this->relations, $relationIndex .'.method');
36
    }
37
38
    public function getRelationTitleField($relationIndex = 0)
39
    {
40
        return Arr::get($this->relations, $relationIndex .'.title');
41
    }
42
43
    public function relation(string $method, string $titleField, string $groupTitle = '')
44
    {
45
        $this->relations[] = [
46
            'method' => $method,
47
            'title'  => $titleField,
48
            'group'  => $groupTitle,
49
        ];
50
51
        return $this;
52
    }
53
54
    public function isGroupedRelation()
55
    {
56
        return count($this->relations) > 1;
57
    }
58
59
    public function getRelations()
60
    {
61
        return $this->relations;
62
    }
63
64
    public function getOptions(int $page = null, int $perPage = null, $query = null, &$total = 0, $relationIndex = 0, \Closure $clause = null)
65
    {
66
        $options = $this->options;
67
        if ($this->isRelationField() && !$options) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $options of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
68
            $options = [];
69
            $model = $this->getModel();
70
71
            $related = (new $model)->{$this->getRelationMethod($relationIndex)}()->getRelated();
72
            $relatedQuery = $related->query();
73
            if (!is_null($query)) {
74
                $callback = $this->searchCallback;
75
                if ($callback) {
76
                    $relatedQuery = $callback($relatedQuery, $this->getRelationTitleField($relationIndex), $query);
77
                } else {
78
                    $relatedQuery->where($this->getRelationTitleField($relationIndex), $query);
79
                }
80
            }
81
            if (!is_null($clause)) {
82
                $clause($relatedQuery, $related);
83
            }
84
85
            if (!is_null($page) && !is_null($perPage)) {
86
                $total += (clone $relatedQuery)->count();
87
88
                $offset = ($page - 1) * $perPage;
89
                $relatedQuery->limit($perPage)->offset($offset);
90
            }
91
92
            $callback = $this->additionalCondition;
93
            if ($callback) {
94
                $callback($relatedQuery, $related);
95
            }
96
97
            $relations = $relatedQuery->get();
98
            foreach ($relations as $relation) {
99
                $options[$relation->getKey()] = $relation->{$this->getRelationTitleField($relationIndex)};
100
            }
101
        }
102
103
        return $options;
104
    }
105
106
    public function getGroupedOptions(int $page = null, int $perPage = null, $query = null, &$total = 0)
107
    {
108
        $options = [];
109
        foreach ($this->getRelations() as $index => $relation) {
110
            $options[$relation['group']] = $this->getOptions($page, $perPage, $query, $total, $index);
111
        }
112
113
        return $options;
114
    }
115
116
    protected function getRelatedList($relationClass, $value, $ids): array
117
    {
118
        if (!$this->isMultiple()) {
119
            return array_filter([
120
                $relationClass->find($value)
121
            ]);
122
        }
123
124
        if (!$ids) {
125
            return [];
126
        }
127
128
        $relatedList = [];
129
        $relatedModels = $relationClass->whereIn($relationClass->getKeyName(), $ids)->get();
130
        foreach ($relatedModels as $relatedModel) {
131
            $relatedList[] = $relatedModel;
132
        }
133
134
        return array_filter($relatedList);
135
    }
136
137
    /**
138
     * @param Model $model
139
     * @param $value
140
     */
141
    public function syncRelations($model, $value)
142
    {
143
        foreach ($this->relations as $index => $relation) {
144
            $relationQuery = $model->{$this->getRelationMethod($index)}()->getRelated();
145
            $relationClass = get_class($relationQuery);
146
            $relationClass = new $relationClass;
147
            $ids = $value ?: [];
148
            
149
            switch (get_class($model->{$this->getRelationMethod($index)}())) {
150
                case HasMany::class:
151
                    $model->{$this->getRelationMethod($index)}()->update([
152
                        $model->{$this->getRelationMethod($index)}()->getForeignKeyName() => null,
153
                    ]);
154
155
                    $relatedList = $this->getRelatedList($relationClass, $value, $ids);
156
157
                    $model->{$this->getRelationMethod($index)}()->saveMany($relatedList);
158
                    break;
159
                case MorphMany::class:
160
                    $model->{$this->getRelationMethod($index)}()->update([
161
                        $model->{$this->getRelationMethod($index)}()->getMorphType() => null,
162
                        $model->{$this->getRelationMethod($index)}()->getForeignKeyName() => null,
163
                    ]);
164
165
                    $relatedList = $this->getRelatedList($relationClass, $value, $ids);
166
                    if ($relatedList) {
167
                        $model->{$this->getRelationMethod($index)}()->saveMany($relatedList);
168
                    }
169
                    break;
170
                case MorphToMany::class:
171
                    if ($relation['group']) { // is morphedByMany
172
                        $ids = $this->filterValuesForMorphToManyRelation($ids, crc32($relation['group']));
0 ignored issues
show
Bug introduced by
It seems like filterValuesForMorphToManyRelation() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

172
                        /** @scrutinizer ignore-call */ 
173
                        $ids = $this->filterValuesForMorphToManyRelation($ids, crc32($relation['group']));
Loading history...
173
                        $model->{$this->getRelationMethod($index)}()->sync($ids);
174
                    } else { // is morphToMany
175
                        $model->{$this->getRelationMethod($index)}()->update([
176
                            $model->{$this->getRelationMethod($index)}()->getMorphType() => null,
177
                            $model->{$this->getRelationMethod($index)}()->getForeignPivotKeyName() => null,
178
                            $model->{$this->getRelationMethod($index)}()->getRelatedPivotKeyName() => null,
179
                        ]);
180
181
                        $relatedList = $this->getRelatedList($relationClass, $value, $ids);
182
                        $model->{$this->getRelationMethod($index)}()->saveMany($relatedList);
183
                    }
184
                    break;
185
                case BelongsTo::class:
186
                case MorphTo::class:
187
                    $model->{$this->getRelationMethod($index)}()->dissociate();
188
189
                    $relatedList = $this->getRelatedList($relationClass, $value, $ids);
190
                    foreach ($relatedList as $relatedModel) {
191
                        $model->{$this->getRelationMethod($index)}()->associate($relatedModel);
192
                    }
193
                    $model->save();
194
                    break;
195
                case HasOne::class:
196
                    $model->{$this->getRelationMethod($index)}()->update([
197
                        $model->{$this->getRelationMethod($index)}()->getForeignKeyName() => null,
198
                    ]);
199
200
                    $relatedList = $this->getRelatedList($relationClass, $value, $ids);
201
                    foreach ($relatedList as $relatedModel) {
202
                        $model->{$this->getRelationMethod($index)}()->save($relatedModel);
203
                    }
204
                    break;
205
                case BelongsToMany::class:
206
                    $relatedList = $this->getRelatedList($relationClass, $value, $ids);
207
                    $model->{$this->getRelationMethod($index)}()->sync(
208
                        collect($relatedList)->pluck($relationClass->getKeyName())->toArray()
209
                    );
210
                    break;
211
            }
212
        }
213
    }
214
215
    /**
216
     * Get selected options array.
217
     *
218
     * @param null $model
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $model is correct as it would always require null to be passed?
Loading history...
219
     * @param int $index
220
     * @return array
221
     */
222
    public function getSelectedOptions($model = null, $index = 0)
223
    {
224
        $options = [];
225
        if (is_null($model)) {
0 ignored issues
show
introduced by
The condition is_null($model) is always true.
Loading history...
226
            return $options;
227
        }
228
229
        $relations = $model->{$this->getRelationMethod($index)};
230
        if (is_null($relations)) {
231
            return $options;
232
        }
233
234
        if ($this->isMultiple()) {
235
            foreach ($relations as $relation) {
236
                $options[$relation->getKey()] = $relation->{$this->getRelationTitleField($index)};
237
            }
238
        } else {
239
            $options[$relations->getKey()] = $relations->{$this->getRelationTitleField($index)};
240
        }
241
242
        return $options;
243
    }
244
245
    public function getSelectedGroupedOptions($model = null)
246
    {
247
        $options = [];
248
        foreach ($this->relations as $index => $relation) {
249
            $options[$relation['group']] = $this->getSelectedOptions($model, $index);
250
        }
251
252
        return $options;
253
    }
254
255
    public function isRelationField()
256
    {
257
        return (bool) $this->relations;
258
    }
259
260
    public function addCondition(\Closure $callback)
261
    {
262
        $this->additionalCondition = $callback;
263
264
        return $this;
265
    }
266
267
    public function relationSearch(\Closure $callback)
268
    {
269
        $this->searchCallback = $callback;
270
271
        return $this;
272
    }
273
274
    abstract public function getModel();
275
    abstract public function isMultiple();
276
}
277