Issues (150)

src/Helpers/Json.php (2 issues)

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
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
    /**
30
     * @var bool
31
     */
32
    private bool $isCollection = false;
33
34
    /**
35
     * @var array
36
     */
37
    private array $meta = [];
38
39
    /**
40
     * @param $relations      \Illuminate\Database\Eloquent\Collection
41
     * @param string $entity
42
     * @return array JSON API rels compatible array
43
     */
44
    public static function getRelations($relations, string $entity): array
45
    {
46
        $jsonArr = [];
47
        if ($relations instanceof \Illuminate\Database\Eloquent\Collection) {
48
            $cnt = \count($relations);
49
            if ($cnt > 1) {
50
                foreach ($relations as $v) {
51
                    $attrs = $v->getAttributes();
52
                    $jsonArr[] = [ApiInterface::RAML_TYPE => $entity,
53
                        ApiInterface::RAML_ID => $attrs[ApiInterface::RAML_ID]];
54
                }
55
            } else {
56
                foreach ($relations as $v) {
57
                    $attrs = $v->getAttributes();
58
                    $jsonArr = [ApiInterface::RAML_TYPE => $entity,
59
                        ApiInterface::RAML_ID => $attrs[ApiInterface::RAML_ID]];
60
                }
61
            }
62
        } elseif ($relations instanceof Model) {
63
            $attrs = $relations->getAttributes();
64
            $jsonArr = [ApiInterface::RAML_TYPE => $entity,
65
                ApiInterface::RAML_ID => $attrs[ApiInterface::RAML_ID]];
66
        }
67
68
        return $jsonArr;
69
    }
70
71
    /**
72
     * Output errors in JSON API compatible format
73
     * @param array $errors
74
     * @param bool $return
75
     * @return string
76
     */
77
    public static function outputErrors(array $errors, bool $return = false)
78
    {
79
        $arr[JSONApiInterface::CONTENT_ERRORS] = [];
80
        if (empty($errors) === false) {
81
            $arr[JSONApiInterface::CONTENT_ERRORS] = $errors;
82
        }
83
        // errors and codes must be clear with readable json
84
        $encoded = self::encode($arr, JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT);
85
        if ($return === false && env('APP_ENV') !== 'dev') {
86
            echo $encoded;
87
            exit(JSONApiInterface::EXIT_STATUS_ERROR);
0 ignored issues
show
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...
88
        }
89
        return $encoded;
90
    }
91
92
    /**
93
     *
94
     * @param array $errors
95
     * @return string
96
     */
97
    public function getErrors(array $errors): string
98
    {
99
        $arr[JSONApiInterface::CONTENT_ERRORS] = [];
100
        if (empty($errors) === false) {
101
            $arr[JSONApiInterface::CONTENT_ERRORS] = $errors;
102
        }
103
104
        return self::encode($arr, JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT);
105
    }
106
107
    /**
108
     * Returns composition of relations
109
     *
110
     * @param Request $request
111
     * @param array $data
112
     * @return string
113
     */
114
    public static function prepareSerializedRelations(Request $request, array $data): string
115
    {
116
        $arr[JSONApiInterface::CONTENT_LINKS] = [
117
            JSONApiInterface::CONTENT_SELF => $request->getUri(),
118
        ];
119
120
        $arr[JSONApiInterface::CONTENT_DATA] = $data;
121
122
        return self::encode($arr);
123
    }
124
125
    /**
126
     * @param BaseFormRequest $formRequest
127
     * @param                 $model
128
     * @param string $entity
129
     *
130
     * @return Collection|Item
131
     */
132
    public function getResource(BaseFormRequest $formRequest, $model, string $entity)
133
    {
134
        $transformer = new DefaultTransformer($formRequest);
135
        if ($this->isCollection === true) {
136
            $collection = new Collection($model, $transformer, strtolower($entity));
137
            if (empty($this->meta) === false) {
138
                $collection->setMeta($this->meta);
139
            }
140
141
            if ($model instanceof LengthAwarePaginator) { // only for paginator
142
                $collection->setPaginator(new IlluminatePaginatorAdapter($model));
143
            }
144
145
            return $collection;
146
        }
147
148
        $item = new Item($model, $transformer, strtolower($entity));
149
        $item->setMeta($this->meta);
150
151
        return $item;
152
    }
153
154
    /**
155
     * Prepares data to output in json-api format
156
     *
157
     * @param ResourceInterface $resource
158
     * @param array $data
159
     * @return string
160
     */
161
    public static function prepareSerializedData(ResourceInterface $resource, $data = ModelsInterface::DEFAULT_DATA): string
162
    {
163
        if (empty($resource->getData())) { // preventing 3d party libs (League etc) from crash on empty data
164
            return self::encode([
165
                ModelsInterface::PARAM_DATA => []
166
            ]);
167
        }
168
169
        $manager = new Manager();
170
        if (isset($_GET['include'])) {
171
            $manager->parseIncludes($_GET['include']);
172
        }
173
174
        $manager->setSerializer(new JsonApiSerializer((new Req())->getBasePath()));
175
        return self::getSelectedData($manager->createData($resource)->toJson(), $data);
176
    }
177
178
    /**
179
     * Gets data only with those elements of object/array that should be provided as output
180
     *
181
     * @param string $json
182
     * @param array $data
183
     * @return string
184
     */
185
    private static function getSelectedData(string $json, array $data): string
186
    {
187
        if (current($data) === PhpInterface::ASTERISK) {// do nothing - grab all fields
188
            return $json;
189
        }
190
191
        $jsonArr = self::decode($json);
192
        $current = current($jsonArr[ApiInterface::RAML_DATA]);
193
194
        if (empty($current[JSONApiInterface::CONTENT_ATTRIBUTES]) === false) {// this is an array of values
195
            self::unsetArray($jsonArr, $data);
196
        } else {// this is just one element
197
            self::unsetObject($jsonArr, $data);
198
        }
199
200
        return self::encode($jsonArr);
201
    }
202
203
    /**
204
     * Unsets objects from array that shouldn't be provided as output
205
     *
206
     * @param array &$json
207
     * @param array $data
208
     */
209
    private static function unsetArray(array &$json, array $data): void
210
    {
211
        $attrsCase = ConfigHelper::getParam(ConfigInterface::ATTRIBUTES_CASE);
212
        foreach ($json as $type => &$jsonObject) {
213
214
            if ($type === ApiInterface::RAML_DATA) { // unset only data->attributes fields
215
                foreach ($jsonObject as &$v) {
216
217
                    if (empty($v[JSONApiInterface::CONTENT_ATTRIBUTES]) === false) { // can be any of meta/link
218
                        foreach ($v[JSONApiInterface::CONTENT_ATTRIBUTES] as $key => $attr) {
219
220
                            if (\in_array($key, $data, true) === false) {
221
                                unset($v[JSONApiInterface::CONTENT_ATTRIBUTES][$key]);
222
                            } else if ($attrsCase !== ConfigInterface::DEFAULT_CASE) {
223
                                self::changeParamKey($json, $key, $attr, $attrsCase);
224
                            }
225
                        }
226
                    }
227
                }
228
            }
229
        }
230
    }
231
232
    /**
233
     * Unsets objects that shouldn't be provided as output
234
     *
235
     * @param array $json
236
     * @param array $data
237
     */
238
    private static function unsetObject(array &$json, array $data): void
239
    {
240
        $isDataAndAttrs = empty($json[JSONApiInterface::CONTENT_DATA]) === false
241
            && empty($json[JSONApiInterface::CONTENT_DATA][JSONApiInterface::CONTENT_ATTRIBUTES]) === false;
242
243
        if ($isDataAndAttrs) {
244
            $attrsCase = ConfigHelper::getParam(ConfigInterface::ATTRIBUTES_CASE);
245
            foreach ($json[JSONApiInterface::CONTENT_DATA][JSONApiInterface::CONTENT_ATTRIBUTES] as $k => $v) {
246
                if (\in_array($k, $data, true) === false) {
247
                    unset($json[JSONApiInterface::CONTENT_DATA][JSONApiInterface::CONTENT_ATTRIBUTES][$k]);
248
                } else if ($attrsCase !== ConfigInterface::DEFAULT_CASE) {
249
                    self::changeParamKey($json, $k, $v, $attrsCase);
250
                }
251
            }
252
        }
253
    }
254
255
    /**
256
     * @param array $json
257
     * @param string $k
258
     * @param mixed $v
259
     * @param string $attrsCase
260
     */
261
    public static function changeParamKey(array &$json, string $k, $v, string $attrsCase): void
262
    {
263
        $changedKey = $k;
264
        if ($attrsCase === ConfigInterface::CAMEL_CASE) {
265
            $changedKey = self::changeParamKeyToLowerCamelCase($k);
266
        } else if ($attrsCase === ConfigInterface::LISP_CASE) {
267
            $changedKey = self::changeParamKeyToLispCase($k);
268
        }
269
270
        $json[JSONApiInterface::CONTENT_DATA][JSONApiInterface::CONTENT_ATTRIBUTES][$changedKey] = $v;
271
        if ($changedKey !== $k) {
272
            unset($json[JSONApiInterface::CONTENT_DATA][JSONApiInterface::CONTENT_ATTRIBUTES][$k]);
273
        }
274
    }
275
276
    /**
277
     * @param string $param
278
     * @return string
279
     */
280
    public static function changeParamKeyToLowerCamelCase(string $param): string
281
    {
282
        return lcfirst(
283
            str_replace(' ', '', ucwords(
284
                    str_replace('_', ' ', $param)
285
                )
286
            )
287
        );
288
    }
289
290
    /**
291
     * @param string $param
292
     * @return string
293
     */
294
    public static function changeParamKeyToLispCase(string $param): string
295
    {
296
        return lcfirst(str_replace('_', '-', $param));
297
    }
298
299
    /**
300
     * @param bool $isCollection
301
     * @return Json
302
     */
303
    public function setIsCollection(bool $isCollection): Json
304
    {
305
        $this->isCollection = $isCollection;
306
307
        return $this;
308
    }
309
310
    /**
311
     * @param array $meta
312
     * @return Json
313
     */
314
    public function setMeta(array $meta): Json
315
    {
316
        $this->meta = $meta;
317
318
        return $this;
319
    }
320
}