ApiTransformer::transformAttributes()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4

Importance

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