Completed
Push — master ( eab647...b4731b )
by Mads
23:22 queued 20:45
created

ApiTransformer::parseParameters()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
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 && false === array_key_exists($key, $this->apiMapping)) {
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 (true === $relationship instanceof Collection) {
117
                // do not transform empty relationships
118 10
                if($relationship->isEmpty()) {
119 2
                    continue;
120
                }
121
122 8
                if ($this->isTransformAware($relationship->first())) {
123 8
                    $output[$relationshipName] = $relationship->first()->getTransformer()->transformOutput($relationship);
124
                } else {
125 8
                    $output[$relationshipName] = $relationship->toArray();
126
                }
127
            } else {
128
                // model
129 2
                if ($this->isTransformAware($relationship)) {
130 2
                    $output[$relationshipName] = $relationship->getTransformer()->transformOutput($relationship);
131
                } else {
132 8
                    $output[$relationshipName] = $relationship->getAttributes();
133
                }
134
            }
135
        }
136
137 12
        return $output;
138
    }
139
140 6
    protected function transformPaginatedOutput($data): array
141
    {
142 6
        $result = $data->toArray();
143
144 6
        $result['data'] = $this->transformOutput($data->getCollection());
145
146 6
        return $result;
147
    }
148
149
    /**
150
     * @param array $output
151
     * @param Collection $data
152
     * @return array
153
     */
154 16
    protected function transformCollection(array $output, Collection $data): array
155
    {
156 16
        foreach ($data as $item) {
157 16
            $output[] = $this->transformOutput($item);
158
        }
159
160 16
        return $output;
161
    }
162
163
    /**
164
     * @param string $newKey
165
     * @return string
166
     */
167 4
    protected function findOriginalKey(string $newKey)
168
    {
169 4
        foreach ($this->apiMapping as $key => $value) {
170 2
            if (true === \in_array($newKey, $value)) {
171 2
                return $key;
172
            }
173
        }
174
175 4
        return $newKey;
176
    }
177
178
    /**
179
     * @param string $originalKey
180
     * @return string
181
     */
182 28
    protected function findNewKey(string $originalKey): string
183
    {
184 28
        if (true === array_key_exists($originalKey, $this->apiMapping)) {
185 28
            return $this->apiMapping[$originalKey]['newName'];
186
        }
187
188 4
        return $originalKey;
189
    }
190
191
    /**
192
     * @param string $key
193
     * @param mixed $value
194
     * @param string $newKey
195
     * @return mixed
196
     */
197 28
    protected function convertValueType(string $key, $value)
198
    {
199 28
        $type = (true === array_key_exists($key, $this->apiMapping))
200 28
            ? $this->apiMapping[$key]['dataType']
201 28
            : 'string';
202
203 28
        foreach (static::normalizeType($type) as list($method, $parameters)) {
204 28
            if (true === empty($method)) {
205
                return $value;
206
            }
207
208 28
            if ('Nullable' === $method) {
209 2
                if (true === empty($value) && false === \is_numeric($value)) {
210 2
                    return null;
211
                }
212
213 2
                continue;
214
            }
215
216 28
            $method = "convert{$method}";
217
218 28
            if (false === method_exists(TransformerMethods::class, $method)) {
219
                return $value;
220
            }
221
222 28
            return TransformerMethods::$method($value, $parameters);
223
        }
224
    }
225
226
    /**
227
     * @param $type
228
     * @return array
229
     */
230 28
    protected static function parseStringDataType($type): array
231
    {
232 28
        $parameters = [];
233
234
        // The format for transforming data-types and parameters follows an
235
        // easy {data-type}:{parameters} formatting convention. For instance the
236
        // data-type "float:3" states that the value will be converted to a float with 3 decimals.
237 28
        if (mb_strpos($type, ':') !== false) {
238 2
            list($dataType, $parameter) = explode(':', $type, 2);
239
240 2
            $parameters = static::parseParameters($parameter);
241
        }
242
243 28
        $dataType = static::normalizeDataType(trim($dataType ?? $type));
244
245 28
        return [Str::studly($dataType), $parameters ?? []];
246
    }
247
248
    /**
249
     * Parse a parameter list.
250
     *
251
     * @param  string  $parameter
252
     * @return array
253
     */
254 2
    protected static function parseParameters($parameter): array
255
    {
256 2
        return str_getcsv($parameter);
257
    }
258
259
    /**
260
     * @param $type
261
     * @return array
262
     */
263 2
    protected static function parseManyDataTypes($type): array
264
    {
265 2
        $parsed = [];
266
267 2
        $dataTypes = explode('|', $type);
268
269 2
        foreach ($dataTypes as $dataType) {
270 2
            $parsed[] = static::parseStringDataType(trim($dataType));
271
        }
272
273 2
        return $parsed;
274
    }
275
276
    /**
277
     * @param $type
278
     * @return array
279
     */
280 28
    protected static function normalizeType($type): array
281
    {
282 28
        if (false !== mb_strpos($type, '|')) {
283 2
            return self::normalizeNullable(
284 2
                static::parseManyDataTypes($type)
285
            );
286
        }
287
288 26
        return [static::parseStringDataType(trim($type))];
289
    }
290
291
    /**
292
     * @param $type
293
     * @return bool
294
     */
295
    protected static function hasParameters($type): bool
296
    {
297
        return false !== mb_strpos($type, ':');
298
    }
299
300
    /**
301
     * @param $dataTypes
302
     * @return array
303
     */
304 2
    protected static function normalizeNullable($dataTypes): array
305
    {
306 2
        if (isset($dataTypes[1][0]) && $dataTypes[1][0] === 'Nullable') {
307 2
            return array_reverse($dataTypes);
308
        }
309
310 2
        return $dataTypes;
311
    }
312
313
    /**
314
     * @param $type
315
     * @return string
316
     */
317 28
    protected static function normalizeDataType($type): string
318
    {
319
        switch ($type) {
320 28
            case 'int':
321 26
                return 'integer';
322 28
            case 'bool':
323 8
                return 'boolean';
324 28
            case 'date':
325
                return 'datetime';
326
            default:
327 28
                return $type;
328
        }
329
    }
330
331
    /**
332
     * @param $model
333
     * @return bool
334
     */
335 8
    protected function isTransformAware($model): bool
336
    {
337 8
        return array_key_exists(TransformerAware::class, class_uses($model));
338
    }
339
}
340