PageRenderTime 35ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/filp/whoops/src/Whoops/Run.php

https://gitlab.com/madwanz64/laravel
PHP | 545 lines | 250 code | 68 blank | 227 comment | 36 complexity | 9933a3d4a8a443e17f0bbb04ceb703bb MD5 | raw file
  1. <?php
  2. /**
  3. * Whoops - php errors for cool kids
  4. * @author Filipe Dobreira <http://github.com/filp>
  5. */
  6. namespace Whoops;
  7. use InvalidArgumentException;
  8. use Throwable;
  9. use Whoops\Exception\ErrorException;
  10. use Whoops\Exception\Inspector;
  11. use Whoops\Handler\CallbackHandler;
  12. use Whoops\Handler\Handler;
  13. use Whoops\Handler\HandlerInterface;
  14. use Whoops\Util\Misc;
  15. use Whoops\Util\SystemFacade;
  16. final class Run implements RunInterface
  17. {
  18. /**
  19. * @var bool
  20. */
  21. private $isRegistered;
  22. /**
  23. * @var bool
  24. */
  25. private $allowQuit = true;
  26. /**
  27. * @var bool
  28. */
  29. private $sendOutput = true;
  30. /**
  31. * @var integer|false
  32. */
  33. private $sendHttpCode = 500;
  34. /**
  35. * @var integer|false
  36. */
  37. private $sendExitCode = 1;
  38. /**
  39. * @var HandlerInterface[]
  40. */
  41. private $handlerStack = [];
  42. /**
  43. * @var array
  44. * @psalm-var list<array{patterns: string, levels: int}>
  45. */
  46. private $silencedPatterns = [];
  47. /**
  48. * @var SystemFacade
  49. */
  50. private $system;
  51. /**
  52. * In certain scenarios, like in shutdown handler, we can not throw exceptions.
  53. *
  54. * @var bool
  55. */
  56. private $canThrowExceptions = true;
  57. public function __construct(SystemFacade $system = null)
  58. {
  59. $this->system = $system ?: new SystemFacade;
  60. }
  61. /**
  62. * Explicitly request your handler runs as the last of all currently registered handlers.
  63. *
  64. * @param callable|HandlerInterface $handler
  65. *
  66. * @return Run
  67. */
  68. public function appendHandler($handler)
  69. {
  70. array_unshift($this->handlerStack, $this->resolveHandler($handler));
  71. return $this;
  72. }
  73. /**
  74. * Explicitly request your handler runs as the first of all currently registered handlers.
  75. *
  76. * @param callable|HandlerInterface $handler
  77. *
  78. * @return Run
  79. */
  80. public function prependHandler($handler)
  81. {
  82. return $this->pushHandler($handler);
  83. }
  84. /**
  85. * Register your handler as the last of all currently registered handlers (to be executed first).
  86. * Prefer using appendHandler and prependHandler for clarity.
  87. *
  88. * @param callable|HandlerInterface $handler
  89. *
  90. * @return Run
  91. *
  92. * @throws InvalidArgumentException If argument is not callable or instance of HandlerInterface.
  93. */
  94. public function pushHandler($handler)
  95. {
  96. $this->handlerStack[] = $this->resolveHandler($handler);
  97. return $this;
  98. }
  99. /**
  100. * Removes and returns the last handler pushed to the handler stack.
  101. *
  102. * @see Run::removeFirstHandler(), Run::removeLastHandler()
  103. *
  104. * @return HandlerInterface|null
  105. */
  106. public function popHandler()
  107. {
  108. return array_pop($this->handlerStack);
  109. }
  110. /**
  111. * Removes the first handler.
  112. *
  113. * @return void
  114. */
  115. public function removeFirstHandler()
  116. {
  117. array_pop($this->handlerStack);
  118. }
  119. /**
  120. * Removes the last handler.
  121. *
  122. * @return void
  123. */
  124. public function removeLastHandler()
  125. {
  126. array_shift($this->handlerStack);
  127. }
  128. /**
  129. * Returns an array with all handlers, in the order they were added to the stack.
  130. *
  131. * @return array
  132. */
  133. public function getHandlers()
  134. {
  135. return $this->handlerStack;
  136. }
  137. /**
  138. * Clears all handlers in the handlerStack, including the default PrettyPage handler.
  139. *
  140. * @return Run
  141. */
  142. public function clearHandlers()
  143. {
  144. $this->handlerStack = [];
  145. return $this;
  146. }
  147. /**
  148. * Registers this instance as an error handler.
  149. *
  150. * @return Run
  151. */
  152. public function register()
  153. {
  154. if (!$this->isRegistered) {
  155. // Workaround PHP bug 42098
  156. // https://bugs.php.net/bug.php?id=42098
  157. class_exists("\\Whoops\\Exception\\ErrorException");
  158. class_exists("\\Whoops\\Exception\\FrameCollection");
  159. class_exists("\\Whoops\\Exception\\Frame");
  160. class_exists("\\Whoops\\Exception\\Inspector");
  161. $this->system->setErrorHandler([$this, self::ERROR_HANDLER]);
  162. $this->system->setExceptionHandler([$this, self::EXCEPTION_HANDLER]);
  163. $this->system->registerShutdownFunction([$this, self::SHUTDOWN_HANDLER]);
  164. $this->isRegistered = true;
  165. }
  166. return $this;
  167. }
  168. /**
  169. * Unregisters all handlers registered by this Whoops\Run instance.
  170. *
  171. * @return Run
  172. */
  173. public function unregister()
  174. {
  175. if ($this->isRegistered) {
  176. $this->system->restoreExceptionHandler();
  177. $this->system->restoreErrorHandler();
  178. $this->isRegistered = false;
  179. }
  180. return $this;
  181. }
  182. /**
  183. * Should Whoops allow Handlers to force the script to quit?
  184. *
  185. * @param bool|int $exit
  186. *
  187. * @return bool
  188. */
  189. public function allowQuit($exit = null)
  190. {
  191. if (func_num_args() == 0) {
  192. return $this->allowQuit;
  193. }
  194. return $this->allowQuit = (bool) $exit;
  195. }
  196. /**
  197. * Silence particular errors in particular files.
  198. *
  199. * @param array|string $patterns List or a single regex pattern to match.
  200. * @param int $levels Defaults to E_STRICT | E_DEPRECATED.
  201. *
  202. * @return Run
  203. */
  204. public function silenceErrorsInPaths($patterns, $levels = 10240)
  205. {
  206. $this->silencedPatterns = array_merge(
  207. $this->silencedPatterns,
  208. array_map(
  209. function ($pattern) use ($levels) {
  210. return [
  211. "pattern" => $pattern,
  212. "levels" => $levels,
  213. ];
  214. },
  215. (array) $patterns
  216. )
  217. );
  218. return $this;
  219. }
  220. /**
  221. * Returns an array with silent errors in path configuration.
  222. *
  223. * @return array
  224. */
  225. public function getSilenceErrorsInPaths()
  226. {
  227. return $this->silencedPatterns;
  228. }
  229. /**
  230. * Should Whoops send HTTP error code to the browser if possible?
  231. * Whoops will by default send HTTP code 500, but you may wish to
  232. * use 502, 503, or another 5xx family code.
  233. *
  234. * @param bool|int $code
  235. *
  236. * @return int|false
  237. *
  238. * @throws InvalidArgumentException
  239. */
  240. public function sendHttpCode($code = null)
  241. {
  242. if (func_num_args() == 0) {
  243. return $this->sendHttpCode;
  244. }
  245. if (!$code) {
  246. return $this->sendHttpCode = false;
  247. }
  248. if ($code === true) {
  249. $code = 500;
  250. }
  251. if ($code < 400 || 600 <= $code) {
  252. throw new InvalidArgumentException(
  253. "Invalid status code '$code', must be 4xx or 5xx"
  254. );
  255. }
  256. return $this->sendHttpCode = $code;
  257. }
  258. /**
  259. * Should Whoops exit with a specific code on the CLI if possible?
  260. * Whoops will exit with 1 by default, but you can specify something else.
  261. *
  262. * @param int $code
  263. *
  264. * @return int
  265. *
  266. * @throws InvalidArgumentException
  267. */
  268. public function sendExitCode($code = null)
  269. {
  270. if (func_num_args() == 0) {
  271. return $this->sendExitCode;
  272. }
  273. if ($code < 0 || 255 <= $code) {
  274. throw new InvalidArgumentException(
  275. "Invalid status code '$code', must be between 0 and 254"
  276. );
  277. }
  278. return $this->sendExitCode = (int) $code;
  279. }
  280. /**
  281. * Should Whoops push output directly to the client?
  282. * If this is false, output will be returned by handleException.
  283. *
  284. * @param bool|int $send
  285. *
  286. * @return bool
  287. */
  288. public function writeToOutput($send = null)
  289. {
  290. if (func_num_args() == 0) {
  291. return $this->sendOutput;
  292. }
  293. return $this->sendOutput = (bool) $send;
  294. }
  295. /**
  296. * Handles an exception, ultimately generating a Whoops error page.
  297. *
  298. * @param Throwable $exception
  299. *
  300. * @return string Output generated by handlers.
  301. */
  302. public function handleException($exception)
  303. {
  304. // Walk the registered handlers in the reverse order
  305. // they were registered, and pass off the exception
  306. $inspector = $this->getInspector($exception);
  307. // Capture output produced while handling the exception,
  308. // we might want to send it straight away to the client,
  309. // or return it silently.
  310. $this->system->startOutputBuffering();
  311. // Just in case there are no handlers:
  312. $handlerResponse = null;
  313. $handlerContentType = null;
  314. try {
  315. foreach (array_reverse($this->handlerStack) as $handler) {
  316. $handler->setRun($this);
  317. $handler->setInspector($inspector);
  318. $handler->setException($exception);
  319. // The HandlerInterface does not require an Exception passed to handle()
  320. // and neither of our bundled handlers use it.
  321. // However, 3rd party handlers may have already relied on this parameter,
  322. // and removing it would be possibly breaking for users.
  323. $handlerResponse = $handler->handle($exception);
  324. // Collect the content type for possible sending in the headers.
  325. $handlerContentType = method_exists($handler, 'contentType') ? $handler->contentType() : null;
  326. if (in_array($handlerResponse, [Handler::LAST_HANDLER, Handler::QUIT])) {
  327. // The Handler has handled the exception in some way, and
  328. // wishes to quit execution (Handler::QUIT), or skip any
  329. // other handlers (Handler::LAST_HANDLER). If $this->allowQuit
  330. // is false, Handler::QUIT behaves like Handler::LAST_HANDLER
  331. break;
  332. }
  333. }
  334. $willQuit = $handlerResponse == Handler::QUIT && $this->allowQuit();
  335. } finally {
  336. $output = $this->system->cleanOutputBuffer();
  337. }
  338. // If we're allowed to, send output generated by handlers directly
  339. // to the output, otherwise, and if the script doesn't quit, return
  340. // it so that it may be used by the caller
  341. if ($this->writeToOutput()) {
  342. // @todo Might be able to clean this up a bit better
  343. if ($willQuit) {
  344. // Cleanup all other output buffers before sending our output:
  345. while ($this->system->getOutputBufferLevel() > 0) {
  346. $this->system->endOutputBuffering();
  347. }
  348. // Send any headers if needed:
  349. if (Misc::canSendHeaders() && $handlerContentType) {
  350. header("Content-Type: {$handlerContentType}");
  351. }
  352. }
  353. $this->writeToOutputNow($output);
  354. }
  355. if ($willQuit) {
  356. // HHVM fix for https://github.com/facebook/hhvm/issues/4055
  357. $this->system->flushOutputBuffer();
  358. $this->system->stopExecution(
  359. $this->sendExitCode()
  360. );
  361. }
  362. return $output;
  363. }
  364. /**
  365. * Converts generic PHP errors to \ErrorException instances, before passing them off to be handled.
  366. *
  367. * This method MUST be compatible with set_error_handler.
  368. *
  369. * @param int $level
  370. * @param string $message
  371. * @param string|null $file
  372. * @param int|null $line
  373. *
  374. * @return bool
  375. *
  376. * @throws ErrorException
  377. */
  378. public function handleError($level, $message, $file = null, $line = null)
  379. {
  380. if ($level & $this->system->getErrorReportingLevel()) {
  381. foreach ($this->silencedPatterns as $entry) {
  382. $pathMatches = (bool) preg_match($entry["pattern"], $file);
  383. $levelMatches = $level & $entry["levels"];
  384. if ($pathMatches && $levelMatches) {
  385. // Ignore the error, abort handling
  386. // See https://github.com/filp/whoops/issues/418
  387. return true;
  388. }
  389. }
  390. // XXX we pass $level for the "code" param only for BC reasons.
  391. // see https://github.com/filp/whoops/issues/267
  392. $exception = new ErrorException($message, /*code*/ $level, /*severity*/ $level, $file, $line);
  393. if ($this->canThrowExceptions) {
  394. throw $exception;
  395. } else {
  396. $this->handleException($exception);
  397. }
  398. // Do not propagate errors which were already handled by Whoops.
  399. return true;
  400. }
  401. // Propagate error to the next handler, allows error_get_last() to
  402. // work on silenced errors.
  403. return false;
  404. }
  405. /**
  406. * Special case to deal with Fatal errors and the like.
  407. *
  408. * @return void
  409. */
  410. public function handleShutdown()
  411. {
  412. // If we reached this step, we are in shutdown handler.
  413. // An exception thrown in a shutdown handler will not be propagated
  414. // to the exception handler. Pass that information along.
  415. $this->canThrowExceptions = false;
  416. $error = $this->system->getLastError();
  417. if ($error && Misc::isLevelFatal($error['type'])) {
  418. // If there was a fatal error,
  419. // it was not handled in handleError yet.
  420. $this->allowQuit = false;
  421. $this->handleError(
  422. $error['type'],
  423. $error['message'],
  424. $error['file'],
  425. $error['line']
  426. );
  427. }
  428. }
  429. /**
  430. * @param Throwable $exception
  431. *
  432. * @return Inspector
  433. */
  434. private function getInspector($exception)
  435. {
  436. return new Inspector($exception);
  437. }
  438. /**
  439. * Resolves the giving handler.
  440. *
  441. * @param callable|HandlerInterface $handler
  442. *
  443. * @return HandlerInterface
  444. *
  445. * @throws InvalidArgumentException
  446. */
  447. private function resolveHandler($handler)
  448. {
  449. if (is_callable($handler)) {
  450. $handler = new CallbackHandler($handler);
  451. }
  452. if (!$handler instanceof HandlerInterface) {
  453. throw new InvalidArgumentException(
  454. "Handler must be a callable, or instance of "
  455. . "Whoops\\Handler\\HandlerInterface"
  456. );
  457. }
  458. return $handler;
  459. }
  460. /**
  461. * Echo something to the browser.
  462. *
  463. * @param string $output
  464. *
  465. * @return Run
  466. */
  467. private function writeToOutputNow($output)
  468. {
  469. if ($this->sendHttpCode() && Misc::canSendHeaders()) {
  470. $this->system->setHttpResponseCode(
  471. $this->sendHttpCode()
  472. );
  473. }
  474. echo $output;
  475. return $this;
  476. }
  477. }