FieldsManager::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 2
1
<?php
2
3
namespace Laravel\ProductFields;
4
5
use Illuminate\Database\Eloquent\Model;
6
use Illuminate\Support\Collection;
7
use Illuminate\Support\Facades\DB;
8
9
class FieldsManager
10
{
11
    /**
12
     * @var ModelsResolver
13
     */
14
    protected $modelsResolver;
15
16
    /**
17
     * @var ResourcesResolver
18
     */
19
    protected $resourcesResolver;
20
21
    /**
22
     * FieldsManager constructor.
23
     *
24
     * @param ModelsResolver    $modelsResolver
25
     * @param ResourcesResolver $resourcesResolver
26
     */
27
    public function __construct(ModelsResolver $modelsResolver, ResourcesResolver $resourcesResolver)
28
    {
29
        $this->modelsResolver = $modelsResolver;
30
        $this->resourcesResolver = $resourcesResolver;
31
    }
32
33
    /**
34
     * Attach field values to the given resource.
35
     *
36
     * @param Model $resource
37
     * @param array $fieldValues
38
     *
39
     * @return Model
40
     */
41
    public function attachFields(Model $resource, array $fieldValues)
42
    {
43
        $fields = $this->createAttachableArray($fieldValues);
44
45
        $resource->fields()->attach($fields);
46
47
        return $resource;
48
    }
49
50
    /**
51
     * Sync field values with given resource.
52
     *
53
     * @param Model $resource
54
     * @param array $fieldValues
55
     * @param bool  $detach
56
     *
57
     * @return Model
58
     */
59
    public function syncFields(Model $resource, array $fieldValues, bool $detach = true)
60
    {
61
        $fields = $this->createAttachableArray($fieldValues);
62
63
        if ($detach === true) {
64
            $resource->fields()->sync($fields);
65
        } else {
66
            $resource->fields()->syncWithoutDetaching($fields);
67
        }
68
69
        return $resource;
70
    }
71
72
    /**
73
     * Detach field values from the given model.
74
     *
75
     * @param Model $resource
76
     * @param array $fieldIds
77
     *
78
     * @return Model
79
     */
80
    public function detachFields(Model $resource, array $fieldIds)
81
    {
82
        $resource->fields()->detach($fieldIds);
83
84
        return $resource;
85
    }
86
87
    /**
88
     * Transforms fieldId=>fieldValueId array to attachable list.
89
     *
90
     * @param array $fieldValues
91
     *
92
     * @return array
93
     */
94
    protected function createAttachableArray(array $fieldValues)
95
    {
96
        $fields = [];
97
98
        foreach ($fieldValues as $fieldId => $fieldValueId) {
99
            $fields[$fieldId] = ['field_value_id' => $fieldValueId];
100
        }
101
102
        return $fields;
103
    }
104
105
    /**
106
     * Loads resources that have exact field values attached.
107
     *
108
     * @param Collection $fieldValues
109
     *
110
     * @return Collection
111
     */
112
    public function getResourcesByFieldValues(Collection $fieldValues)
113
    {
114
        $valuesId = $this->resolveValuesId($fieldValues);
115
        $rows = $this->loadRowsByValuesId($valuesId);
116
117
        if (!count($rows)) {
118
            return collect([]);
119
        }
120
121
        return $this->resolveModels($rows);
122
    }
123
124
    /**
125
     * Loads resources by given field=>value pairs.
126
     *
127
     * @param Collection $filter
128
     *
129
     * @return Collection
130
     */
131
    public function filterResources(Collection $filter)
132
    {
133
        $rows = $this->loadRowsByFilter($filter);
134
135
        if (!count($rows)) {
136
            return collect([]);
137
        }
138
139
        return $this->resolveModels($rows);
140
    }
141
142
    /**
143
     * Resolves actual models via ResourceResolver and groups them by short key or model class name.
144
     *
145
     * @param Collection $rows
146
     *
147
     * @return Collection
148
     */
149
    protected function resolveModels($rows)
150
    {
151
        $groupped = $this->groupRowsByModelType($rows);
152
        $models = collect([]);
153
154
        $groupped->each(function (Collection $ids, string $model) use ($models) {
155
            $results = $this->resourcesResolver->resolve($model, $ids->toArray());
156
157
            if (!count($results['resources'])) {
158
                return;
159
            }
160
161
            $models->put($results['short_key'], collect($results['resources']));
162
        });
163
164
        return $models;
165
    }
166
167
    /**
168
     * Loads filtered rows from `filerables` table.
169
     *
170
     * @param Collection $filter
171
     *
172
     * @return Collection
173
     */
174
    protected function loadRowsByFilter(Collection $filter)
175
    {
176
        $query = DB::table($this->modelsResolver->morphName().'s');
177
        $fieldValueModel = $this->modelsResolver->fieldValue();
178
179
        $filter->each(function ($value, int $fieldId) use ($query, $fieldValueModel) {
180
            $query->orWhere(function ($q) use ($fieldId, $value, $fieldValueModel) {
181
                $q->where('field_id', $fieldId)
182
                    ->whereIn('field_value_id', $this->resolveValuesId($value, $fieldValueModel));
183
            });
184
        });
185
186
        return $query->get();
187
    }
188
189
    /**
190
     * Groups DB rows by attached model type.
191
     *
192
     * @param Collection $rows
193
     *
194
     * @return Collection
195
     */
196
    protected function groupRowsByModelType(Collection $rows)
197
    {
198
        $typeColumn = $this->modelsResolver->morphName().'_type';
199
        $idColumn = $this->modelsResolver->morphName().'_id';
200
201
        return collect($rows)->groupBy($typeColumn)->map->pluck($idColumn);
202
    }
203
204
    /**
205
     * Load rows that have given field_value_id list attached.
206
     *
207
     * @param array $valuesId
208
     *
209
     * @return Collection
210
     */
211
    protected function loadRowsByValuesId(array $valuesId)
212
    {
213
        $table = $this->modelsResolver->morphName().'s';
214
215
        return DB::table($table)->whereIn('field_value_id', $valuesId)->get();
216
    }
217
218
    /**
219
     * Creates array of value IDs from Collection or array of models/integers (or from single model/integer).
220
     *
221
     * @param \Illuminate\Database\Eloquent\Model|int|Collection|array $fieldValues
222
     * @param string                                                   $fieldValueModel = null
223
     *
224
     * @return array
225
     */
226
    protected function resolveValuesId($fieldValues, string $fieldValueModel = null): array
227
    {
228
        $fieldValueModel = $fieldValueModel ?? $this->modelsResolver->fieldValue();
229
230
        if ($fieldValues instanceof $fieldValueModel) {
231
            return [$fieldValues->id];
232
        } elseif (is_numeric($fieldValues)) {
233
            return [intval($fieldValues)];
234
        } elseif ($fieldValues instanceof Collection) {
235
            return $this->mapIdsFromCollection($fieldValues, $fieldValueModel);
236
        } elseif (is_array($fieldValues)) {
237
            return $this->mapIdsFromCollection(collect($fieldValues), $fieldValueModel);
238
        }
239
240
        return [];
241
    }
242
243
    /**
244
     * Extracts value IDs from collection.
245
     *
246
     * @param Collection $fieldValues
247
     * @param $fieldValueModel
248
     *
249
     * @return array
250
     */
251
    protected function mapIdsFromCollection(Collection $fieldValues, $fieldValueModel)
252
    {
253
        $values = $fieldValues->map(function ($value) use ($fieldValueModel) {
254
            if ($value instanceof $fieldValueModel) {
255
                return $value->id;
256
            } elseif (is_numeric($value)) {
257
                return intval($value);
258
            }
259
260
            return null;
261
        });
262
263
        return $values->reject(null)->toArray();
264
    }
265
}
266