Passed
Push — master ( 5eda98...385235 )
by Arthur
03:20
created

Json::changeParamKey()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 8
c 1
b 0
f 0
dl 0
loc 12
rs 10
cc 4
nc 6
nop 4
1
<?php
2
3
namespace SoliDry\Helpers;
4
5
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Http\Request;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, SoliDry\Helpers\Request. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
8
use League\Fractal\Manager;
9
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
10
use League\Fractal\Resource\Collection;
11
use League\Fractal\Resource\Item;
12
use League\Fractal\Resource\ResourceInterface;
13
use League\Fractal\Serializer\JsonApiSerializer;
14
use SoliDry\Types\ConfigInterface;
15
use SoliDry\Types\ModelsInterface;
16
use SoliDry\Types\PhpInterface;
17
use SoliDry\Types\ApiInterface;
18
use SoliDry\Extension\BaseFormRequest;
19
use SoliDry\Extension\JSONApiInterface;
20
use SoliDry\Transformers\DefaultTransformer;
21
use SoliDry\Helpers\Request as Req;
22
23
/**
24
 * Class Json
25
 * @package SoliDry\Helpers
26
 */
27
class Json extends JsonAbstract
28
{
29
    private $isCollection = false;
30
    private $meta = [];
31
32
    /**
33
     * @param $relations      \Illuminate\Database\Eloquent\Collection
34
     * @param string $entity
35
     * @return array JSON API rels compatible array
36
     */
37
    public static function getRelations($relations, string $entity): array
38
    {
39
        $jsonArr = [];
40
        if ($relations instanceof \Illuminate\Database\Eloquent\Collection) {
41
            $cnt = \count($relations);
42
            if ($cnt > 1) {
43
                foreach ($relations as $v) {
44
                    $attrs = $v->getAttributes();
45
                    $jsonArr[] = [ApiInterface::RAML_TYPE => $entity,
46
                        ApiInterface::RAML_ID => $attrs[ApiInterface::RAML_ID]];
47
                }
48
            } else {
49
                foreach ($relations as $v) {
50
                    $attrs = $v->getAttributes();
51
                    $jsonArr = [ApiInterface::RAML_TYPE => $entity,
52
                        ApiInterface::RAML_ID => $attrs[ApiInterface::RAML_ID]];
53
                }
54
            }
55
        } elseif ($relations instanceof Model) {
56
            $attrs = $relations->getAttributes();
57
            $jsonArr = [ApiInterface::RAML_TYPE => $entity,
58
                ApiInterface::RAML_ID => $attrs[ApiInterface::RAML_ID]];
59
        }
60
61
        return $jsonArr;
62
    }
63
64
    /**
65
     * Output errors in JSON API compatible format
66
     * @param array $errors
67
     * @param bool $return
68
     * @return string
69
     */
70
    public static function outputErrors(array $errors, bool $return = false)
71
    {
72
        $arr[JSONApiInterface::CONTENT_ERRORS] = [];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$arr was never initialized. Although not strictly required by PHP, it is generally a good practice to add $arr = array(); before regardless.
Loading history...
73
        if (empty($errors) === false) {
74
            $arr[JSONApiInterface::CONTENT_ERRORS] = $errors;
75
        }
76
        // errors and codes must be clear with readable json
77
        $encoded = self::encode($arr, JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT);
78
        if ($return === false && env('APP_ENV') !== 'dev') {
79
            echo $encoded;
80
            exit(JSONApiInterface::EXIT_STATUS_ERROR);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
81
        }
82
        return $encoded;
83
    }
84
85
    /**
86
     *
87
     * @param array $errors
88
     * @return string
89
     */
90
    public function getErrors(array $errors): string
91
    {
92
        $arr[JSONApiInterface::CONTENT_ERRORS] = [];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$arr was never initialized. Although not strictly required by PHP, it is generally a good practice to add $arr = array(); before regardless.
Loading history...
93
        if (empty($errors) === false) {
94
            $arr[JSONApiInterface::CONTENT_ERRORS] = $errors;
95
        }
96
97
        return self::encode($arr, JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT);
98
    }
99
100
    /**
101
     * Returns composition of relations
102
     *
103
     * @param Request $request
104
     * @param array $data
105
     * @return string
106
     */
107
    public static function prepareSerializedRelations(Request $request, array $data): string
108
    {
109
        $arr[JSONApiInterface::CONTENT_LINKS] = [
0 ignored issues
show
Comprehensibility Best Practice introduced by
$arr was never initialized. Although not strictly required by PHP, it is generally a good practice to add $arr = array(); before regardless.
Loading history...
110
            JSONApiInterface::CONTENT_SELF => $request->getUri(),
111
        ];
112
113
        $arr[JSONApiInterface::CONTENT_DATA] = $data;
114
115
        return self::encode($arr);
116
    }
117
118
    /**
119
     * @param BaseFormRequest $formRequest
120
     * @param                 $model
121
     * @param string $entity
122
     *
123
     * @return Collection|Item
124
     */
125
    public function getResource(BaseFormRequest $formRequest, $model, string $entity)
126
    {
127
        $transformer = new DefaultTransformer($formRequest);
128
        if ($this->isCollection === true) {
129
            $collection = new Collection($model, $transformer, strtolower($entity));
130
            if (empty($this->meta) === false) {
131
                $collection->setMeta($this->meta);
132
            }
133
134
            if ($model instanceof LengthAwarePaginator) { // only for paginator
135
                $collection->setPaginator(new IlluminatePaginatorAdapter($model));
136
            }
137
138
            return $collection;
139
        }
140
141
        $item = new Item($model, $transformer, strtolower($entity));
142
        $item->setMeta($this->meta);
143
144
        return $item;
145
    }
146
147
    /**
148
     * Prepares data to output in json-api format
149
     *
150
     * @param ResourceInterface $resource
151
     * @param array $data
152
     * @return string
153
     */
154
    public static function prepareSerializedData(ResourceInterface $resource, $data = ModelsInterface::DEFAULT_DATA): string
155
    {
156
        if (empty($resource->getData())) { // preventing 3d party libs (League etc) from crash on empty data
157
            return self::encode([
158
                ModelsInterface::PARAM_DATA => []
159
            ]);
160
        }
161
162
        $manager = new Manager();
163
        if (isset($_GET['include'])) {
164
            $manager->parseIncludes($_GET['include']);
165
        }
166
167
        $manager->setSerializer(new JsonApiSerializer((new Req())->getBasePath()));
168
        return self::getSelectedData($manager->createData($resource)->toJson(), $data);
169
    }
170
171
    /**
172
     * Gets data only with those elements of object/array that should be provided as output
173
     *
174
     * @param string $json
175
     * @param array $data
176
     * @return string
177
     */
178
    private static function getSelectedData(string $json, array $data): string
179
    {
180
        if (current($data) === PhpInterface::ASTERISK) {// do nothing - grab all fields
181
            return $json;
182
        }
183
184
        $jsonArr = self::decode($json);
185
        $current = current($jsonArr[ApiInterface::RAML_DATA]);
186
187
        if (empty($current[JSONApiInterface::CONTENT_ATTRIBUTES]) === false) {// this is an array of values
188
            self::unsetArray($jsonArr, $data);
189
        } else {// this is just one element
190
            self::unsetObject($jsonArr, $data);
191
        }
192
193
        return self::encode($jsonArr);
194
    }
195
196
    /**
197
     * Unsets objects from array that shouldn't be provided as output
198
     *
199
     * @param array &$json
200
     * @param array $data
201
     */
202
    private static function unsetArray(array &$json, array $data): void
203
    {
204
        $attrsCase = ConfigHelper::getParam(ConfigInterface::ATTRIBUTES_CASE);
205
        foreach ($json as $type => &$jsonObject) {
206
207
            if ($type === ApiInterface::RAML_DATA) { // unset only data->attributes fields
208
                foreach ($jsonObject as &$v) {
209
210
                    if (empty($v[JSONApiInterface::CONTENT_ATTRIBUTES]) === false) { // can be any of meta/link
211
                        foreach ($v[JSONApiInterface::CONTENT_ATTRIBUTES] as $key => $attr) {
212
213
                            if (\in_array($key, $data, true) === false) {
214
                                unset($v[JSONApiInterface::CONTENT_ATTRIBUTES][$key]);
215
                            } else if ($attrsCase !== ConfigInterface::DEFAULT_CASE) {
216
                                self::changeParamKey($json, $k, $v, $attrsCase);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $k seems to be never defined.
Loading history...
217
                            }
218
                        }
219
                    }
220
                }
221
            }
222
        }
223
    }
224
225
    /**
226
     * Unsets objects that shouldn't be provided as output
227
     *
228
     * @param array $json
229
     * @param array $data
230
     */
231
    private static function unsetObject(array &$json, array $data): void
232
    {
233
        $isDataAndAttrs = empty($json[JSONApiInterface::CONTENT_DATA]) === false
234
            && empty($json[JSONApiInterface::CONTENT_DATA][JSONApiInterface::CONTENT_ATTRIBUTES]) === false;
235
236
        if ($isDataAndAttrs) {
237
            $attrsCase = ConfigHelper::getParam(ConfigInterface::ATTRIBUTES_CASE);
238
            foreach ($json[JSONApiInterface::CONTENT_DATA][JSONApiInterface::CONTENT_ATTRIBUTES] as $k => $v) {
239
                if (\in_array($k, $data, true) === false) {
240
                    unset($json[JSONApiInterface::CONTENT_DATA][JSONApiInterface::CONTENT_ATTRIBUTES][$k]);
241
                } else if ($attrsCase !== ConfigInterface::DEFAULT_CASE) {
242
                    self::changeParamKey($json, $k, $v, $attrsCase);
0 ignored issues
show
Bug introduced by
It seems like $attrsCase can also be of type Illuminate\Config\Repository; however, parameter $attrsCase of SoliDry\Helpers\Json::changeParamKey() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

242
                    self::changeParamKey($json, $k, $v, /** @scrutinizer ignore-type */ $attrsCase);
Loading history...
243
                }
244
            }
245
        }
246
    }
247
248
    /**
249
     * @param array $json
250
     * @param string $k
251
     * @param mixed $v
252
     * @param string $attrsCase
253
     */
254
    public static function changeParamKey(array &$json, string $k, $v, string $attrsCase): void
255
    {
256
        $changedKey = $k;
257
        if ($attrsCase === ConfigInterface::CAMEL_CASE) {
258
            $changedKey = self::changeParamKeyToLowerCamelCase($k);
259
        } else if ($attrsCase === ConfigInterface::LISP_CASE) {
260
            $changedKey = self::changeParamKeyToLispCase($k);
261
        }
262
263
        $json[JSONApiInterface::CONTENT_DATA][JSONApiInterface::CONTENT_ATTRIBUTES][$changedKey] = $v;
264
        if ($changedKey !== $k) {
265
            unset($json[JSONApiInterface::CONTENT_DATA][JSONApiInterface::CONTENT_ATTRIBUTES][$k]);
266
        }
267
    }
268
269
    /**
270
     * @param string $param
271
     * @return string
272
     */
273
    public static function changeParamKeyToLowerCamelCase(string $param): string
274
    {
275
        return lcfirst(
276
            str_replace(' ', '', ucwords(
277
                    str_replace('_', ' ', $param)
278
                )
279
            )
280
        );
281
    }
282
283
    /**
284
     * @param string $param
285
     * @return string
286
     */
287
    public static function changeParamKeyToLispCase(string $param): string
288
    {
289
        return lcfirst(str_replace('_', '-', $param));
290
    }
291
292
    /**
293
     * @param bool $isCollection
294
     * @return Json
295
     */
296
    public function setIsCollection(bool $isCollection): Json
297
    {
298
        $this->isCollection = $isCollection;
299
300
        return $this;
301
    }
302
303
    /**
304
     * @param array $meta
305
     * @return Json
306
     */
307
    public function setMeta(array $meta): Json
308
    {
309
        $this->meta = $meta;
310
311
        return $this;
312
    }
313
}