• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

FastyBird / json-api / 10238182751

04 Aug 2024 05:56PM UTC coverage: 4.214% (-0.09%) from 4.302%
10238182751

push

github

web-flow
Merge pull request #2 from FastyBird/feature/constructor-params

Added parameters from entity constructor

0 of 41 new or added lines in 3 files covered. (0.0%)

9 existing lines in 2 files now uncovered.

41 of 973 relevant lines covered (4.21%)

0.13 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.99
/src/Builder/Builder.php
1
<?php declare(strict_types = 1);
2

3
/**
4
 * JsonApi.php
5
 *
6
 * @license        More in LICENSE.md
7
 * @copyright      https://www.fastybird.com
8
 * @author         Adam Kadlec <adam.kadlec@fastybird.com>
9
 * @package        FastyBird:JsonApi!
10
 * @subpackage     Builder
11
 * @since          0.1.0
12
 *
13
 * @date           17.04.19
14
 */
15

16
namespace FastyBird\JsonApi\Builder;
17

18
use FastyBird\JsonApi\JsonApi;
19
use InvalidArgumentException;
20
use Neomerx;
21
use Neomerx\JsonApi\Contracts;
22
use Neomerx\JsonApi\Schema;
23
use Nette\DI;
24
use Psr\Http\Message\ResponseInterface;
25
use Psr\Http\Message\ServerRequestInterface;
26
use Psr\Http\Message\UriInterface;
27
use RuntimeException;
28
use function array_key_exists;
29
use function array_merge;
30
use function call_user_func_array;
31
use function explode;
32
use function http_build_query;
33
use function is_array;
34
use function round;
35
use function str_contains;
36
use function str_replace;
37
use function str_starts_with;
38
use function strval;
39
use const JSON_PRETTY_PRINT;
40

41
/**
42
 * {JSON:API} formatting output handling middleware
43
 *
44
 * @package        FastyBird:JsonApi!
45
 * @subpackage     Builder
46
 *
47
 * @author         Adam Kadlec <adam.kadlec@fastybird.com>
48
 */
49
class Builder
50
{
51

52
        private const LINK_SELF = Contracts\Schema\DocumentInterface::KEYWORD_SELF;
53

54
        private const LINK_RELATED = Contracts\Schema\DocumentInterface::KEYWORD_RELATED;
55

56
        private const LINK_FIRST = Contracts\Schema\DocumentInterface::KEYWORD_FIRST;
57

58
        private const LINK_LAST = Contracts\Schema\DocumentInterface::KEYWORD_LAST;
59

60
        private const LINK_NEXT = Contracts\Schema\DocumentInterface::KEYWORD_NEXT;
61

62
        private const LINK_PREV = Contracts\Schema\DocumentInterface::KEYWORD_PREV;
63

64
        /**
65
         * @param string|array<string> $metaAuthor
66
         */
67
        public function __construct(
68
                private readonly DI\Container $container,
69
                private readonly string|array $metaAuthor,
70
                private readonly string|null $metaCopyright = null,
71
        )
72
        {
73
        }
3✔
74

75
        /**
76
         * @param object|array<object>|null $entity
77
         * @param callable(string): bool $linkValidator
78
         *
79
         * @throws InvalidArgumentException
80
         * @throws RuntimeException
81
         */
82
        public function build(
83
                ServerRequestInterface $request,
84
                ResponseInterface $response,
85
                object|array|null $entity,
86
                int|null $totalCount = null,
87
                callable|null $linkValidator = null,
88
        ): ResponseInterface
89
        {
90
                $encoder = $this->getEncoder();
×
91

92
                $links = [
×
93
                        self::LINK_SELF => new Schema\Link(false, $this->uriToString($request->getUri()), false),
×
94
                ];
×
95

96
                $meta = $this->getBaseMeta();
×
97

98
                if ($totalCount !== null) {
×
99
                        $meta = array_merge($meta, [
×
100
                                'totalCount' => $totalCount,
×
101
                        ]);
×
102

103
                        if (array_key_exists('page', $request->getQueryParams())) {
×
104
                                $queryParams = $request->getQueryParams();
×
105

106
                                $pageOffset = isset($queryParams['page']['offset']) ? (int) $queryParams['page']['offset'] : null;
×
107
                                $pageLimit = isset($queryParams['page']['limit']) ? (int) $queryParams['page']['limit'] : null;
×
108

109
                        } else {
110
                                $pageOffset = null;
×
111
                                $pageLimit = null;
×
112
                        }
113

114
                        if ($pageOffset !== null && $pageLimit !== null && $pageLimit > 0) {
×
115
                                $lastPage = (int) round($totalCount / $pageLimit) * $pageLimit;
×
116

117
                                if ($lastPage === $totalCount) {
×
118
                                        $lastPage = $totalCount - $pageLimit;
×
119
                                }
120

121
                                $uri = $request->getUri();
×
122

123
                                $uriSelf = $uri->withQuery($this->buildPageQuery($pageOffset, $pageLimit));
×
124
                                $uriFirst = $uri->withQuery($this->buildPageQuery(0, $pageLimit));
×
125
                                $uriLast = $uri->withQuery($this->buildPageQuery($lastPage, $pageLimit));
×
126
                                $uriPrev = $uri->withQuery($this->buildPageQuery($pageOffset - $pageLimit, $pageLimit));
×
127
                                $uriNext = $uri->withQuery($this->buildPageQuery($pageOffset + $pageLimit, $pageLimit));
×
128

129
                                $links = array_merge($links, [
×
130
                                        self::LINK_SELF => new Schema\Link(false, $this->uriToString($uriSelf), false),
×
131
                                        self::LINK_FIRST => new Schema\Link(false, $this->uriToString($uriFirst), false),
×
132
                                ]);
×
133

134
                                if ($pageOffset - 1 >= 0) {
×
135
                                        $links = array_merge($links, [
×
136
                                                self::LINK_PREV => new Schema\Link(false, $this->uriToString($uriPrev), false),
×
137
                                        ]);
×
138
                                }
139

140
                                if ($totalCount - $pageLimit - ($pageOffset + $pageLimit) >= 0) {
×
141
                                        $links = array_merge($links, [
×
142
                                                self::LINK_NEXT => new Schema\Link(false, $this->uriToString($uriNext), false),
×
143
                                        ]);
×
144
                                }
145

146
                                $links = array_merge($links, [
×
147
                                        self::LINK_LAST => new Schema\Link(false, $this->uriToString($uriLast), false),
×
148
                                ]);
×
149
                        }
150
                }
151

152
                $encoder->withMeta($meta);
×
153

154
                $encoder->withLinks($links);
×
155

NEW
156
                if (str_contains($request->getUri()->getPath(), '/relationships/')) {
×
157
                        $encodedData = $encoder->encodeDataAsArray($entity);
×
158

159
                        // Try to get "self" link from encoded entity as array
160
                        if (
161
                                array_key_exists('data', $encodedData)
×
162
                                && array_key_exists('links', $encodedData['data'])
×
163
                                && array_key_exists(self::LINK_SELF, $encodedData['data']['links'])
×
164
                        ) {
165
                                $encoder->withLinks(array_merge($links, [
×
166
                                        self::LINK_RELATED => new Schema\Link(
×
167
                                                false,
×
168
                                                strval($encodedData['data']['links'][self::LINK_SELF]),
×
169
                                                false,
×
170
                                        ),
×
171
                                ]));
×
172

173
                        } else {
174
                                if ($linkValidator !== null) {
×
175
                                        $uriRelated = $request->getUri();
×
176

177
                                        $linkRelated = str_replace('/relationships/', '/', $this->uriToString($uriRelated));
×
178

179
                                        $isValid = call_user_func_array($linkValidator, [$linkRelated]);
×
180

181
                                        if ($isValid === true) {
×
182
                                                $encoder->withLinks(array_merge($links, [
×
183
                                                        self::LINK_RELATED => new Schema\Link(false, $linkRelated, false),
×
184
                                                ]));
×
185
                                        }
186
                                }
187
                        }
188

189
                        $content = $encoder->encodeIdentifiers($entity);
×
190

191
                } else {
192
                        if (array_key_exists('include', $request->getQueryParams())) {
×
193
                                $encoder->withIncludedPaths(explode(',', $request->getQueryParams()['include']));
×
194
                        }
195

196
                        $content = $encoder->encodeData($entity);
×
197
                }
198

199
                $response->getBody()->write($content);
×
200

201
                // Setup content type
202
                return $response
×
203
                        // Content headers
×
204
                        ->withHeader('Content-Type', Contracts\Http\Headers\MediaTypeInterface::JSON_API_MEDIA_TYPE);
×
205
        }
206

207
        /**
208
         * @throws DI\MissingServiceException
209
         */
210
        private function getEncoder(): JsonApi\Encoder
211
        {
212
                $encoder = new JsonApi\Encoder(
×
213
                        new Neomerx\JsonApi\Factories\Factory(),
×
214
                        $this->container->getByType(Contracts\Schema\SchemaContainerInterface::class),
×
215
                );
×
216

217
                $encoder->withEncodeOptions(JSON_PRETTY_PRINT);
×
218

219
                $encoder->withJsonApiVersion(Contracts\Encoder\EncoderInterface::JSON_API_VERSION);
×
220

221
                return $encoder;
×
222
        }
223

224
        private function uriToString(UriInterface $uri): string
225
        {
226
                $result = '';
×
227

228
                // Add a leading slash if necessary.
229
                if (!str_starts_with($uri->getPath(), '/')) {
×
230
                        $result .= '/';
×
231
                }
232

233
                $result .= $uri->getPath();
×
234

235
                if ($uri->getQuery() !== '') {
×
236
                        $result .= '?' . $uri->getQuery();
×
237
                }
238

239
                if ($uri->getFragment() !== '') {
×
240
                        $result .= '#' . $uri->getFragment();
×
241
                }
242

243
                return $result;
×
244
        }
245

246
        /**
247
         * @return array<mixed>
248
         */
249
        private function getBaseMeta(): array
250
        {
251
                $meta = [];
×
252

253
                if (is_array($this->metaAuthor)) {
×
254
                        $meta['authors'] = $this->metaAuthor;
×
255

256
                } else {
257
                        $meta['author'] = $this->metaAuthor;
×
258
                }
259

260
                if ($this->metaCopyright !== null) {
×
261
                        $meta['copyright'] = $this->metaCopyright;
×
262
                }
263

264
                return $meta;
×
265
        }
266

267
        private function buildPageQuery(int $offset, int|string $limit): string
268
        {
269
                $query = [
×
270
                        'page' => [
×
271
                                'offset' => $offset,
×
272
                                'limit' => $limit,
×
273
                        ],
×
274
                ];
×
275

276
                return http_build_query($query);
×
277
        }
278

279
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc