Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\Debug\Exception; Chris@0: Chris@12: use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface; Chris@0: use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; Chris@0: Chris@0: /** Chris@0: * FlattenException wraps a PHP Exception to be able to serialize it. Chris@0: * Chris@0: * Basically, this class removes all objects from the trace. Chris@0: * Chris@0: * @author Fabien Potencier Chris@0: */ Chris@0: class FlattenException Chris@0: { Chris@0: private $message; Chris@0: private $code; Chris@0: private $previous; Chris@0: private $trace; Chris@0: private $class; Chris@0: private $statusCode; Chris@0: private $headers; Chris@0: private $file; Chris@0: private $line; Chris@0: Chris@17: public static function create(\Exception $exception, $statusCode = null, array $headers = []) Chris@0: { Chris@0: $e = new static(); Chris@0: $e->setMessage($exception->getMessage()); Chris@0: $e->setCode($exception->getCode()); Chris@0: Chris@0: if ($exception instanceof HttpExceptionInterface) { Chris@0: $statusCode = $exception->getStatusCode(); Chris@0: $headers = array_merge($headers, $exception->getHeaders()); Chris@12: } elseif ($exception instanceof RequestExceptionInterface) { Chris@12: $statusCode = 400; Chris@0: } Chris@0: Chris@0: if (null === $statusCode) { Chris@0: $statusCode = 500; Chris@0: } Chris@0: Chris@0: $e->setStatusCode($statusCode); Chris@0: $e->setHeaders($headers); Chris@0: $e->setTraceFromException($exception); Chris@17: $e->setClass(\get_class($exception)); Chris@0: $e->setFile($exception->getFile()); Chris@0: $e->setLine($exception->getLine()); Chris@0: Chris@0: $previous = $exception->getPrevious(); Chris@0: Chris@0: if ($previous instanceof \Exception) { Chris@0: $e->setPrevious(static::create($previous)); Chris@0: } elseif ($previous instanceof \Throwable) { Chris@0: $e->setPrevious(static::create(new FatalThrowableError($previous))); Chris@0: } Chris@0: Chris@0: return $e; Chris@0: } Chris@0: Chris@0: public function toArray() Chris@0: { Chris@17: $exceptions = []; Chris@17: foreach (array_merge([$this], $this->getAllPrevious()) as $exception) { Chris@17: $exceptions[] = [ Chris@0: 'message' => $exception->getMessage(), Chris@0: 'class' => $exception->getClass(), Chris@0: 'trace' => $exception->getTrace(), Chris@17: ]; Chris@0: } Chris@0: Chris@0: return $exceptions; Chris@0: } Chris@0: Chris@0: public function getStatusCode() Chris@0: { Chris@0: return $this->statusCode; Chris@0: } Chris@0: Chris@0: public function setStatusCode($code) Chris@0: { Chris@0: $this->statusCode = $code; Chris@0: } Chris@0: Chris@0: public function getHeaders() Chris@0: { Chris@0: return $this->headers; Chris@0: } Chris@0: Chris@0: public function setHeaders(array $headers) Chris@0: { Chris@0: $this->headers = $headers; Chris@0: } Chris@0: Chris@0: public function getClass() Chris@0: { Chris@0: return $this->class; Chris@0: } Chris@0: Chris@0: public function setClass($class) Chris@0: { Chris@0: $this->class = $class; Chris@0: } Chris@0: Chris@0: public function getFile() Chris@0: { Chris@0: return $this->file; Chris@0: } Chris@0: Chris@0: public function setFile($file) Chris@0: { Chris@0: $this->file = $file; Chris@0: } Chris@0: Chris@0: public function getLine() Chris@0: { Chris@0: return $this->line; Chris@0: } Chris@0: Chris@0: public function setLine($line) Chris@0: { Chris@0: $this->line = $line; Chris@0: } Chris@0: Chris@0: public function getMessage() Chris@0: { Chris@0: return $this->message; Chris@0: } Chris@0: Chris@0: public function setMessage($message) Chris@0: { Chris@0: $this->message = $message; Chris@0: } Chris@0: Chris@0: public function getCode() Chris@0: { Chris@0: return $this->code; Chris@0: } Chris@0: Chris@0: public function setCode($code) Chris@0: { Chris@0: $this->code = $code; Chris@0: } Chris@0: Chris@0: public function getPrevious() Chris@0: { Chris@0: return $this->previous; Chris@0: } Chris@0: Chris@16: public function setPrevious(self $previous) Chris@0: { Chris@0: $this->previous = $previous; Chris@0: } Chris@0: Chris@0: public function getAllPrevious() Chris@0: { Chris@17: $exceptions = []; Chris@0: $e = $this; Chris@0: while ($e = $e->getPrevious()) { Chris@0: $exceptions[] = $e; Chris@0: } Chris@0: Chris@0: return $exceptions; Chris@0: } Chris@0: Chris@0: public function getTrace() Chris@0: { Chris@0: return $this->trace; Chris@0: } Chris@0: Chris@0: public function setTraceFromException(\Exception $exception) Chris@0: { Chris@0: $this->setTrace($exception->getTrace(), $exception->getFile(), $exception->getLine()); Chris@0: } Chris@0: Chris@0: public function setTrace($trace, $file, $line) Chris@0: { Chris@17: $this->trace = []; Chris@17: $this->trace[] = [ Chris@0: 'namespace' => '', Chris@0: 'short_class' => '', Chris@0: 'class' => '', Chris@0: 'type' => '', Chris@0: 'function' => '', Chris@0: 'file' => $file, Chris@0: 'line' => $line, Chris@17: 'args' => [], Chris@17: ]; Chris@0: foreach ($trace as $entry) { Chris@0: $class = ''; Chris@0: $namespace = ''; Chris@0: if (isset($entry['class'])) { Chris@0: $parts = explode('\\', $entry['class']); Chris@0: $class = array_pop($parts); Chris@0: $namespace = implode('\\', $parts); Chris@0: } Chris@0: Chris@17: $this->trace[] = [ Chris@0: 'namespace' => $namespace, Chris@0: 'short_class' => $class, Chris@0: 'class' => isset($entry['class']) ? $entry['class'] : '', Chris@0: 'type' => isset($entry['type']) ? $entry['type'] : '', Chris@0: 'function' => isset($entry['function']) ? $entry['function'] : null, Chris@0: 'file' => isset($entry['file']) ? $entry['file'] : null, Chris@0: 'line' => isset($entry['line']) ? $entry['line'] : null, Chris@17: 'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : [], Chris@17: ]; Chris@0: } Chris@0: } Chris@0: Chris@0: private function flattenArgs($args, $level = 0, &$count = 0) Chris@0: { Chris@17: $result = []; Chris@0: foreach ($args as $key => $value) { Chris@0: if (++$count > 1e4) { Chris@17: return ['array', '*SKIPPED over 10000 entries*']; Chris@0: } Chris@0: if ($value instanceof \__PHP_Incomplete_Class) { Chris@0: // is_object() returns false on PHP<=7.1 Chris@17: $result[$key] = ['incomplete-object', $this->getClassNameFromIncomplete($value)]; Chris@17: } elseif (\is_object($value)) { Chris@17: $result[$key] = ['object', \get_class($value)]; Chris@17: } elseif (\is_array($value)) { Chris@0: if ($level > 10) { Chris@17: $result[$key] = ['array', '*DEEP NESTED ARRAY*']; Chris@0: } else { Chris@17: $result[$key] = ['array', $this->flattenArgs($value, $level + 1, $count)]; Chris@0: } Chris@0: } elseif (null === $value) { Chris@17: $result[$key] = ['null', null]; Chris@17: } elseif (\is_bool($value)) { Chris@17: $result[$key] = ['boolean', $value]; Chris@17: } elseif (\is_int($value)) { Chris@17: $result[$key] = ['integer', $value]; Chris@17: } elseif (\is_float($value)) { Chris@17: $result[$key] = ['float', $value]; Chris@17: } elseif (\is_resource($value)) { Chris@17: $result[$key] = ['resource', get_resource_type($value)]; Chris@0: } else { Chris@17: $result[$key] = ['string', (string) $value]; Chris@0: } Chris@0: } Chris@0: Chris@0: return $result; Chris@0: } Chris@0: Chris@0: private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value) Chris@0: { Chris@0: $array = new \ArrayObject($value); Chris@0: Chris@0: return $array['__PHP_Incomplete_Class_Name']; Chris@0: } Chris@0: }