Passed
Push — master ( 5c5b78...dff8d5 )
by Mads
04:21
created

ApiTransformer::setApiMapping()   A

Complexity

Conditions 4
Paths 3

Size

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