Completed
Pull Request — master (#15)
by Mads
24:44
created

ApiTransformer::hasParameters()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 0
cp 0
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 3
     */
29
    protected $strict = false;
30 10
31
    /**
32 10
     * @param array|Model $apiMapping
33
     * @return void
34 10
     */
35 3
    public function setApiMapping($apiMapping)
36 7
    {
37 10
        $this->apiMapping = [];
38
39 7
        if (true === $apiMapping instanceof Model && true === property_exists($apiMapping, 'apiMapping')) {
40
            $this->apiMapping = $apiMapping->apiMapping;
41
        } elseif (true === \is_array($apiMapping)) {
42
            $this->apiMapping = $apiMapping;
43 4
        }
44
    }
45 8
46
    /**
47 8
     * @param array|Arrayable $data
48 4
     * @return array
49 6
     */
50 4
    public function transformInput($data): array
51 2
    {
52 4
        $input = [];
53
54 4
        $data = (true === \is_array($data)) ? $data : $data->toArray();
55
        foreach ($data as $key => $value) {
56
            $input[$this->findOriginalKey($key)] = $value;
57
        }
58
59 2
        return $input;
60
    }
61 8
62
    /**
63 8
     * @param array|Arrayable $data
64
     * @return array
65 6
     */
66 2
    public function transformOutput($data): array
67
    {
68 2
        $output = [];
69 8
70 6
        if (true === $data instanceof LengthAwarePaginator || true === $data instanceof Paginator) {
71 5
            return $this->transformPaginatedOutput($data);
72
        }
73 2
74 6
        if (true === $data instanceof Collection) {
75 6
            $output = $this->transformCollection($output, $data);
76 6
        } else if (true === $data instanceof Model) {
77 4
            $output = $this->transformAttributes($output, $data->getAttributes());
78
            $output = $this->transformRelationships($output, $data);
79 6
        } else {
80
            $data = (true === \is_array($data)) ? $data : $data->toArray();
81
            $output = $this->transformAttributes($output, $data);
82
        }
83 6
84 2
        return $output;
85
    }
86 4
87 1
88 3
    /**
89
     * @param array $output
90 2
     * @param array $data
91
     * @return array
92 4
     */
93
    protected function transformAttributes(array $output, array $data): array
94
    {
95
        foreach ($data as $key => $value) {
96
            if (true === $this->strict && false === array_key_exists($key, $this->apiMapping)) {
97
                continue;
98
            }
99 4
100
            $output[$this->findNewKey($key)] = $this->convertValueType($key, $value);
101 4
        }
102 3
103 1
        return $output;
104
    }
105 1
106
    /**
107 2
     * @param array $output
108
     * @param Model $data
109
     * @return array
110
     */
111
    protected function transformRelationships(array $output, Model $data): array
112
    {
113 2
        /** @var Model $data */
114 6
        $relationships = $data->getRelations();
115 2
        foreach ($relationships as $relationshipName => $relationship) {
116 8
            if (true === $relationship instanceof Collection) {
117 8
                // do not transform empty relationships
118
                if($relationship->isEmpty()) {
119
                    continue;
120 4
                }
121
122 2
                if ($this->isTransformAware($relationship->first())) {
123 2
                    $output[$relationshipName] = $relationship->first()->getTransformer()->transformOutput($relationship);
124 2
                } else {
125 2
                    $output[$relationshipName] = $relationship->toArray();
126 2
                }
127
            } else {
128 8
                // model
129
                if ($this->isTransformAware($relationship)) {
130 8
                    $output[$relationshipName] = $relationship->getTransformer()->transformOutput($relationship);
131 7
                } else {
132 6
                    $output[$relationshipName] = $relationship->getAttributes();
133 2
                }
134
            }
135 6
        }
136
137 6
        return $output;
138 6
    }
139 6
140 4
    protected function transformPaginatedOutput($data): array
141 6
    {
142
        $result = $data->toArray();
143 6
144
        $result['data'] = $this->transformOutput($data->getCollection());
145 6
146 1
        return $result;
147
    }
148 6
149
    /**
150
     * @param array $output
151
     * @param Collection $data
152
     * @return array
153
     */
154
    protected function transformCollection(array $output, Collection $data): array
155
    {
156
        foreach ($data as $item) {
157
            $output[] = $this->transformOutput($item);
158
        }
159
160
        return $output;
161
    }
162
163
    /**
164
     * @param string $newKey
165
     * @return string
166
     */
167
    protected function findOriginalKey(string $newKey)
168
    {
169
        foreach ($this->apiMapping as $key => $value) {
170
            if (true === \in_array($newKey, $value)) {
171
                return $key;
172
            }
173
        }
174
175
        return $newKey;
176
    }
177
178
    /**
179
     * @param string $originalKey
180
     * @return string
181
     */
182
    protected function findNewKey(string $originalKey): string
183
    {
184
        if (true === array_key_exists($originalKey, $this->apiMapping)) {
185
            return $this->apiMapping[$originalKey]['newName'];
186
        }
187
188
        return $originalKey;
189
    }
190
191
    /**
192
     * @param string $key
193
     * @param mixed $value
194
     * @param string $newKey
195
     * @return mixed
196
     */
197
    protected function convertValueType(string $key, $value)
198
    {
199
        $type = (true === array_key_exists($key, $this->apiMapping))
200
            ? $this->apiMapping[$key]['dataType']
201
            : 'string';
202
203
        foreach (static::normalizeType($type) as list($method, $parameters)) {
204
            if (true === empty($method)) {
205
                return $value;
206
            }
207
208
            if ('Nullable' === $method) {
209
                if (true === empty($value) && false === \is_numeric($value)) {
210
                    return null;
211
                }
212
213
                continue;
214
            }
215
216
            $method = "convert{$method}";
217
218
            if (false === method_exists(TransformerMethods::class, $method)) {
219
                return $value;
220
            }
221
222
            return TransformerMethods::$method($value, $parameters);
223
        }
224
    }
225
226
    /**
227
     * @param $type
228
     * @return array
229
     */
230
    protected static function parseStringDataType($type): array
231
    {
232
        $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
        if (mb_strpos($type, ':') !== false) {
238
            list($dataType, $parameter) = explode(':', $type, 2);
239
240
            $parameters = static::parseParameters($parameter);
241
        }
242
243
        $dataType = static::normalizeDataType(trim($dataType ?? $type));
244
245
        return [Str::studly($dataType), $parameters ?? []];
246
    }
247
248
    /**
249
     * Parse a parameter list.
250
     *
251
     * @param  string  $parameter
252
     * @return array
253
     */
254
    protected static function parseParameters($parameter): array
255
    {
256
        return str_getcsv($parameter);
257
    }
258
259
    /**
260
     * @param $type
261
     * @return array
262
     */
263
    protected static function parseManyDataTypes($type): array
264
    {
265
        $parsed = [];
266
267
        $dataTypes = explode('|', $type);
268
269
        foreach ($dataTypes as $dataType) {
270
            $parsed[] = static::parseStringDataType(trim($dataType));
271
        }
272
273
        return $parsed;
274
    }
275
276
    /**
277
     * @param $type
278
     * @return array
279
     */
280
    protected static function normalizeType($type): array
281
    {
282
        if (false !== mb_strpos($type, '|')) {
283
            return self::normalizeNullable(
284
                static::parseManyDataTypes($type)
285
            );
286
        }
287
288
        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
    protected static function normalizeNullable($dataTypes): array
305
    {
306
        if (isset($dataTypes[1][0]) && $dataTypes[1][0] === 'Nullable') {
307
            return array_reverse($dataTypes);
308
        }
309
310
        return $dataTypes;
311
    }
312
313
    /**
314
     * @param $type
315
     * @return string
316
     */
317
    protected static function normalizeDataType($type): string
318
    {
319
        switch ($type) {
320
            case 'int':
321
                return 'integer';
322
            case 'bool':
323
                return 'boolean';
324
            case 'date':
325
                return 'datetime';
326
            default:
327
                return $type;
328
        }
329
    }
330
331
    /**
332
     * @param $model
333
     * @return bool
334
     */
335
    protected function isTransformAware($model): bool
336
    {
337
        return array_key_exists(TransformerAware::class, class_uses($model));
338
    }
339
}
340