ErrorsDocument   A
last analyzed

Complexity

Total Complexity 23

Size/Duplication

Total Lines 176
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 52
c 1
b 0
f 0
dl 0
loc 176
ccs 51
cts 51
cp 1
rs 10
wmc 23

7 Methods

Rating   Name   Duplication   Size   Complexity  
A addErrorObject() 0 5 2
A __construct() 0 5 2
B determineHttpStatusCode() 0 32 7
A fromException() 0 11 3
A addException() 0 14 5
A add() 0 4 1
A toArray() 0 13 3
1
<?php
2
3
namespace alsvanzelf\jsonapi;
4
5
use alsvanzelf\jsonapi\Document;
6
use alsvanzelf\jsonapi\exceptions\InputException;
7
use alsvanzelf\jsonapi\objects\ErrorObject;
8
9
/**
10
 * this document is used to send one or multiple errors
11
 */
12
class ErrorsDocument extends Document {
13
	/** @var ErrorObject[] */
14
	protected $errors = [];
15
	/** @var array */
16
	protected $httpStatusCodes;
17
	/** @var array */
18
	protected static $defaults = [
19
		/**
20
		 * add the trace of exceptions when adding exceptions
21
		 * in some cases it might be handy to disable if traces are too big
22
		 */
23
		'includeExceptionTrace' => true,
24
		
25
		/**
26
		 * add previous exceptions as separate errors when adding exceptions
27
		 */
28
		'includeExceptionPrevious' => true,
29
	];
30
	
31
	/**
32
	 * @param ErrorObject $errorObject optional
33
	 */
34 20
	public function __construct(ErrorObject $errorObject=null) {
35 20
		parent::__construct();
36
		
37 20
		if ($errorObject !== null) {
38 1
			$this->addErrorObject($errorObject);
39
		}
40
	}
41
	
42
	/**
43
	 * human api
44
	 */
45
	
46
	/**
47
	 * @param  \Exception|\Throwable $exception
48
	 * @param  array                 $options   optional {@see ErrorsDocument::$defaults}
49
	 * @return ErrorsDocument
50
	 * 
51
	 * @throws InputException if $exception is not \Exception or \Throwable
52
	 */
53 4
	public static function fromException($exception, array $options=[]) {
54 4
		if ($exception instanceof \Exception === false && $exception instanceof \Throwable === false) {
55 1
			throw new InputException('input is not a real exception in php5 or php7');
56
		}
57
		
58 3
		$options = array_merge(self::$defaults, $options);
59
		
60 3
		$errorsDocument = new self();
61 3
		$errorsDocument->addException($exception, $options);
62
		
63 3
		return $errorsDocument;
64
	}
65
	
66
	/**
67
	 * add an ErrorObject for the given $exception
68
	 * 
69
	 * recursively adds multiple ErrorObjects if $exception carries a ->getPrevious()
70
	 * 
71
	 * @param \Exception|\Throwable $exception
72
	 * @param array                 $options   optional {@see ErrorsDocument::$defaults}
73
	 * 
74
	 * @throws InputException if $exception is not \Exception or \Throwable
75
	 */
76 7
	public function addException($exception, array $options=[]) {
77 7
		if ($exception instanceof \Exception === false && $exception instanceof \Throwable === false) {
78 1
			throw new InputException('input is not a real exception in php5 or php7');
79
		}
80
		
81 6
		$options = array_merge(self::$defaults, $options);
82
		
83 6
		$this->addErrorObject(ErrorObject::fromException($exception, $options));
84
		
85 6
		if ($options['includeExceptionPrevious']) {
86 5
			$exception = $exception->getPrevious();
87 5
			while ($exception !== null) {
88 2
				$this->addException($exception, $options);
89 2
				$exception = $exception->getPrevious();
90
			}
91
		}
92
	}
93
	
94
	/**
95
	 * @param string|int $genericCode       developer-friendly code of the generic type of error
96
	 * @param string     $genericTitle      human-friendly title of the generic type of error
97
	 * @param string     $specificDetails   optional, human-friendly explanation of the specific error
98
	 * @param string     $specificAboutLink optional, human-friendly explanation of the specific error
99
	 * @param string     $genericTypeLink   optional, human-friendly explanation of the generic type of error
100
	 */
101 1
	public function add($genericCode, $genericTitle, $specificDetails=null, $specificAboutLink=null, $genericTypeLink=null) {
102 1
		$errorObject = new ErrorObject($genericCode, $genericTitle, $specificDetails, $specificAboutLink, $genericTypeLink);
103
		
104 1
		$this->addErrorObject($errorObject);
105
	}
106
	
107
	/**
108
	 * spec api
109
	 */
110
	
111
	/**
112
	 * @note also defines the http status code of the document if the ErrorObject has it defined
113
	 * 
114
	 * @param ErrorObject $errorObject
115
	 */
116 9
	public function addErrorObject(ErrorObject $errorObject) {
117 9
		$this->errors[] = $errorObject;
118
		
119 9
		if ($errorObject->hasHttpStatusCode()) {
120 3
			$this->setHttpStatusCode($this->determineHttpStatusCode($errorObject->getHttpStatusCode()));
121
		}
122
	}
123
	
124
	/**
125
	 * DocumentInterface
126
	 */
127
	
128
	/**
129
	 * @inheritDoc
130
	 */
131 8
	public function toArray() {
132 8
		$array = parent::toArray();
133
		
134 8
		$array['errors'] = [];
135 8
		foreach ($this->errors as $error) {
136 8
			if ($error->isEmpty()) {
137 1
				continue;
138
			}
139
			
140 8
			$array['errors'][] = $error->toArray();
141
		}
142
		
143 8
		return $array;
144
	}
145
	
146
	/**
147
	 * internal api
148
	 */
149
	
150
	/**
151
	 * @internal
152
	 * 
153
	 * @param  string|int $httpStatusCode
154
	 * @return int
155
	 */
156 13
	protected function determineHttpStatusCode($httpStatusCode) {
157
		// add the new code
158 13
		$category = substr($httpStatusCode, 0, 1);
159 13
		$this->httpStatusCodes[$category][$httpStatusCode] = true;
160
		
161 13
		$advisedStatusCode = $httpStatusCode;
162
		
163
		// when there's multiple, give preference to 5xx errors
164 13
		if (isset($this->httpStatusCodes['5']) && isset($this->httpStatusCodes['4'])) {
165
			// use a generic one
166 3
			$advisedStatusCode = 500;
167
		}
168 13
		elseif (isset($this->httpStatusCodes['5'])) {
169 4
			if (count($this->httpStatusCodes['5']) === 1) {
170 4
				$advisedStatusCode = key($this->httpStatusCodes['5']);
171
			}
172
			else {
173
				// use a generic one
174 4
				$advisedStatusCode = 500;
175
			}
176
		}
177 9
		elseif (isset($this->httpStatusCodes['4'])) {
178 8
			if (count($this->httpStatusCodes['4']) === 1) {
179 8
				$advisedStatusCode = key($this->httpStatusCodes['4']);
180
			}
181
			else {
182
				// use a generic one
183 3
				$advisedStatusCode = 400;
184
			}
185
		}
186
		
187 13
		return (int) $advisedStatusCode;
188
	}
189
}
190