RequestParser::hasFilter()   A
last analyzed

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