Passed
Pull Request — main (#66)
by Lode
04:07 queued 31s
created

ErrorObject::addMeta()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 2
rs 10
1
<?php
2
3
namespace alsvanzelf\jsonapi\objects;
4
5
use alsvanzelf\jsonapi\Document;
6
use alsvanzelf\jsonapi\exceptions\InputException;
7
use alsvanzelf\jsonapi\helpers\AtMemberManager;
8
use alsvanzelf\jsonapi\helpers\Converter;
9
use alsvanzelf\jsonapi\helpers\ExtensionMemberManager;
10
use alsvanzelf\jsonapi\helpers\HttpStatusCodeManager;
11
use alsvanzelf\jsonapi\helpers\LinksManager;
12
use alsvanzelf\jsonapi\helpers\Validator;
13
use alsvanzelf\jsonapi\interfaces\ObjectInterface;
14
15
class ErrorObject implements ObjectInterface {
16
	use AtMemberManager, ExtensionMemberManager, HttpStatusCodeManager, LinksManager;
17
	
18
	/** @var string */
19
	protected $id;
20
	/** @var string */
21
	protected $code;
22
	/** @var string */
23
	protected $title;
24
	/** @var string */
25
	protected $detail;
26
	/** @var array */
27
	protected $source = [];
28
	/** @var MetaObject */
29
	protected $meta;
30
	/** @var array */
31
	protected static $defaults = [
32
		/**
33
		 * add the trace of exceptions when adding exceptions
34
		 * in some cases it might be handy to disable if traces are too big
35
		 */
36
		'includeExceptionTrace' => true,
37
		
38
		/**
39
		 * strip a base path from exception file and trace paths
40
		 * set this to the applications root to have more readable exception responses
41
		 */
42
		'stripExceptionBasePath' => null,
43
	];
44
	
45
	/**
46
	 * @param string|int $genericCode       developer-friendly code of the generic type of error
47
	 * @param string     $genericTitle      human-friendly title of the generic type of error
48
	 * @param string     $specificDetails   optional, human-friendly explanation of the specific error
49
	 * @param string     $specificAboutLink optional, human-friendly explanation of the specific error
50
	 * @param string     $genericTypeLink   optional, human-friendly explanation of the generic type of error
51
	 */
52 20
	public function __construct($genericCode=null, $genericTitle=null, $specificDetails=null, $specificAboutLink=null, $genericTypeLink=null) {
53 20
		if ($genericCode !== null) {
54 7
			$this->setApplicationCode($genericCode);
55
		}
56 20
		if ($genericTitle !== null) {
57 2
			$this->setHumanExplanation($genericTitle, $specificDetails, $specificAboutLink, $genericTypeLink);
58
		}
59 20
	}
60
	
61
	/**
62
	 * human api
63
	 */
64
	
65
	/**
66
	 * @param  \Exception|\Throwable $exception
67
	 * @param  array                 $options   optional {@see ErrorObject::$defaults}
68
	 * @return ErrorObject
69
	 * 
70
	 * @throws InputException if $exception is not \Exception or \Throwable
71
	 */
72 12
	public static function fromException($exception, array $options=[]) {
73 12
		if ($exception instanceof \Exception === false && $exception instanceof \Throwable === false) {
74 1
			throw new InputException('input is not a real exception in php5 or php7');
75
		}
76
		
77 11
		$options = array_merge(self::$defaults, $options);
78
		
79 11
		$errorObject = new self();
80
		
81 11
		$className = get_class($exception);
82 11
		if (strpos($className, '\\')) {
83 2
			$exploded  = explode('\\', $className);
84 2
			$className = end($exploded);
85
		}
86 11
		$errorObject->setApplicationCode(Converter::camelCaseToWords($className));
87
		
88 11
		$filePath = $exception->getFile();
89 11
		if ($options['stripExceptionBasePath'] !== null) {
90 3
			$filePath = str_replace($options['stripExceptionBasePath'], '', $filePath);
91
		}
92
		
93 11
		$metaObject = MetaObject::fromArray([
94 11
			'type'    => get_class($exception),
95 11
			'message' => $exception->getMessage(),
96 11
			'code'    => $exception->getCode(),
97 11
			'file'    => $filePath,
98 11
			'line'    => $exception->getLine(),
99
		]);
100
		
101 11
		if ($options['includeExceptionTrace']) {
102 8
			$trace = $exception->getTrace();
103 8
			if ($options['stripExceptionBasePath'] !== null) {
104 1
				foreach ($trace as &$traceElement) {
105 1
					if (isset($traceElement['file'])) {
106 1
						$traceElement['file'] = str_replace($options['stripExceptionBasePath'], '', $traceElement['file']);
107
					}
108
				}
109
			}
110
			
111 8
			$metaObject->add('trace', $trace);
112
		}
113
		
114 11
		$errorObject->setMetaObject($metaObject);
115
		
116 11
		if (Validator::checkHttpStatusCode($exception->getCode())) {
117 3
			$errorObject->setHttpStatusCode($exception->getCode());
118
		}
119
		
120 11
		return $errorObject;
121
	}
122
	
123
	/**
124
	 * explain this particular occurence of the error in a human-friendly way
125
	 * 
126
	 * @param string $genericTitle      title of the generic type of error
127
	 * @param string $specificDetails   optional, explanation of the specific error
128
	 * @param string $specificAboutLink optional, explanation of the specific error
129
	 * @param string $genericTypeLink   optional, explanation of the generic type of error
130
	 */
131 6
	public function setHumanExplanation($genericTitle, $specificDetails=null, $specificAboutLink=null, $genericTypeLink=null) {
132 6
		$this->setHumanTitle($genericTitle);
133
		
134 6
		if ($specificDetails !== null) {
135 6
			$this->setHumanDetails($specificDetails);
136
		}
137 6
		if ($specificAboutLink !== null) {
138 1
			$this->setAboutLink($specificAboutLink);
139
		}
140 6
		if ($genericTypeLink !== null) {
141 1
			$this->appendTypeLink($genericTypeLink);
142
		}
143 6
	}
144
	
145
	/**
146
	 * set the link about this specific occurence of the error, explained in a human-friendly way
147
	 * 
148
	 * @param string $href
149
	 * @param array  $meta optional, if given a LinkObject is added, otherwise a link string is added
150
	 */
151 1
	public function setAboutLink($href, array $meta=[]) {
152 1
		$this->addLink('about', $href, $meta);
153 1
	}
154
	
155
	/**
156
	 * append a link of the generic type of this error, explained in a human-friendly way
157
	 * 
158
	 * @param string $href
159
	 * @param array  $meta optional, if given a LinkObject is added, otherwise a link string is added
160
	 */
161 5
	public function appendTypeLink($href, array $meta=[]) {
162 5
		$this->appendLink('type', $href, $meta);
163 5
	}
164
	
165
	/**
166
	 * blame the json pointer from the request body causing this error
167
	 * 
168
	 * @see https://tools.ietf.org/html/rfc6901
169
	 * 
170
	 * @param  string $pointer e.g. "/data/attributes/title" or "/data"
171
	 */
172 1
	public function blameJsonPointer($pointer) {
173 1
		$this->addSource('pointer', $pointer);
174 1
	}
175
	
176
	/**
177
	 * blame the query parameter from the request causing this error
178
	 * 
179
	 * @param  string $parameter
180
	 */
181 4
	public function blameQueryParameter($parameter) {
182 4
		$this->addSource('parameter', $parameter);
183 4
	}
184
	
185
	/**
186
	 * @param string $key
187
	 * @param mixed  $value
188
	 */
189 3
	public function addMeta($key, $value) {
190 3
		if ($this->meta === null) {
191 3
			$this->setMetaObject(new MetaObject());
192
		}
193
		
194 3
		$this->meta->add($key, $value);
195 3
	}
196
	
197
	/**
198
	 * spec api
199
	 */
200
	
201
	/**
202
	 * a unique identifier for this specific occurrence of the error
203
	 * 
204
	 * @param string|int $id
205
	 */
206 2
	public function setUniqueIdentifier($id) {
207 2
		$this->id = $id;
208 2
	}
209
	
210
	/**
211
	 * a code expressing the generic type of this error
212
	 * it should be application-specific and aimed at developers
213
	 * 
214
	 * @param string|int $genericCode will be casted to a string
215
	 */
216 18
	public function setApplicationCode($genericCode) {
217 18
		$this->code = (string) $genericCode;
218 18
	}
219
	
220
	/**
221
	 * add the source of the error
222
	 * 
223
	 * @param string $key   {@see ->blameJsonPointer(), ->blameQueryParameter()}
224
	 * @param string $value
225
	 */
226 6
	public function addSource($key, $value) {
227 6
		Validator::checkMemberName($key);
228
		
229 6
		$this->source[$key] = $value;
230 6
	}
231
	
232
	/**
233
	 * a short human-friendly explanation of the generic type of this error
234
	 * 
235
	 * @param string $genericTitle
236
	 */
237 7
	public function setHumanTitle($genericTitle) {
238 7
		$this->title = $genericTitle;
239 7
	}
240
	
241
	/**
242
	 * a human-friendly explanation of this specific occurrence of the error
243
	 * 
244
	 * @param string $specificDetails
245
	 */
246 7
	public function setHumanDetails($specificDetails) {
247 7
		$this->detail = $specificDetails;
248 7
	}
249
	
250
	/**
251
	 * @param MetaObject $metaObject
252
	 */
253 14
	public function setMetaObject(MetaObject $metaObject) {
254 14
		$this->meta = $metaObject;
255 14
	}
256
	
257
	/**
258
	 * ObjectInterface
259
	 */
260
	
261
	/**
262
	 * @inheritDoc
263
	 */
264 9
	public function isEmpty() {
265 9
		if ($this->id !== null) {
266 2
			return false;
267
		}
268 9
		if ($this->hasHttpStatusCode()) {
269 3
			return false;
270
		}
271 8
		if ($this->code !== null) {
272 8
			return false;
273
		}
274 2
		if ($this->title !== null) {
275 1
			return false;
276
		}
277 2
		if ($this->detail !== null) {
278 1
			return false;
279
		}
280 2
		if ($this->links !== null && $this->links->isEmpty() === false) {
281 1
			return false;
282
		}
283 2
		if ($this->source !== []) {
284 1
			return false;
285
		}
286 2
		if ($this->meta !== null && $this->meta->isEmpty() === false) {
287 1
			return false;
288
		}
289 2
		if ($this->hasAtMembers()) {
290 1
			return false;
291
		}
292 2
		if ($this->hasExtensionMembers()) {
293 1
			return false;
294
		}
295
		
296 2
		return true;
297
	}
298
	
299
	/**
300
	 * @inheritDoc
301
	 */
302 18
	public function toArray() {
303 18
		$array = [];
304
		
305 18
		if ($this->hasAtMembers()) {
306 1
			$array = array_merge($array, $this->getAtMembers());
307
		}
308 18
		if ($this->hasExtensionMembers()) {
309 1
			$array = array_merge($array, $this->getExtensionMembers());
310
		}
311 18
		if ($this->id !== null) {
312 1
			$array['id'] = $this->id;
313
		}
314 18
		if ($this->hasHttpStatusCode()) {
315 7
			$array['status'] = (string) $this->getHttpStatusCode();
316
		}
317 18
		if ($this->code !== null) {
318 17
			$array['code'] = $this->code;
319
		}
320 18
		if ($this->title !== null) {
321 6
			$array['title'] = $this->title;
322
		}
323 18
		if ($this->detail !== null) {
324 6
			$array['detail'] = $this->detail;
325
		}
326 18
		if ($this->links !== null && $this->links->isEmpty() === false) {
327 6
			$array['links'] = $this->links->toArray();
328
		}
329 18
		if ($this->source !== []) {
330 5
			$array['source'] = $this->source;
331
		}
332 18
		if ($this->meta !== null && $this->meta->isEmpty() === false) {
333 13
			$array['meta'] = $this->meta->toArray();
334
		}
335
		
336 18
		return $array;
337
	}
338
}
339