Passed
Pull Request — main (#63)
by Lode
17:39
created

RequestParser::hasQueryParameters()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 1
c 1
b 0
f 1
nc 1
nop 0
dl 0
loc 2
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
namespace alsvanzelf\jsonapi\helpers;
4
5
use alsvanzelf\jsonapi\Document;
6
use Psr\Http\Message\RequestInterface;
7
use Psr\Http\Message\ServerRequestInterface;
8
9
class RequestParser {
10
	const SORT_ASCENDING  = 'ascending';
11
	const SORT_DESCENDING = 'descending';
12
	
13
	/** @var array */
14
	protected static $defaults = [
15
		/**
16
		 * reformat the include query parameter paths to nested arrays
17
		 * this allows easier processing on each step of the chain
18
		 */
19
		'useNestedIncludePaths' => true,
20
		
21
		/**
22
		 * reformat the sort query parameter paths to separate the sort order
23
		 * this allows easier processing of sort orders and field names
24
		 */
25
		'useAnnotatedSortFields' => true,
26
	];
27
	/** @var string */
28
	protected $selfLink = '';
29
	/** @var array */
30
	protected $queryParameters = [];
31
	/** @var array */
32
	protected $document = [];
33
	
34
	/**
35
	 * @param string $selfLink        the uri used to make this request {@see getSelfLink()}
36
	 * @param array  $queryParameters all query parameters defined by the specification
37
	 * @param array  $document        the request jsonapi document
38
	 */
39 32
	public function __construct($selfLink='', array $queryParameters=[], array $document=[]) {
40 32
		$this->selfLink        = $selfLink;
41 32
		$this->queryParameters = $queryParameters;
42 32
		$this->document        = $document;
43 32
	}
44
	
45
	/**
46
	 * @return self
47
	 */
48 4
	public static function fromSuperglobals() {
49 4
		$selfLink = '';
50
		
51 4
		if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
52 1
			$selfLink .= $_SERVER['HTTP_X_FORWARDED_PROTO'].'://';
53
		}
54 3
		elseif (isset($_SERVER['REQUEST_SCHEME'])) {
55 2
			$selfLink .= $_SERVER['REQUEST_SCHEME'].'://';
56
		}
57
		else {
58 1
			$selfLink .= 'http://';
59
		}
60
		
61 4
		if (isset($_SERVER['HTTP_HOST'])) {
62 2
			$selfLink .= $_SERVER['HTTP_HOST'];
63
		}
64 2
		elseif (isset($_SERVER['SCRIPT_URI'])) {
65 1
			$startOfDomain  = strpos($_SERVER['SCRIPT_URI'], '://') + strlen('://');
66 1
			$endOfDomain    = strpos($_SERVER['SCRIPT_URI'], '/', $startOfDomain);
67 1
			$lengthOfDomain = ($endOfDomain - $startOfDomain);
68 1
			$selfLink      .= substr($_SERVER['SCRIPT_URI'], $startOfDomain, $lengthOfDomain);
69
		}
70
		
71 4
		if (isset($_SERVER['REQUEST_URI'])) {
72 2
			$selfLink .= $_SERVER['REQUEST_URI'];
73
		}
74 2
		elseif (isset($_SERVER['PATH_INFO']) && isset($_SERVER['QUERY_STRING'])) {
75 1
			$selfLink .= $_SERVER['PATH_INFO'];
76 1
			$selfLink .= ($_SERVER['QUERY_STRING'] !== '') ? '?'.$_SERVER['QUERY_STRING'] : '';
77
		}
78
		
79 4
		$queryParameters = $_GET;
80
		
81 4
		$document = $_POST;
82 4
		if ($document === [] && isset($_SERVER['CONTENT_TYPE'])) {
83 1
			$documentIsJsonapi = (strpos($_SERVER['CONTENT_TYPE'], Document::CONTENT_TYPE_OFFICIAL) !== false);
84 1
			$documentIsJson    = (strpos($_SERVER['CONTENT_TYPE'], Document::CONTENT_TYPE_DEBUG)    !== false);
85
			
86 1
			if ($documentIsJsonapi || $documentIsJson) {
87 1
				$document = json_decode(file_get_contents('php://input'), true);
88
				
89 1
				if ($document === null) {
90 1
					$document = [];
91
				}
92
			}
93
		}
94
		
95 4
		return new self($selfLink, $queryParameters, $document);
96
	}
97
	
98
	/**
99
	 * @param  ServerRequestInterface|RequestInterface $request
100
	 * @return self
101
	 */
102 3
	public static function fromPsrRequest(RequestInterface $request) {
103 3
		$selfLink = (string) $request->getUri();
104
		
105 3
		if ($request instanceof ServerRequestInterface) {
106 1
			$queryParameters = $request->getQueryParams();
107
		}
108
		else {
109 2
			$queryParameters = [];
110 2
			parse_str($request->getUri()->getQuery(), $queryParameters);
111
		}
112
		
113 3
		if ($request->getBody()->getContents() === '') {
114 1
			$document = [];
115
		}
116
		else {
117 2
			$document = json_decode($request->getBody()->getContents(), true);
118
			
119 2
			if ($document === null) {
120
				$document = [];
121
			}
122
		}
123
		
124 3
		return new self($selfLink, $queryParameters, $document);
125
	}
126
	
127
	/**
128
	 * the full link used to make this request
129
	 * 
130
	 * this is not a bare self link of a resource and includes query parameters if used
131
	 * 
132
	 * @return string
133
	 */
134 5
	public function getSelfLink() {
135 5
		return $this->selfLink;
136
	}
137
	
138
	/**
139
	 * @return boolean
140
	 */
141 3
	public function hasIncludePaths() {
142 3
		return isset($this->queryParameters['include']);
143
	}
144
	
145
	/**
146
	 * returns a nested array based on the path, or the raw paths
147
	 * 
148
	 * the nested format allows easier processing on each step of the chain
149
	 * the raw format allows for custom processing
150
	 * 
151
	 * @param  array $options optional {@see RequestParser::$defaults}
152
	 * @return string[]|array
153
	 */
154 4
	public function getIncludePaths(array $options=[]) {
155 4
		$includePaths = explode(',', $this->queryParameters['include']);
156
		
157 4
		$options = array_merge(self::$defaults, $options);
158 4
		if ($options['useNestedIncludePaths'] === false) {
159 1
			return $includePaths;
160
		}
161
		
162 3
		$restructured = [];
163 3
		foreach ($includePaths as $path) {
164 3
			$steps = explode('.', $path);
165
			
166 3
			$wrapped = [];
167 3
			while ($steps !== []) {
168 3
				$lastStep = array_pop($steps);
169 3
				$wrapped  = [$lastStep => $wrapped];
170
			}
171
			
172 3
			$restructured = array_merge_recursive($restructured, $wrapped);
173
		}
174
		
175 3
		return $restructured;
176
	}
177
	
178
	/**
179
	 * @return boolean
180
	 */
181 3
	public function hasAnySparseFieldset() {
182 3
		return (isset($this->queryParameters['fields']));
183
	}
184
	
185
	/**
186
	 * @param  string $type
187
	 * @return boolean
188
	 */
189 3
	public function hasSparseFieldset($type) {
190 3
		return isset($this->queryParameters['fields'][$type]);
191
	}
192
	
193
	/**
194
	 * @param  string $type
195
	 * @return string[]
196
	 */
197 3
	public function getSparseFieldset($type) {
198 3
		if ($this->queryParameters['fields'][$type] === '') {
199 1
			return [];
200
		}
201
		
202 3
		return explode(',', $this->queryParameters['fields'][$type]);
203
	}
204
	
205
	/**
206
	 * @return boolean
207
	 */
208 4
	public function hasSortFields() {
209 4
		return isset($this->queryParameters['sort']);
210
	}
211
	
212
	/**
213
	 * returns an array with sort order annotations, or the raw sort fields with minus signs
214
	 * 
215
	 * the annotated format allows easier processing of sort orders and field names
216
	 * the raw format allows for custom processing
217
	 * 
218
	 * @todo return some kind of SortFieldObject
219
	 * 
220
	 * @param  array $options optional {@see RequestParser::$defaults}
221
	 * @return string[]|array[] {
222
	 *         @var string $field the sort field, without any minus sign for descending sort order
223
	 *         @var string $order one of the RequestParser::SORT_* constants
224
	 * }
225
	 */
226 5
	public function getSortFields(array $options=[]) {
227 5
		$fields = explode(',', $this->queryParameters['sort']);
228
		
229 5
		$options = array_merge(self::$defaults, $options);
230 5
		if ($options['useAnnotatedSortFields'] === false) {
231 1
			return $fields;
232
		}
233
		
234 4
		$sort = [];
235 4
		foreach ($fields as $field) {
236 4
			$order = RequestParser::SORT_ASCENDING;
237
			
238 4
			if (strpos($field, '-') === 0) {
239 4
				$field = substr($field, 1);
240 4
				$order = RequestParser::SORT_DESCENDING;
241
			}
242
			
243 4
			$sort[] = [
244 4
				'field' => $field,
245 4
				'order' => $order,
246
			];
247
		}
248
		
249 4
		return $sort;
250
	}
251
	
252
	/**
253
	 * @return boolean
254
	 */
255 3
	public function hasPagination() {
256 3
		return isset($this->queryParameters['page']);
257
	}
258
	
259
	/**
260
	 * @todo return some kind of PaginatorObject which recognizes the strategy of pagination used
261
	 *       e.g. page-based, offset-based, cursor-based, or unknown
262
	 * 
263
	 * @return array
264
	 */
265 3
	public function getPagination() {
266 3
		return $this->queryParameters['page'];
267
	}
268
	
269
	/**
270
	 * @return boolean
271
	 */
272 3
	public function hasFilter() {
273 3
		return isset($this->queryParameters['filter']);
274
	}
275
	
276
	/**
277
	 * @return array
278
	 */
279 3
	public function getFilter() {
280 3
		return $this->queryParameters['filter'];
281
	}
282
	
283
	/**
284
	 * @return boolean
285
	 */
286 1
	public function hasLocalId() {
287 1
		return (isset($this->document['data']['lid']));
288
	}
289
	
290
	/**
291
	 * @return string
292
	 */
293 1
	public function getLocalId() {
294 1
		return $this->document['data']['lid'];
295
	}
296
	
297
	/**
298
	 * @param  string $attributeName
299
	 * @return boolean
300
	 */
301 3
	public function hasAttribute($attributeName) {
302 3
		if (isset($this->document['data']['attributes']) === false) {
303 1
			return false;
304
		}
305 3
		if (array_key_exists($attributeName, $this->document['data']['attributes']) === false) {
306 1
			return false;
307
		}
308
		
309 3
		return true;
310
	}
311
	
312
	/**
313
	 * @param  string $attributeName
314
	 * @return mixed
315
	 */
316 3
	public function getAttribute($attributeName) {
317 3
		return $this->document['data']['attributes'][$attributeName];
318
	}
319
	
320
	/**
321
	 * @param  string $relationshipName
322
	 * @return boolean
323
	 */
324 3
	public function hasRelationship($relationshipName) {
325 3
		if (isset($this->document['data']['relationships']) === false) {
326 1
			return false;
327
		}
328 3
		if (array_key_exists($relationshipName, $this->document['data']['relationships']) === false) {
329 1
			return false;
330
		}
331
		
332 3
		return true;
333
	}
334
	
335
	/**
336
	 * @todo return some kind of read-only ResourceIdentifierObject
337
	 * 
338
	 * @param  string $relationshipName
339
	 * @return array
340
	 */
341 3
	public function getRelationship($relationshipName) {
342 3
		return $this->document['data']['relationships'][$relationshipName];
343
	}
344
	
345
	/**
346
	 * @param  string $metaKey
347
	 * @return boolean
348
	 */
349 3
	public function hasMeta($metaKey) {
350 3
		if (isset($this->document['meta']) === false) {
351 1
			return false;
352
		}
353 3
		if (array_key_exists($metaKey, $this->document['meta']) === false) {
354 1
			return false;
355
		}
356
		
357 3
		return true;
358
	}
359
	
360
	/**
361
	 * @param  string $metaKey
362
	 * @return mixed
363
	 */
364 3
	public function getMeta($metaKey) {
365 3
		return $this->document['meta'][$metaKey];
366
	}
367
	
368
	/**
369
	 * @return boolean
370
	 */
371 3
	public function hasQueryParameters() {
372 3
		return ($this->queryParameters !== []);
373
	}
374
	
375
	/**
376
	 * @return array
377
	 */
378 3
	public function getQueryParameters() {
379 3
		return $this->queryParameters;
380
	}
381
	
382
	/**
383
	 * @return boolean
384
	 */
385 3
	public function hasDocument() {
386 3
		return ($this->document !== []);
387
	}
388
	
389
	/**
390
	 * @return array
391
	 */
392 7
	public function getDocument() {
393 7
		return $this->document;
394
	}
395
}
396