HasRelationships::mappedTransformerClass()   A
last analyzed

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