FlattenException.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\debug;
  8. /**
  9. * FlattenException wraps a PHP Exception to be able to serialize it.
  10. * Implements the Throwable interface
  11. * Basically, this class removes all objects from the trace.
  12. * Ported from Symfony components @link https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Debug/Exception/FlattenException.php
  13. *
  14. * @author Dmitry Bashkarev <dmitry@bashkarev.com>
  15. * @since 2.0.10
  16. */
  17. class FlattenException
  18. {
  19. /**
  20. * @var string
  21. */
  22. protected $message;
  23. /**
  24. * @var mixed|int
  25. */
  26. protected $code;
  27. /**
  28. * @var string
  29. */
  30. protected $file;
  31. /**
  32. * @var int
  33. */
  34. protected $line;
  35. /**
  36. * @var FlattenException|null
  37. */
  38. private $_previous;
  39. /**
  40. * @var array
  41. */
  42. private $_trace;
  43. /**
  44. * @var string
  45. */
  46. private $_toString;
  47. /**
  48. * @var string
  49. */
  50. private $_class;
  51. /**
  52. * FlattenException constructor.
  53. * @param \Exception $exception
  54. */
  55. public function __construct(\Exception $exception)
  56. {
  57. $this->setMessage($exception->getMessage());
  58. $this->setCode($exception->getCode());
  59. $this->setFile($exception->getFile());
  60. $this->setLine($exception->getLine());
  61. $this->setTrace($exception->getTrace());
  62. $this->setToString($exception->__toString());
  63. $this->setClass(get_class($exception));
  64. $previous = $exception->getPrevious();
  65. if ($previous instanceof \Exception) {
  66. $this->setPrevious(new self($previous));
  67. }
  68. }
  69. /**
  70. * Gets the Exception message
  71. * @return string the Exception message as a string.
  72. */
  73. public function getMessage()
  74. {
  75. return $this->message;
  76. }
  77. /**
  78. * Gets the Exception code
  79. * @return mixed|int the exception code as integer.
  80. */
  81. public function getCode()
  82. {
  83. return $this->code;
  84. }
  85. /**
  86. * Gets the file in which the exception occurred
  87. * @return string the filename in which the exception was created.
  88. */
  89. public function getFile()
  90. {
  91. return $this->file;
  92. }
  93. /**
  94. * Gets the line in which the exception occurred
  95. * @return int the line number where the exception was created.
  96. */
  97. public function getLine()
  98. {
  99. return $this->line;
  100. }
  101. /**
  102. * Gets the stack trace
  103. * @return array the Exception stack trace as an array.
  104. */
  105. public function getTrace()
  106. {
  107. return $this->_trace;
  108. }
  109. /**
  110. * Returns previous Exception
  111. * @return FlattenException the previous `FlattenException` if available or null otherwise.
  112. */
  113. public function getPrevious()
  114. {
  115. return $this->_previous;
  116. }
  117. /**
  118. * Gets the stack trace as a string
  119. * @return string the Exception stack trace as a string.
  120. */
  121. public function getTraceAsString()
  122. {
  123. $remove = "Stack trace:\n";
  124. $len = strpos($this->_toString, $remove);
  125. if ($len === false) {
  126. return '';
  127. }
  128. return substr($this->_toString, $len + strlen($remove));
  129. }
  130. /**
  131. * String representation of the exception
  132. * @return string the string representation of the exception.
  133. */
  134. public function __toString()
  135. {
  136. return $this->_toString;
  137. }
  138. /**
  139. * @return string the name of the class in which the exception was created.
  140. */
  141. public function getClass()
  142. {
  143. return $this->_class;
  144. }
  145. /**
  146. * @param string $message the Exception message as a string.
  147. */
  148. protected function setMessage($message)
  149. {
  150. $this->message = $message;
  151. }
  152. /**
  153. * @param mixed|int $code the exception code as integer.
  154. */
  155. protected function setCode($code)
  156. {
  157. $this->code = $code;
  158. }
  159. /**
  160. * @param string $file the filename in which the exception was created.
  161. */
  162. protected function setFile($file)
  163. {
  164. $this->file = $file;
  165. }
  166. /**
  167. * @param int $line the line number where the exception was created.
  168. */
  169. protected function setLine($line)
  170. {
  171. $this->line = $line;
  172. }
  173. /**
  174. * @param array $trace the Exception stack trace as an array.
  175. */
  176. protected function setTrace($trace)
  177. {
  178. $this->_trace = [];
  179. foreach ($trace as $entry) {
  180. $class = '';
  181. $namespace = '';
  182. if (isset($entry['class'])) {
  183. $parts = explode('\\', $entry['class']);
  184. $class = array_pop($parts);
  185. $namespace = implode('\\', $parts);
  186. }
  187. $this->_trace[] = [
  188. 'namespace' => $namespace,
  189. 'short_class' => $class,
  190. 'class' => isset($entry['class']) ? $entry['class'] : '',
  191. 'type' => isset($entry['type']) ? $entry['type'] : '',
  192. 'function' => isset($entry['function']) ? $entry['function'] : null,
  193. 'file' => isset($entry['file']) ? $entry['file'] : null,
  194. 'line' => isset($entry['line']) ? $entry['line'] : null,
  195. 'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : [],
  196. ];
  197. }
  198. }
  199. /**
  200. * @param string $string the string representation of the thrown object.
  201. */
  202. protected function setToString($string)
  203. {
  204. $this->_toString = $string;
  205. }
  206. /**
  207. * @param FlattenException $previous previous Exception.
  208. */
  209. protected function setPrevious(FlattenException $previous)
  210. {
  211. $this->_previous = $previous;
  212. }
  213. /**
  214. * @param string $class the name of the class in which the exception was created.
  215. */
  216. protected function setClass($class)
  217. {
  218. $this->_class = $class;
  219. }
  220. /**
  221. * Allows you to sterilize the Exception trace arguments
  222. * @param array $args
  223. * @param int $level recursion level
  224. * @param int $count number of records counter
  225. * @return array arguments tracing.
  226. */
  227. private function flattenArgs($args, $level = 0, &$count = 0)
  228. {
  229. $result = [];
  230. foreach ($args as $key => $value) {
  231. if (++$count > 10000) {
  232. return ['array', '*SKIPPED over 10000 entries*'];
  233. }
  234. if ($value instanceof \__PHP_Incomplete_Class) {
  235. // is_object() returns false on PHP<=7.1
  236. $result[$key] = ['incomplete-object', $this->getClassNameFromIncomplete($value)];
  237. } elseif (is_object($value)) {
  238. $result[$key] = ['object', get_class($value)];
  239. } elseif (is_array($value)) {
  240. if ($level > 10) {
  241. $result[$key] = ['array', '*DEEP NESTED ARRAY*'];
  242. } else {
  243. $result[$key] = ['array', $this->flattenArgs($value, $level + 1, $count)];
  244. }
  245. } elseif (null === $value) {
  246. $result[$key] = ['null', null];
  247. } elseif (is_bool($value)) {
  248. $result[$key] = ['boolean', $value];
  249. } elseif (is_int($value)) {
  250. $result[$key] = ['integer', $value];
  251. } elseif (is_float($value)) {
  252. $result[$key] = ['float', $value];
  253. } elseif (is_resource($value)) {
  254. $result[$key] = ['resource', get_resource_type($value)];
  255. } else {
  256. $result[$key] = ['string', (string)$value];
  257. }
  258. }
  259. return $result;
  260. }
  261. /**
  262. * @param \__PHP_Incomplete_Class $value
  263. * @return string the real class name of an incomplete class
  264. */
  265. private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value)
  266. {
  267. $array = new \ArrayObject($value);
  268. return $array['__PHP_Incomplete_Class_Name'];
  269. }
  270. }