FlattenException.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Debug\Exception;
  11. use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
  12. use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
  13. /**
  14. * FlattenException wraps a PHP Exception to be able to serialize it.
  15. *
  16. * Basically, this class removes all objects from the trace.
  17. *
  18. * @author Fabien Potencier <fabien@symfony.com>
  19. */
  20. class FlattenException
  21. {
  22. private $message;
  23. private $code;
  24. private $previous;
  25. private $trace;
  26. private $class;
  27. private $statusCode;
  28. private $headers;
  29. private $file;
  30. private $line;
  31. public static function create(\Exception $exception, $statusCode = null, array $headers = array())
  32. {
  33. $e = new static();
  34. $e->setMessage($exception->getMessage());
  35. $e->setCode($exception->getCode());
  36. if ($exception instanceof HttpExceptionInterface) {
  37. $statusCode = $exception->getStatusCode();
  38. $headers = array_merge($headers, $exception->getHeaders());
  39. } elseif ($exception instanceof RequestExceptionInterface) {
  40. $statusCode = 400;
  41. }
  42. if (null === $statusCode) {
  43. $statusCode = 500;
  44. }
  45. $e->setStatusCode($statusCode);
  46. $e->setHeaders($headers);
  47. $e->setTraceFromException($exception);
  48. $e->setClass(\get_class($exception));
  49. $e->setFile($exception->getFile());
  50. $e->setLine($exception->getLine());
  51. $previous = $exception->getPrevious();
  52. if ($previous instanceof \Exception) {
  53. $e->setPrevious(static::create($previous));
  54. } elseif ($previous instanceof \Throwable) {
  55. $e->setPrevious(static::create(new FatalThrowableError($previous)));
  56. }
  57. return $e;
  58. }
  59. public function toArray()
  60. {
  61. $exceptions = array();
  62. foreach (array_merge(array($this), $this->getAllPrevious()) as $exception) {
  63. $exceptions[] = array(
  64. 'message' => $exception->getMessage(),
  65. 'class' => $exception->getClass(),
  66. 'trace' => $exception->getTrace(),
  67. );
  68. }
  69. return $exceptions;
  70. }
  71. public function getStatusCode()
  72. {
  73. return $this->statusCode;
  74. }
  75. public function setStatusCode($code)
  76. {
  77. $this->statusCode = $code;
  78. }
  79. public function getHeaders()
  80. {
  81. return $this->headers;
  82. }
  83. public function setHeaders(array $headers)
  84. {
  85. $this->headers = $headers;
  86. }
  87. public function getClass()
  88. {
  89. return $this->class;
  90. }
  91. public function setClass($class)
  92. {
  93. $this->class = $class;
  94. }
  95. public function getFile()
  96. {
  97. return $this->file;
  98. }
  99. public function setFile($file)
  100. {
  101. $this->file = $file;
  102. }
  103. public function getLine()
  104. {
  105. return $this->line;
  106. }
  107. public function setLine($line)
  108. {
  109. $this->line = $line;
  110. }
  111. public function getMessage()
  112. {
  113. return $this->message;
  114. }
  115. public function setMessage($message)
  116. {
  117. $this->message = $message;
  118. }
  119. public function getCode()
  120. {
  121. return $this->code;
  122. }
  123. public function setCode($code)
  124. {
  125. $this->code = $code;
  126. }
  127. public function getPrevious()
  128. {
  129. return $this->previous;
  130. }
  131. public function setPrevious(self $previous)
  132. {
  133. $this->previous = $previous;
  134. }
  135. public function getAllPrevious()
  136. {
  137. $exceptions = array();
  138. $e = $this;
  139. while ($e = $e->getPrevious()) {
  140. $exceptions[] = $e;
  141. }
  142. return $exceptions;
  143. }
  144. public function getTrace()
  145. {
  146. return $this->trace;
  147. }
  148. public function setTraceFromException(\Exception $exception)
  149. {
  150. $this->setTrace($exception->getTrace(), $exception->getFile(), $exception->getLine());
  151. }
  152. public function setTrace($trace, $file, $line)
  153. {
  154. $this->trace = array();
  155. $this->trace[] = array(
  156. 'namespace' => '',
  157. 'short_class' => '',
  158. 'class' => '',
  159. 'type' => '',
  160. 'function' => '',
  161. 'file' => $file,
  162. 'line' => $line,
  163. 'args' => array(),
  164. );
  165. foreach ($trace as $entry) {
  166. $class = '';
  167. $namespace = '';
  168. if (isset($entry['class'])) {
  169. $parts = explode('\\', $entry['class']);
  170. $class = array_pop($parts);
  171. $namespace = implode('\\', $parts);
  172. }
  173. $this->trace[] = array(
  174. 'namespace' => $namespace,
  175. 'short_class' => $class,
  176. 'class' => isset($entry['class']) ? $entry['class'] : '',
  177. 'type' => isset($entry['type']) ? $entry['type'] : '',
  178. 'function' => isset($entry['function']) ? $entry['function'] : null,
  179. 'file' => isset($entry['file']) ? $entry['file'] : null,
  180. 'line' => isset($entry['line']) ? $entry['line'] : null,
  181. 'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : array(),
  182. );
  183. }
  184. }
  185. private function flattenArgs($args, $level = 0, &$count = 0)
  186. {
  187. $result = array();
  188. foreach ($args as $key => $value) {
  189. if (++$count > 1e4) {
  190. return array('array', '*SKIPPED over 10000 entries*');
  191. }
  192. if ($value instanceof \__PHP_Incomplete_Class) {
  193. // is_object() returns false on PHP<=7.1
  194. $result[$key] = array('incomplete-object', $this->getClassNameFromIncomplete($value));
  195. } elseif (\is_object($value)) {
  196. $result[$key] = array('object', \get_class($value));
  197. } elseif (\is_array($value)) {
  198. if ($level > 10) {
  199. $result[$key] = array('array', '*DEEP NESTED ARRAY*');
  200. } else {
  201. $result[$key] = array('array', $this->flattenArgs($value, $level + 1, $count));
  202. }
  203. } elseif (null === $value) {
  204. $result[$key] = array('null', null);
  205. } elseif (\is_bool($value)) {
  206. $result[$key] = array('boolean', $value);
  207. } elseif (\is_int($value)) {
  208. $result[$key] = array('integer', $value);
  209. } elseif (\is_float($value)) {
  210. $result[$key] = array('float', $value);
  211. } elseif (\is_resource($value)) {
  212. $result[$key] = array('resource', get_resource_type($value));
  213. } else {
  214. $result[$key] = array('string', (string) $value);
  215. }
  216. }
  217. return $result;
  218. }
  219. private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value)
  220. {
  221. $array = new \ArrayObject($value);
  222. return $array['__PHP_Incomplete_Class_Name'];
  223. }
  224. }