Passed
Push — master ( 89df82...17dfde )
by Mads
04:02
created

ApiTransformer::findNewKey()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 7
ccs 4
cts 4
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Napp\Core\Api\Transformers;
4
5
use Illuminate\Contracts\Support\Arrayable;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Pagination\LengthAwarePaginator;
8
use Illuminate\Pagination\Paginator;
9
use Illuminate\Support\Collection;
10
use Illuminate\Support\Str;
11
12
/**
13
 * Class ApiTransformer
14
 * @package Napp\Core\Api\Transformers
15
 */
16
class ApiTransformer implements TransformerInterface
17
{
18
    /**
19
     * @var array
20
     */
21
    public $apiMapping = [];
22
23
    /**
24
     * Strict mode removes keys that are
25
     * not specified in api mapping array.
26
     *
27
     * @var bool
28
     */
29
    protected $strict = false;
30
31
    /**
32
     * @param array|Model $apiMapping
33
     * @return void
34
     */
35 30
    public function setApiMapping($apiMapping)
36
    {
37 30
        $this->apiMapping = [];
38
39 30
        if (true === $apiMapping instanceof Model && true === property_exists($apiMapping, 'apiMapping')) {
40 12
            $this->apiMapping = $apiMapping->apiMapping;
41 30
        } elseif (true === \is_array($apiMapping)) {
42 30
            $this->apiMapping = $apiMapping;
43
        }
44 30
    }
45
46
    /**
47
     * @param array|Arrayable $data
48
     * @return array
49
     */
50 8
    public function transformInput($data): array
51
    {
52 8
        $input = [];
53
54 8
        $data = (true === \is_array($data)) ? $data : $data->toArray();
55 8
        foreach ($data as $key => $value) {
56 4
            $input[$this->findOriginalKey($key)] = $value;
57
        }
58
59 8
        return $input;
60
    }
61
62
    /**
63
     * @param array|Arrayable $data
64
     * @return array
65
     */
66 28
    public function transformOutput($data): array
67
    {
68 28
        $output = [];
69
70 28
        if (true === $data instanceof LengthAwarePaginator || true === $data instanceof Paginator) {
71 6
            return $this->transformPaginatedOutput($data);
72
        }
73
74 28
        if (true === $data instanceof Collection) {
75 16
            $output = $this->transformCollection($output, $data);
76 28
        } else if (true === $data instanceof Model) {
77 12
            $output = $this->transformAttributes($output, $data->getAttributes());
78 12
            $output = $this->transformRelationships($output, $data);
79
        } else {
80 16
            $data = (true === \is_array($data)) ? $data : $data->toArray();
81 16
            $output = $this->transformAttributes($output, $data);
82
        }
83
84 28
        return $output;
85
    }
86
87
88
    /**
89
     * @param array $output
90
     * @param array $data
91
     * @return array
92
     */
93 28
    protected function transformAttributes(array $output, array $data): array
94
    {
95 28
        foreach ($data as $key => $value) {
96 28
            if (true === $this->strict && !$this->isMapped($key)) {
97 16
                continue;
98
            }
99
100 28
            $output[$this->findNewKey($key)] = $this->convertValueType($key, $value);
101
        }
102
103 28
        return $output;
104
    }
105
106
    /**
107
     * @param array $output
108
     * @param Model $data
109
     * @return array
110
     */
111 12
    protected function transformRelationships(array $output, Model $data): array
112
    {
113
        /** @var Model $data */
114 12
        $relationships = $data->getRelations();
115 12
        foreach ($relationships as $relationshipName => $relationship) {
116 10
            if (null === $relationship) {
117
                $output[$relationshipName] = $this->isMapped($relationshipName) ? $this->convertValueType($relationshipName, null) : null;
118
            }
119 10
            else if (true === $relationship instanceof Collection) {
120
                // do not transform empty relationships
121 10
                if ($relationship->isEmpty()) {
122 2
                    continue;
123
                }
124
125 8
                if ($this->isTransformAware($relationship->first())) {
126 8
                    $output[$relationshipName] = $relationship->first()->getTransformer()->transformOutput($relationship);
127
                } else {
128 8
                    $output[$relationshipName] = $relationship->toArray();
129
                }
130
            }
131
            else {
132
                // model
133 2
                if ($this->isTransformAware($relationship)) {
134 2
                    $output[$relationshipName] = $relationship->getTransformer()->transformOutput($relationship);
135
                } else {
136 8
                    $output[$relationshipName] = $relationship->getAttributes();
137
                }
138
            }
139
        }
140
141 12
        return $output;
142
    }
143
144 6
    protected function transformPaginatedOutput($data): array
145
    {
146 6
        $result = $data->toArray();
147
148 6
        $result['data'] = $this->transformOutput($data->getCollection());
149
150 6
        return $result;
151
    }
152
153
    /**
154
     * @param array $output
155
     * @param Collection $data
156
     * @return array
157
     */
158 16
    protected function transformCollection(array $output, Collection $data): array
159
    {
160 16
        foreach ($data as $item) {
161 16
            $output[] = $this->transformOutput($item);
162
        }
163
164 16
        return $output;
165
    }
166
167
    /**
168
     * @param string $newKey
169
     * @return string
170
     */
171 4
    protected function findOriginalKey(string $newKey)
172
    {
173 4
        foreach ($this->apiMapping as $key => $value) {
174 2
            if (true === \in_array($newKey, $value)) {
175 2
                return $key;
176
            }
177
        }
178
179 4
        return $newKey;
180
    }
181
182
    /**
183
     * @param string $originalKey
184
     * @return string
185
     */
186 28
    protected function findNewKey(string $originalKey): string
187
    {
188 28
        if (true === array_key_exists($originalKey, $this->apiMapping)) {
189 28
            return $this->apiMapping[$originalKey]['newName'];
190
        }
191
192 4
        return $originalKey;
193
    }
194
195
    /**
196
     * @param string $key
197
     * @param mixed $value
198
     * @param string $newKey
199
     * @return mixed
200
     */
201 28
    protected function convertValueType(string $key, $value)
202
    {
203 28
        $type = (true === array_key_exists($key, $this->apiMapping))
204 28
            ? $this->apiMapping[$key]['dataType']
205 28
            : 'string';
206
207 28
        foreach (static::normalizeType($type) as list($method, $parameters)) {
208 28
            if (true === empty($method)) {
209
                return $value;
210
            }
211
212 28
            if ('Nullable' === $method) {
213 2
                if (true === empty($value) && false === \is_numeric($value)) {
214 2
                    return null;
215
                }
216
217 2
                continue;
218
            }
219
220 28
            $method = "convert{$method}";
221
222 28
            if (false === method_exists(TransformerMethods::class, $method)) {
223
                return $value;
224
            }
225
226 28
            return TransformerMethods::$method($value, $parameters);
227
        }
228
    }
229
230
    /**
231
     * @param $type
232
     * @return array
233
     */
234 28
    protected static function parseStringDataType($type): array
235
    {
236 28
        $parameters = [];
237
238
        // The format for transforming data-types and parameters follows an
239
        // easy {data-type}:{parameters} formatting convention. For instance the
240
        // data-type "float:3" states that the value will be converted to a float with 3 decimals.
241 28
        if (mb_strpos($type, ':') !== false) {
242 2
            list($dataType, $parameter) = explode(':', $type, 2);
243
244 2
            $parameters = static::parseParameters($parameter);
245
        }
246
247 28
        $dataType = static::normalizeDataType(trim($dataType ?? $type));
248
249 28
        return [Str::studly($dataType), $parameters ?? []];
250
    }
251
252
    /**
253
     * Parse a parameter list.
254
     *
255
     * @param  string  $parameter
256
     * @return array
257
     */
258 2
    protected static function parseParameters($parameter): array
259
    {
260 2
        return str_getcsv($parameter);
261
    }
262
263
    /**
264
     * @param $type
265
     * @return array
266
     */
267 2
    protected static function parseManyDataTypes($type): array
268
    {
269 2
        $parsed = [];
270
271 2
        $dataTypes = explode('|', $type);
272
273 2
        foreach ($dataTypes as $dataType) {
274 2
            $parsed[] = static::parseStringDataType(trim($dataType));
275
        }
276
277 2
        return $parsed;
278
    }
279
280
    /**
281
     * @param $type
282
     * @return array
283
     */
284 28
    protected static function normalizeType($type): array
285
    {
286 28
        if (false !== mb_strpos($type, '|')) {
287 2
            return self::normalizeNullable(
288 2
                static::parseManyDataTypes($type)
289
            );
290
        }
291
292 26
        return [static::parseStringDataType(trim($type))];
293
    }
294
295
    /**
296
     * @param $type
297
     * @return bool
298
     */
299
    protected static function hasParameters($type): bool
300
    {
301
        return false !== mb_strpos($type, ':');
302
    }
303
304
    /**
305
     * @param $dataTypes
306
     * @return array
307
     */
308 2
    protected static function normalizeNullable($dataTypes): array
309
    {
310 2
        if (isset($dataTypes[1][0]) && $dataTypes[1][0] === 'Nullable') {
311 2
            return array_reverse($dataTypes);
312
        }
313
314 2
        return $dataTypes;
315
    }
316
317
    /**
318
     * @param $type
319
     * @return string
320
     */
321 28
    protected static function normalizeDataType($type): string
322
    {
323
        switch ($type) {
324 28
            case 'int':
325 26
                return 'integer';
326 28
            case 'bool':
327 8
                return 'boolean';
328 28
            case 'date':
329
                return 'datetime';
330
            default:
331 28
                return $type;
332
        }
333
    }
334
335
    /**
336
     * @param $model
337
     * @return bool
338
     */
339 8
    protected function isTransformAware($model): bool
340
    {
341 8
        return array_key_exists(TransformerAware::class, class_uses($model));
342
    }
343
344
    /**
345
     * Check if key is mapped with apiMapping
346
     * @param $key
347
     * @return bool
348
     */
349 16
    protected function isMapped($key): bool
350
    {
351 16
        return true === array_key_exists($key, $this->apiMapping);
352
    }
353
}
354