1
|
|
|
<?php |
2
|
|
|
/* |
3
|
|
|
* This file is part of the reva2/jsonapi. |
4
|
|
|
* |
5
|
|
|
* (c) Sergey Revenko <[email protected]> |
6
|
|
|
* |
7
|
|
|
* For the full copyright and license information, please view the LICENSE |
8
|
|
|
* file that was distributed with this source code. |
9
|
|
|
*/ |
10
|
|
|
|
11
|
|
|
|
12
|
|
|
namespace Reva2\JsonApi\Http\Query; |
13
|
|
|
|
14
|
|
|
use Neomerx\JsonApi\Contracts\Encoder\Parameters\EncodingParametersInterface; |
15
|
|
|
use Symfony\Component\Validator\Context\ExecutionContextInterface; |
16
|
|
|
use Reva2\JsonApi\Annotations as API; |
17
|
|
|
use Symfony\Component\Validator\Constraints as Assert; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* JSON API single resource query parameters |
21
|
|
|
* |
22
|
|
|
* @package Reva2\JsonApi\Http\Query |
23
|
|
|
* @author Sergey Revenko <[email protected]> |
24
|
|
|
*/ |
25
|
|
|
class QueryParameters implements EncodingParametersInterface |
26
|
|
|
{ |
27
|
|
|
const INVALID_INCLUDE_PATHS = '9f4922b8-8e8b-4847-baf2-5831adfd6813'; |
28
|
|
|
const INVALID_FIELD_SET = 'ec7d2c6b-97d1-4f94-ba94-d141d985fc6f'; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @var string[]|null |
32
|
|
|
* @API\Property(path="[include]", parser="parseIncludePaths") |
33
|
|
|
* @Assert\Type(type="array") |
34
|
|
|
* @Assert\All({ |
35
|
|
|
* @Assert\Type(type="string") |
36
|
|
|
* }) |
37
|
|
|
*/ |
38
|
|
|
protected $includePaths; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @var array[]|null |
42
|
|
|
* @API\Property(path="[fields]", parser="parseFieldSets") |
43
|
|
|
* @Assert\Type(type="array") |
44
|
|
|
* @Assert\All({ |
45
|
|
|
* @Assert\Type(type="array") |
46
|
|
|
* }) |
47
|
|
|
*/ |
48
|
|
|
protected $fieldSets; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @inheritdoc |
52
|
|
|
*/ |
53
|
4 |
|
public function getIncludePaths() |
54
|
|
|
{ |
55
|
4 |
|
return $this->includePaths; |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Sets include paths |
60
|
|
|
* |
61
|
|
|
* @param string[]|null $paths |
62
|
|
|
* @return $this |
63
|
|
|
*/ |
64
|
5 |
|
public function setIncludePaths(array $paths = null) |
65
|
|
|
{ |
66
|
5 |
|
$this->includePaths = $paths; |
67
|
|
|
|
68
|
5 |
|
return $this; |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* @inheritdoc |
73
|
|
|
*/ |
74
|
2 |
|
public function getFieldSets() |
75
|
|
|
{ |
76
|
2 |
|
return $this->fieldSets; |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* @inheritdoc |
81
|
|
|
*/ |
82
|
2 |
|
public function getFieldSet($type) |
83
|
|
|
{ |
84
|
2 |
|
return (isset($this->fieldSets[$type])) ? $this->fieldSets[$type] : null; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* @param array[]|null $fieldSets |
89
|
|
|
* @return $this |
90
|
|
|
*/ |
91
|
4 |
|
public function setFieldSets(array $fieldSets = null) |
92
|
|
|
{ |
93
|
4 |
|
$this->fieldSets = $fieldSets; |
94
|
|
|
|
95
|
4 |
|
return $this; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* @inheritdoc |
100
|
|
|
*/ |
101
|
1 |
|
public function getSortParameters() |
102
|
|
|
{ |
103
|
1 |
|
return null; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* @inheritdoc |
108
|
|
|
*/ |
109
|
1 |
|
public function getPaginationParameters() |
110
|
|
|
{ |
111
|
1 |
|
return null; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* @inheritdoc |
116
|
|
|
*/ |
117
|
1 |
|
public function getFilteringParameters() |
118
|
|
|
{ |
119
|
1 |
|
return null; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* @inheritdoc |
124
|
|
|
*/ |
125
|
1 |
|
public function getUnrecognizedParameters() |
126
|
|
|
{ |
127
|
1 |
|
return null; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* @inheritdoc |
132
|
|
|
*/ |
133
|
1 |
|
public function isEmpty() |
134
|
|
|
{ |
135
|
1 |
|
if (empty($this->getIncludePaths()) && |
136
|
1 |
|
empty($this->getFieldSets()) && |
137
|
1 |
|
empty($this->getSortParameters()) && |
138
|
1 |
|
empty($this->getPaginationParameters()) && |
139
|
1 |
|
empty($this->getFilteringParameters()) |
140
|
1 |
|
) { |
141
|
1 |
|
return true; |
142
|
|
|
} |
143
|
|
|
|
144
|
1 |
|
return false; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* Parse value of parameter that store include paths |
149
|
|
|
* which should be included into response |
150
|
|
|
* |
151
|
|
|
* @param string|null $value |
152
|
|
|
* @return array|null |
153
|
|
|
*/ |
154
|
4 |
|
public function parseIncludePaths($value = null) { |
155
|
4 |
|
if (empty($value)) { |
156
|
1 |
|
return null; |
157
|
|
|
} |
158
|
|
|
|
159
|
4 |
|
if (!is_string($value)) { |
160
|
1 |
|
throw new \InvalidArgumentException('Include paths value must be a string', 400); |
161
|
|
|
} |
162
|
|
|
|
163
|
3 |
|
return explode(',', $value); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* Parse value of parameter that store fields which |
168
|
|
|
* should be included into response |
169
|
|
|
* |
170
|
|
|
* @param array|null $value |
171
|
|
|
* @return array|null |
172
|
|
|
*/ |
173
|
3 |
|
public function parseFieldSets($value = null) { |
174
|
3 |
|
if (empty($value)) { |
175
|
1 |
|
return null; |
176
|
|
|
} |
177
|
|
|
|
178
|
3 |
|
if (!is_array($value)) { |
179
|
1 |
|
throw new \InvalidArgumentException('Field sets value must be an array', 400); |
180
|
|
|
} |
181
|
|
|
|
182
|
2 |
|
foreach ($value as $resource => $fields) { |
183
|
2 |
|
$value[$resource] = explode(',', $fields); |
184
|
2 |
|
} |
185
|
|
|
|
186
|
2 |
|
return $value; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* Validate specified include paths |
191
|
|
|
* |
192
|
|
|
* @param ExecutionContextInterface $context |
193
|
|
|
* @Assert\Callback() |
194
|
|
|
*/ |
195
|
1 |
|
public function validateIncludePaths(ExecutionContextInterface $context) |
196
|
|
|
{ |
197
|
1 |
|
if (!is_array($this->includePaths)) { |
198
|
1 |
|
return; |
199
|
|
|
} |
200
|
|
|
|
201
|
1 |
|
$invalidPaths = array_diff($this->includePaths, $this->getAllowedIncludePaths()); |
202
|
1 |
|
if (count($invalidPaths) > 0) { |
203
|
|
|
$context |
204
|
1 |
|
->buildViolation('Invalid include paths: %paths%') |
205
|
1 |
|
->setParameter('%paths%', sprintf("'%s'", implode("', '", $invalidPaths))) |
206
|
1 |
|
->setPlural(count($invalidPaths)) |
207
|
1 |
|
->setInvalidValue($invalidPaths) |
208
|
1 |
|
->setCode(self::INVALID_INCLUDE_PATHS) |
209
|
1 |
|
->atPath('includePaths') |
210
|
1 |
|
->addViolation(); |
211
|
1 |
|
} |
212
|
1 |
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* Validate specified fields sets |
216
|
|
|
* |
217
|
|
|
* @param ExecutionContextInterface $context |
218
|
|
|
* @Assert\Callback() |
219
|
|
|
*/ |
220
|
1 |
|
public function validateFieldSets(ExecutionContextInterface $context) |
221
|
|
|
{ |
222
|
1 |
|
if (!is_array($this->fieldSets)) { |
223
|
1 |
|
return; |
224
|
|
|
} |
225
|
|
|
|
226
|
1 |
|
foreach ($this->fieldSets as $resource => $fields) { |
227
|
1 |
|
$invalidFields = array_diff($fields, $this->getAllowedFields($resource)); |
228
|
|
|
|
229
|
1 |
|
if (count($invalidFields) > 0) { |
|
|
|
|
230
|
1 |
|
$this->addViolation( |
231
|
1 |
|
$context, |
232
|
1 |
|
'Invalid fields: %fields%', |
233
|
1 |
|
['%fields%' => sprintf("'%s'", implode("', '", $invalidFields))], |
234
|
1 |
|
$invalidFields, |
235
|
1 |
|
self::INVALID_FIELD_SET, |
236
|
1 |
|
'fieldSets.' . $resource, |
237
|
1 |
|
count($invalidFields) |
238
|
1 |
|
); |
239
|
1 |
|
} |
240
|
1 |
|
} |
241
|
1 |
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* Returns list of allowed include paths |
245
|
|
|
* |
246
|
|
|
* @return string[] |
247
|
|
|
*/ |
248
|
1 |
|
protected function getAllowedIncludePaths() |
249
|
|
|
{ |
250
|
1 |
|
return []; |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* Returns list of fields available in specified resource |
255
|
|
|
* |
256
|
|
|
* @param string $resource |
257
|
|
|
* @return array[] |
258
|
|
|
*/ |
259
|
1 |
|
protected function getAllowedFields($resource) |
260
|
|
|
{ |
261
|
|
|
switch ($resource) { |
262
|
1 |
|
default: |
|
|
|
|
263
|
1 |
|
return []; |
264
|
1 |
|
} |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
/** |
268
|
|
|
* Add specified violation |
269
|
|
|
* |
270
|
|
|
* @param ExecutionContextInterface $context |
271
|
|
|
* @param string $message |
272
|
|
|
* @param array $params |
273
|
|
|
* @param int $plural |
274
|
|
|
* @param mixed $invalidValue |
275
|
|
|
* @param string $code |
276
|
|
|
* @param string $path |
277
|
|
|
*/ |
278
|
3 |
|
protected function addViolation( |
279
|
|
|
ExecutionContextInterface $context, |
280
|
|
|
$message, |
281
|
|
|
array $params, |
282
|
|
|
$invalidValue, |
283
|
|
|
$code, |
284
|
|
|
$path, |
285
|
|
|
$plural = null |
286
|
|
|
) { |
287
|
|
|
$builder = $context |
288
|
3 |
|
->buildViolation($message) |
289
|
3 |
|
->setParameters($params) |
290
|
3 |
|
->setInvalidValue($invalidValue) |
291
|
3 |
|
->setCode($code) |
292
|
3 |
|
->atPath($path); |
293
|
|
|
|
294
|
3 |
|
if (null !== $plural) { |
295
|
2 |
|
$builder->setPlural($plural); |
296
|
2 |
|
} |
297
|
|
|
|
298
|
3 |
|
$builder->addViolation(); |
299
|
3 |
|
} |
300
|
|
|
} |
301
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.