Passed
Push — main ( f8d78a...6afd83 )
by Lode
01:12 queued 12s
created

RequestParser::getMeta()   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 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
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 27
	public function __construct($selfLink='', array $queryParameters=[], array $document=[]) {
40 27
		$this->selfLink        = $selfLink;
41 27
		$this->queryParameters = $queryParameters;
42 27
		$this->document        = $document;
43 27
	}
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 3
	public function hasIncludePaths() {
117 3
		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 4
	public function getIncludePaths(array $options=[]) {
130 4
		$includePaths = explode(',', $this->queryParameters['include']);
131
		
132 4
		$options = array_merge(self::$defaults, $options);
133 4
		if ($options['useNestedIncludePaths'] === false) {
134 1
			return $includePaths;
135
		}
136
		
137 3
		$restructured = [];
138 3
		foreach ($includePaths as $path) {
139 3
			$steps = explode('.', $path);
140
			
141 3
			$wrapped = [];
142 3
			while ($steps !== []) {
143 3
				$lastStep = array_pop($steps);
144 3
				$wrapped  = [$lastStep => $wrapped];
145
			}
146
			
147 3
			$restructured = array_merge_recursive($restructured, $wrapped);
148
		}
149
		
150 3
		return $restructured;
151
	}
152
	
153
	/**
154
	 * @param  string $type
155
	 * @return boolean
156
	 */
157 3
	public function hasSparseFieldset($type) {
158 3
		return isset($this->queryParameters['fields'][$type]);
159
	}
160
	
161
	/**
162
	 * @param  string $type
163
	 * @return string[]
164
	 */
165 3
	public function getSparseFieldset($type) {
166 3
		if ($this->queryParameters['fields'][$type] === '') {
167 1
			return [];
168
		}
169
		
170 3
		return explode(',', $this->queryParameters['fields'][$type]);
171
	}
172
	
173
	/**
174
	 * @return boolean
175
	 */
176 4
	public function hasSortFields() {
177 4
		return isset($this->queryParameters['sort']);
178
	}
179
	
180
	/**
181
	 * returns an array with sort order annotations, or the raw sort fields with minus signs
182
	 * 
183
	 * the annotated format allows easier processing of sort orders and field names
184
	 * the raw format allows for custom processing
185
	 * 
186
	 * @todo return some kind of SortFieldObject
187
	 * 
188
	 * @param  array $options optional {@see RequestParser::$defaults}
189
	 * @return string[]|array[] {
190
	 *         @var string $field the sort field, without any minus sign for descending sort order
191
	 *         @var string $order one of the RequestParser::SORT_* constants
192
	 * }
193
	 */
194 5
	public function getSortFields(array $options=[]) {
195 5
		$fields = explode(',', $this->queryParameters['sort']);
196
		
197 5
		$options = array_merge(self::$defaults, $options);
198 5
		if ($options['useAnnotatedSortFields'] === false) {
199 1
			return $fields;
200
		}
201
		
202 4
		$sort = [];
203 4
		foreach ($fields as $field) {
204 4
			$order = RequestParser::SORT_ASCENDING;
205
			
206 4
			if (strpos($field, '-') === 0) {
207 4
				$field = substr($field, 1);
208 4
				$order = RequestParser::SORT_DESCENDING;
209
			}
210
			
211 4
			$sort[] = [
212 4
				'field' => $field,
213 4
				'order' => $order,
214
			];
215
		}
216
		
217 4
		return $sort;
218
	}
219
	
220
	/**
221
	 * @return boolean
222
	 */
223 3
	public function hasPagination() {
224 3
		return isset($this->queryParameters['page']);
225
	}
226
	
227
	/**
228
	 * @todo return some kind of PaginatorObject which recognizes the strategy of pagination used
229
	 *       e.g. page-based, offset-based, cursor-based, or unknown
230
	 * 
231
	 * @return array
232
	 */
233 3
	public function getPagination() {
234 3
		return $this->queryParameters['page'];
235
	}
236
	
237
	/**
238
	 * @return boolean
239
	 */
240 3
	public function hasFilter() {
241 3
		return isset($this->queryParameters['filter']);
242
	}
243
	
244
	/**
245
	 * @return array
246
	 */
247 3
	public function getFilter() {
248 3
		return $this->queryParameters['filter'];
249
	}
250
	
251
	/**
252
	 * @return boolean
253
	 */
254 1
	public function hasLocalId() {
255 1
		return (isset($this->document['data']['lid']));
256
	}
257
	
258
	/**
259
	 * @return string
260
	 */
261 1
	public function getLocalId() {
262 1
		return $this->document['data']['lid'];
263
	}
264
	
265
	/**
266
	 * @param  string $attributeName
267
	 * @return boolean
268
	 */
269 3
	public function hasAttribute($attributeName) {
270 3
		if (isset($this->document['data']['attributes']) === false) {
271 1
			return false;
272
		}
273 3
		if (array_key_exists($attributeName, $this->document['data']['attributes']) === false) {
274 1
			return false;
275
		}
276
		
277 3
		return true;
278
	}
279
	
280
	/**
281
	 * @param  string $attributeName
282
	 * @return mixed
283
	 */
284 3
	public function getAttribute($attributeName) {
285 3
		return $this->document['data']['attributes'][$attributeName];
286
	}
287
	
288
	/**
289
	 * @param  string $relationshipName
290
	 * @return boolean
291
	 */
292 3
	public function hasRelationship($relationshipName) {
293 3
		if (isset($this->document['data']['relationships']) === false) {
294 1
			return false;
295
		}
296 3
		if (array_key_exists($relationshipName, $this->document['data']['relationships']) === false) {
297 1
			return false;
298
		}
299
		
300 3
		return true;
301
	}
302
	
303
	/**
304
	 * @todo return some kind of read-only ResourceIdentifierObject
305
	 * 
306
	 * @param  string $relationshipName
307
	 * @return array
308
	 */
309 3
	public function getRelationship($relationshipName) {
310 3
		return $this->document['data']['relationships'][$relationshipName];
311
	}
312
	
313
	/**
314
	 * @param  string $metaKey
315
	 * @return boolean
316
	 */
317 3
	public function hasMeta($metaKey) {
318 3
		if (isset($this->document['meta']) === false) {
319 1
			return false;
320
		}
321 3
		if (array_key_exists($metaKey, $this->document['meta']) === false) {
322 1
			return false;
323
		}
324
		
325 3
		return true;
326
	}
327
	
328
	/**
329
	 * @param  string $metaKey
330
	 * @return mixed
331
	 */
332 3
	public function getMeta($metaKey) {
333 3
		return $this->document['meta'][$metaKey];
334
	}
335
	
336
	/**
337
	 * @return array
338
	 */
339 7
	public function getDocument() {
340 7
		return $this->document;
341
	}
342
}
343