Completed
Push — master ( 170670...f094e0 )
by Yaro
04:36
created

Relations::relationSearch()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
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.

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()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
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)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
65
    {
66
        $options = $this->options;
67
        if ($this->isRelationField() && !$options) {
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 View Code Duplication
                case HasMany::class:
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...
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
The method filterValuesForMorphToManyRelation() does not exist on Yaro\Jarboe\Table\Fields\Traits\Relations. Did you maybe mean relation()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

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 View Code Duplication
                case HasOne::class:
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...
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
219
     * @param int $index
220
     * @return array
221
     */
222
    public function getSelectedOptions($model = null, $index = 0)
223
    {
224
        $options = [];
225
        if (is_null($model)) {
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();
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
275
    abstract public function isMultiple();
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
276
}
277