Passed
Pull Request — master (#164)
by
unknown
02:23
created

HasRelationships::mappedTransformerClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Flugg\Responder\Transformers\Concerns;
4
5
use Illuminate\Database\Eloquent\Model;
6
use Illuminate\Support\Arr;
7
use Illuminate\Support\Str;
8
9
/**
10
 * A trait to be used by a transformer to handle relations
11
 *
12
 * @package flugger/laravel-responder
13
 * @author  Alexander Tømmerås <[email protected]>
14
 * @license The MIT License
15
 */
16
trait HasRelationships
17
{
18
    /**
19
     * List of available relations.
20
     *
21
     * @var string[]
22
     */
23
    protected $relations = [];
24
25
    /**
26
     * A list of autoloaded default relations.
27
     *
28
     * @var array
29
     */
30
    protected $load = [];
31
32
    /**
33
     * Get list of available relations.
34
     *
35
     * @return array
36
     */
37 8
    public function getRelations(): array
38
    {
39 8
        return $this->relations;
40
    }
41
42
    /**
43
     * Get a list of whitelisted relations that are requested, including nested relations.
44
     *
45
     * @param  array $requested
46
     * @return array
47
     */
48 41 View Code Duplication
    public function relations(array $requested = []): array
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...
49
    {
50 41
        $requested = $this->normalizeRelations($requested);
51 41
        $relations = $this->applyQueryConstraints($this->extractRelations($requested));
52 41
        $nestedRelations = $this->nestedRelations($requested, $relations, 'relations');
53
54 41
        return array_merge($relations, $nestedRelations);
55
    }
56
57
    /**
58
     * Get a list of default relations including nested relations.
59
     *
60
     * @param  array $requested
61
     * @return array
62
     */
63 41 View Code Duplication
    public function defaultRelations(array $requested = []): array
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...
64
    {
65 41
        $requested = $this->normalizeRelations($requested);
66 41
        $relations = $this->applyQueryConstraints($this->normalizeRelations($this->load));
67 41
        $nestedRelations = $this->nestedRelations($relations, array_merge($relations, $requested), 'defaultRelations');
68
69 41
        return array_merge($relations, $nestedRelations);
70
    }
71
72
    /**
73
     * Get a list of available relations from the transformer with a normalized structure.
74
     *
75
     * @return array
76
     */
77 44
    protected function availableRelations(): array
78
    {
79 44
        return $this->normalizeRelations(array_merge($this->relations, $this->load));
80
    }
81
82
    /**
83
     * Get nested relations from transformers resolved from the $available parameter that
84
     * also occur in the $requested parameter.
85
     *
86
     * @param  array  $requested
87
     * @param  array  $available
88
     * @param  string $method
89
     * @return array
90
     */
91 41
    protected function nestedRelations(array $requested, array $available, string $method): array
92
    {
93 41
        $transformers = $this->mappedTransformers($available);
94
95
        return collect(array_keys($transformers))->reduce(function ($nestedRelations, $relation) use ($requested, $method, $transformers) {
96 12
            $transformer = $transformers[$relation];
97 12
            $children = $this->extractChildRelations($requested, $relation);
98 12
            $childRelations = $this->wrapChildRelations($transformer->$method($children), $relation);
99
100 12
            return array_merge($nestedRelations, $childRelations);
101 41
        }, []);
102
    }
103
104
    /**
105
     * Extract available root relations from the given list of relations.
106
     *
107
     * @param  array $relations
108
     * @return array
109
     */
110 41
    protected function extractRelations(array $relations): array
111
    {
112 41
        $available = $this->availableRelations();
113
114
        return array_filter($this->mapRelations($relations, function ($relation, $constraint) {
115 18
            $identifier = explode('.', $relation)[0];
116 18
            $constraint = $identifier === $relation ? $constraint : null;
117
118 18
            return [$identifier => $constraint ?: $this->resolveQueryConstraint($identifier)];
119
        }), function ($relation) use ($available) {
120 18
            return Arr::has($available, explode(':', $relation)[0]);
121 41
        }, ARRAY_FILTER_USE_KEY);
122
    }
123
124
    /**
125
     * Extract all nested relations under a given identifier.
126
     *
127
     * @param  array  $relations
128
     * @param  string $identifier
129
     * @return array
130
     */
131 12
    protected function extractChildRelations(array $relations, string $identifier): array
132
    {
133
        return array_reduce(array_keys($relations), function ($nested, $relation) use ($relations, $identifier) {
134 12
            if (! Str::startsWith($relation, "$identifier.")) {
135 12
                return $nested;
136
            }
137
138 6
            $nestedIdentifier = explode('.', $relation);
139 6
            array_shift($nestedIdentifier);
140
141 6
            return array_merge($nested, [implode('.', $nestedIdentifier) => $relations[$relation]]);
142 12
        }, []);
143
    }
144
145
    /**
146
     * Wrap the identifier of each relation of the given list of nested relations with
147
     * the parent relation identifier using dot notation.
148
     *
149
     * @param  array  $nestedRelations
150
     * @param  string $relation
151
     * @return array
152
     */
153 12
    protected function wrapChildRelations(array $nestedRelations, string $relation): array
154
    {
155
        return $this->mapRelations($nestedRelations, function ($nestedRelation, $constraint) use ($relation) {
156 9
            return ["$relation.$nestedRelation" => $constraint];
157 12
        });
158
    }
159
160
    /**
161
     * Normalize relations to force an [identifier => constraint/transformer] structure.
162
     *
163
     * @param  array $relations
164
     * @return array
165
     */
166 46
    protected function normalizeRelations(array $relations): array
167
    {
168
        return array_reduce(array_keys($relations), function ($normalized, $relation) use ($relations) {
169 25
            if (is_numeric($relation)) {
170 15
                return array_merge($normalized, [$relations[$relation] => null]);
171
            }
172
173 21
            return array_merge($normalized, [$relation => $relations[$relation]]);
174 46
        }, []);
175
    }
176
177
    /**
178
     * Map over a list of relations with the [identifier => constraint/transformer] structure.
179
     *
180
     * @param  array    $relations
181
     * @param  callable $callback
182
     * @return array
183
     */
184 41
    protected function mapRelations(array $relations, callable $callback): array
185
    {
186 41
        $mapped = [];
187
188 41
        foreach ($relations as $identifier => $value) {
189 20
            $mapped = array_merge($mapped, $callback($identifier, $value));
190
        }
191
192 41
        return $mapped;
193
    }
194
195
    /**
196
     * Applies any query constraints defined in the transformer to the list of relaations.
197
     *
198
     * @param  array $relations
199
     * @return array
200
     */
201 41
    protected function applyQueryConstraints(array $relations): array
202
    {
203
        return $this->mapRelations($relations, function ($relation, $constraint) {
204 19
            return [$relation => is_callable($constraint) ? $constraint : $this->resolveQueryConstraint($relation)];
205 41
        });
206
    }
207
208
    /**
209
     * Resolve a query constraint for a given relation identifier.
210
     *
211
     * @param  string $identifier
212
     * @return \Closure|null
213
     */
214 19
    protected function resolveQueryConstraint(string $identifier)
215
    {
216 19
        if (! method_exists($this, $method = 'load' . ucfirst(Str::camel($identifier)))) {
217 18
            return null;
218
        }
219
220
        return function ($query) use ($method) {
221 1
            return $this->$method($query);
222 1
        };
223
    }
224
225
    /**
226
     * Resolve a relation from a model instance and an identifier.
227
     *
228
     * @param  \Illuminate\Database\Eloquent\Model $model
229
     * @param  string                              $identifier
230
     * @return mixed
231
     */
232 18
    protected function resolveRelation(Model $model, string $identifier)
233
    {
234 18
        $identifier = Str::camel($identifier);
235 18
        $relation = $model->$identifier;
236
237 18
        if (method_exists($this, $method = 'filter' . ucfirst($identifier))) {
238 1
            return $this->$method($relation);
239
        }
240
241 17
        return $relation;
242
    }
243
244
    /**
245
     * Resolve a list of transformers from a list of relations mapped to transformers.
246
     *
247
     * @param  array $relations
248
     * @return array
249
     */
250 41
    protected function mappedTransformers(array $relations): array
251
    {
252
        $transformers = collect($this->availableRelations())->filter(function ($transformer) {
253 19
            return ! is_null($transformer);
254
        })->map(function ($transformer) {
255 12
            return $this->resolveTransformer($transformer);
256 41
        })->all();
257
258 41
        return array_intersect_key($transformers, $relations);
259
    }
260
261
    /**
262
     * Get a related transformer class mapped to a relation identifier.
263
     *
264
     * @param  string $identifier
265
     * @return string|null
266
     */
267 22
    protected function mappedTransformerClass(string $identifier)
268
    {
269 22
        return $this->availableRelations()[$identifier] ?? null;
270
    }
271
272
    /**
273
     * Resolve a transformer from a class name string.
274
     *
275
     * @param  string $transformer
276
     * @return mixed
277
     */
278
    protected abstract function resolveTransformer(string $transformer);
279
}