Test Failed
Pull Request — main (#64)
by Lode
08:15
created

ErrorObject::setUniqueIdentifier()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
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\HttpStatusCodeManager;
10
use alsvanzelf\jsonapi\helpers\LinksManager;
11
use alsvanzelf\jsonapi\helpers\Validator;
12
use alsvanzelf\jsonapi\interfaces\ObjectInterface;
13
14
class ErrorObject implements ObjectInterface {
0 ignored issues
show
Coding Style introduced by
Missing doc comment for class ErrorObject
Loading history...
15
	use AtMemberManager, HttpStatusCodeManager, LinksManager;
16
	
17
	/** @var string */
18
	protected $id;
19
	/** @var string */
20
	protected $code;
21
	/** @var string */
22
	protected $title;
23
	/** @var string */
24
	protected $detail;
25
	/** @var array */
26
	protected $source = [];
27
	/** @var MetaObject */
28
	protected $meta;
29
	/** @var array */
30
	protected static $defaults = [
31
		/**
32
		 * add the trace of exceptions when adding exceptions
33
		 * in some cases it might be handy to disable if traces are too big
34
		 */
35
		'includeExceptionTrace' => true,
36
		
37
		/**
38
		 * strip a base path from exception file and trace paths
39
		 * set this to the applications root to have more readable exception responses
40
		 */
41
		'stripExceptionBasePath' => null,
42
	];
43
	
44
	/**
45
	 * @param string|int $genericCode       developer-friendly code of the generic type of error
46
	 * @param string     $genericTitle      human-friendly title of the generic type of error
47
	 * @param string     $specificDetails   optional, human-friendly explanation of the specific error
48
	 * @param string     $specificAboutLink optional, human-friendly explanation of the specific error
49
	 * @param string     $genericTypeLink   optional, human-friendly explanation of the generic type of error
50
	 */
51
	public function __construct($genericCode=null, $genericTitle=null, $specificDetails=null, $specificAboutLink=null, $genericTypeLink=null) {
52
		if ($genericCode !== null) {
53
			$this->setApplicationCode($genericCode);
54
		}
55
		if ($genericTitle !== null) {
56
			$this->setHumanExplanation($genericTitle, $specificDetails, $specificAboutLink, $genericTypeLink);
57
		}
58
	}
59
	
60
	/**
61
	 * human api
62
	 */
63
	
64
	/**
65
	 * @param  \Exception|\Throwable $exception
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
66
	 * @param  array                 $options   optional {@see ErrorObject::$defaults}
67
	 * @return ErrorObject
68
	 * 
69
	 * @throws InputException if $exception is not \Exception or \Throwable
70
	 */
71
	public static function fromException($exception, array $options=[]) {
72 View Code Duplication
		if ($exception instanceof \Exception === false && $exception instanceof \Throwable === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
73
			throw new InputException('input is not a real exception in php5 or php7');
74
		}
75
		
76
		$options = array_merge(self::$defaults, $options);
77
		
78
		$errorObject = new self();
79
		
80
		$className = get_class($exception);
81
		if (strpos($className, '\\')) {
82
			$exploded  = explode('\\', $className);
83
			$className = end($exploded);
84
		}
85
		$errorObject->setApplicationCode(Converter::camelCaseToWords($className));
86
		
87
		$filePath = $exception->getFile();
88
		if ($options['stripExceptionBasePath'] !== null) {
89
			$filePath = str_replace($options['stripExceptionBasePath'], '', $filePath);
90
		}
91
		
92
		$metaObject = MetaObject::fromArray([
93
			'type'    => get_class($exception),
94
			'message' => $exception->getMessage(),
95
			'code'    => $exception->getCode(),
96
			'file'    => $filePath,
97
			'line'    => $exception->getLine(),
98
		]);
99
		
100
		if ($options['includeExceptionTrace']) {
101
			$trace = $exception->getTrace();
102
			if ($options['stripExceptionBasePath'] !== null) {
103
				foreach ($trace as &$traceElement) {
104
					if (isset($traceElement['file'])) {
105
						$traceElement['file'] = str_replace($options['stripExceptionBasePath'], '', $traceElement['file']);
106
					}
107
				}
108
			}
109
			
110
			$metaObject->add('trace', $trace);
111
		}
112
		
113
		$errorObject->setMetaObject($metaObject);
114
		
115
		if (Validator::checkHttpStatusCode($exception->getCode())) {
116
			$errorObject->setHttpStatusCode($exception->getCode());
117
		}
118
		
119
		return $errorObject;
120
	}
121
	
122
	/**
123
	 * explain this particular occurence of the error in a human-friendly way
124
	 * 
125
	 * @param string $genericTitle      title of the generic type of error
126
	 * @param string $specificDetails   optional, explanation of the specific error
127
	 * @param string $specificAboutLink optional, explanation of the specific error
128
	 * @param string $genericTypeLink   optional, explanation of the generic type of error
129
	 */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
130
	public function setHumanExplanation($genericTitle, $specificDetails=null, $specificAboutLink=null, $genericTypeLink=null) {
131
		$this->setHumanTitle($genericTitle);
132
		
133
		if ($specificDetails !== null) {
134
			$this->setHumanDetails($specificDetails);
135
		}
136
		if ($specificAboutLink !== null) {
137
			$this->setAboutLink($specificAboutLink);
138
		}
139
		if ($genericTypeLink !== null) {
140
			$this->appendTypeLink($genericTypeLink);
141
		}
142
	}
143
	
144
	/**
145
	 * set the link about this specific occurence of the error, explained in a human-friendly way
146
	 * 
147
	 * @param string $href
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
148
	 * @param array  $meta optional, if given a LinkObject is added, otherwise a link string is added
149
	 */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
150
	public function setAboutLink($href, array $meta=[]) {
151
		$this->addLink('about', $href, $meta);
152
	}
153
	
154
	/**
155
	 * append a link of the generic type of this error, explained in a human-friendly way
156
	 * 
157
	 * @param string $href
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
158
	 * @param array  $meta optional, if given a LinkObject is added, otherwise a link string is added
159
	 */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
160
	public function appendTypeLink($href, array $meta=[]) {
161
		$this->appendLink('type', $href, $meta);
162
	}
163
	
164
	/**
165
	 * blame the json pointer from the request body causing this error
166
	 * 
167
	 * @see https://tools.ietf.org/html/rfc6901
168
	 * 
169
	 * @param  string $pointer e.g. "/data/attributes/title" or "/data"
170
	 */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
171
	public function blameJsonPointer($pointer) {
172
		$this->addSource('pointer', $pointer);
173
	}
174
	
175
	/**
176
	 * blame the query parameter from the request causing this error
177
	 * 
178
	 * @param  string $parameter
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
179
	 */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
180
	public function blameQueryParameter($parameter) {
181
		$this->addSource('parameter', $parameter);
182
	}
183
	
184
	/**
185
	 * @param string $key
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
186
	 * @param mixed  $value
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
187
	 */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
188
	public function addMeta($key, $value) {
189
		if ($this->meta === null) {
190
			$this->setMetaObject(new MetaObject());
191
		}
192
		
193
		$this->meta->add($key, $value);
194
	}
195
	
196
	/**
197
	 * spec api
198
	 */
199
	
200
	/**
201
	 * a unique identifier for this specific occurrence of the error
202
	 * 
203
	 * @param string|int $id
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
204
	 */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
205
	public function setUniqueIdentifier($id) {
206
		$this->id = $id;
0 ignored issues
show
Documentation Bug introduced by
It seems like $id can also be of type integer. However, the property $id is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
207
	}
208
	
209
	/**
210
	 * a code expressing the generic type of this error
211
	 * it should be application-specific and aimed at developers
212
	 * 
213
	 * @param string|int $genericCode will be casted to a string
214
	 */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
215
	public function setApplicationCode($genericCode) {
216
		$this->code = (string) $genericCode;
217
	}
218
	
219
	/**
220
	 * add the source of the error
221
	 * 
222
	 * @param string $key   {@see ->blameJsonPointer(), ->blameQueryParameter()}
223
	 * @param string $value
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
224
	 */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
225
	public function addSource($key, $value) {
226
		Validator::checkMemberName($key);
227
		
228
		$this->source[$key] = $value;
229
	}
230
	
231
	/**
232
	 * a short human-friendly explanation of the generic type of this error
233
	 * 
234
	 * @param string $genericTitle
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
235
	 */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
236
	public function setHumanTitle($genericTitle) {
237
		$this->title = $genericTitle;
238
	}
239
	
240
	/**
241
	 * a human-friendly explanation of this specific occurrence of the error
242
	 * 
243
	 * @param string $specificDetails
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
244
	 */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
245
	public function setHumanDetails($specificDetails) {
246
		$this->detail = $specificDetails;
247
	}
248
	
249
	/**
250
	 * @param MetaObject $metaObject
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
251
	 */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
252
	public function setMetaObject(MetaObject $metaObject) {
253
		$this->meta = $metaObject;
254
	}
255
	
256
	/**
257
	 * ObjectInterface
258
	 */
259
	
260
	/**
261
	 * @inheritDoc
262
	 */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
263
	public function isEmpty() {
264
		if ($this->id !== null) {
265
			return false;
266
		}
267
		if ($this->hasHttpStatusCode()) {
268
			return false;
269
		}
270
		if ($this->code !== null) {
271
			return false;
272
		}
273
		if ($this->title !== null) {
274
			return false;
275
		}
276
		if ($this->detail !== null) {
277
			return false;
278
		}
279 View Code Duplication
		if ($this->links !== null && $this->links->isEmpty() === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
280
			return false;
281
		}
282
		if ($this->source !== []) {
283
			return false;
284
		}
285
		if ($this->meta !== null && $this->meta->isEmpty() === false) {
286
			return false;
287
		}
288
		if ($this->hasAtMembers()) {
289
			return false;
290
		}
291
		
292
		return true;
293
	}
294
	
295
	/**
296
	 * @inheritDoc
297
	 */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
298
	public function toArray() {
299
		$array = $this->getAtMembers();
300
		
301
		if ($this->id !== null) {
302
			$array['id'] = $this->id;
303
		}
304
		if ($this->hasHttpStatusCode()) {
305
			$array['status'] = (string) $this->getHttpStatusCode();
306
		}
307
		if ($this->code !== null) {
308
			$array['code'] = $this->code;
309
		}
310
		if ($this->title !== null) {
311
			$array['title'] = $this->title;
312
		}
313
		if ($this->detail !== null) {
314
			$array['detail'] = $this->detail;
315
		}
316
		if ($this->links !== null && $this->links->isEmpty() === false) {
317
			$array['links'] = $this->links->toArray();
318
		}
319
		if ($this->source !== []) {
320
			$array['source'] = $this->source;
321
		}
322
		if ($this->meta !== null && $this->meta->isEmpty() === false) {
323
			$array['meta'] = $this->meta->toArray();
324
		}
325
		
326
		return $array;
327
	}
328
}
329