1: <?php
2: 3: 4: 5: 6: 7: 8: 9:
10:
11: namespace Autarky\Errors;
12:
13: use Exception;
14: use ErrorException;
15: use ReflectionFunction;
16: use ReflectionMethod;
17: use SplDoublyLinkedList;
18: use Symfony\Component\HttpFoundation\Response;
19: use Symfony\Component\Debug\Exception\FatalErrorException;
20: use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
21:
22: 23: 24: 25:
26: class ErrorHandlerManager implements ErrorHandlerManagerInterface
27: {
28: 29: 30:
31: protected $resolver;
32:
33: 34: 35:
36: protected $handlers;
37:
38: 39: 40:
41: protected $defaultHandler;
42:
43: 44: 45: 46: 47:
48: protected $rethrow = false;
49:
50: 51: 52:
53: public function __construct(HandlerResolver $resolver)
54: {
55: $this->resolver = $resolver;
56: $this->handlers = new SplDoublyLinkedList;
57: }
58:
59: 60: 61:
62: public function setRethrow($rethrow)
63: {
64: if ($rethrow !== null) {
65: $this->rethrow = (bool) $rethrow;
66: } else if (PHP_SAPI === 'cli') {
67: $this->rethrow = true;
68: } else {
69: $this->rethrow = false;
70: }
71: }
72:
73: 74: 75:
76: public function appendHandler($handler)
77: {
78: $this->checkHandler($handler);
79: $this->handlers->push($handler);
80: }
81:
82: 83: 84:
85: public function prependHandler($handler)
86: {
87: $this->checkHandler($handler);
88: $this->handlers->unshift($handler);
89: }
90:
91: protected function checkHandler($handler)
92: {
93: if (
94: !$handler instanceof ErrorHandlerInterface
95: && !is_callable($handler)
96: && !is_string($handler)
97: ) {
98: $type = is_object($handler) ? get_class($handler) : gettype($handler);
99: throw new \InvalidArgumentException("Error handler must be callable, string or instance of Autarky\Errors\ErrorHandlerInterface, $type given");
100: }
101: }
102:
103: 104: 105:
106: public function setDefaultHandler(ErrorHandlerInterface $handler)
107: {
108: $this->defaultHandler = $handler;
109: }
110:
111: 112: 113:
114: public function register()
115: {
116: set_error_handler([$this, 'handleError']);
117:
118: if (!$this->rethrow) {
119: set_exception_handler([$this, 'handleUncaught']);
120: register_shutdown_function([$this, 'handleShutdown']);
121: } else {
122: register_shutdown_function([$this, 'throwFatalErrorException']);
123: }
124: }
125:
126: 127: 128:
129: public function handle(Exception $exception)
130: {
131: if ($this->rethrow) throw $exception;
132:
133: foreach ($this->handlers as $index => $handler) {
134: try {
135: if (is_string($handler)) {
136: $handler = $this->resolver->resolve($handler);
137: $this->handlers->offsetSet($index, $handler);
138: } else if (is_array($handler) && is_string($handler[0])) {
139: $handler[0] = $this->resolver->resolve($handler[0]);
140: $this->handlers->offsetSet($index, $handler);
141: }
142:
143: if (!$this->matchesTypehint($handler, $exception)) {
144: continue;
145: }
146:
147: $result = $this->callHandler($handler, $exception);
148:
149: if ($result !== null) {
150: return $this->makeResponse($result, $exception);
151: }
152: } catch (Exception $newException) {
153: return $this->handle($newException);
154: }
155: }
156:
157: return $this->makeResponse($this->defaultHandler->handle($exception), $exception);
158: }
159:
160: 161: 162: 163: 164: 165: 166: 167:
168: protected function makeResponse($response, Exception $exception)
169: {
170: if (!$response instanceof Response) {
171: $response = new Response($response);
172: }
173:
174: if (!$response->isClientError() && !$response->isServerError() && !$response->isRedirect()) {
175: if ($exception instanceof HttpExceptionInterface) {
176: $response->setStatusCode($exception->getStatusCode());
177: $response->headers->add($exception->getHeaders());
178: } else {
179: $response->setStatusCode(500);
180: }
181: }
182:
183: return $response;
184: }
185:
186: 187: 188: 189: 190: 191: 192: 193:
194: protected function matchesTypehint($handler, Exception $exception)
195: {
196: if ($handler instanceof ErrorHandlerInterface) {
197: return true;
198: }
199:
200: if (is_array($handler)) {
201: $reflection = (new ReflectionMethod($handler[0], $handler[1]));
202: } else {
203: $reflection = (new ReflectionFunction($handler));
204: }
205:
206: $params = $reflection->getParameters();
207:
208:
209:
210: if (empty($params)) {
211: return true;
212: }
213:
214: $handlerHint = $params[0]
215: ->getClass();
216:
217:
218:
219: if (!$handlerHint) {
220: return true;
221: }
222:
223: return $handlerHint->isInstance($exception);
224: }
225:
226: 227: 228: 229: 230: 231: 232: 233:
234: protected function callHandler($handler, Exception $exception)
235: {
236: if ($handler instanceof ErrorHandlerInterface) {
237: return $handler->handle($exception);
238: }
239:
240: return call_user_func($handler, $exception);
241: }
242:
243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253:
254: public function handleUncaught(Exception $exception)
255: {
256: if (PHP_SAPI === 'cli') {
257: throw $exception;
258: }
259:
260: return $this->handle($exception)
261: ->send();
262: }
263:
264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274:
275: public function handleError($level, $message, $file = '', $line = 0, $context = array())
276: {
277: if (error_reporting() & $level) {
278: throw new ErrorException($message, 0, $level, $file, $line);
279: }
280: }
281:
282: 283: 284: 285: 286:
287: public function handleShutdown()
288: {
289: $exception = $this->makeFatalErrorException();
290:
291: if ($exception) {
292: $this->handleUncaught($exception);
293: }
294: }
295:
296: 297: 298: 299: 300: 301: 302:
303: public function throwFatalErrorException()
304: {
305: $exception = $this->makeFatalErrorException();
306:
307: if ($exception) throw $exception;
308: }
309:
310: 311: 312: 313: 314: 315:
316: protected function makeFatalErrorException()
317: {
318: $error = error_get_last();
319:
320: if ($error !== null) {
321: return new FatalErrorException($error['message'],
322: $error['type'], 0, $error['file'], $error['line']);
323: }
324:
325: return null;
326: }
327: }
328: