Completed
Push — master ( 1ef2c9...e2ce1d )
by Jaap
22:01 queued 12:05
created

InvalidTag::flattenExceptionBacktrace()   B

Complexity

Conditions 5
Paths 1

Size

Total Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 39
rs 8.9848
c 0
b 0
f 0
cc 5
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace phpDocumentor\Reflection\DocBlock\Tags;
6
7
use Closure;
8
use phpDocumentor\Reflection\DocBlock\Tag;
9
use ReflectionClass;
10
use ReflectionFunction;
11
use Throwable;
12
use function array_map;
13
use function array_walk_recursive;
14
use function get_class;
15
use function get_resource_type;
16
use function is_object;
17
use function is_resource;
18
use function sprintf;
19
20
/**
21
 * This class represents an exception during the tag creation
22
 *
23
 * Since the internals of the library are relaying on the correct syntax of a docblock
24
 * we cannot simply throw exceptions at all time because the exceptions will break the creation of a
25
 * docklock. Just silently ignore the exceptions is not an option because the user as an issue to fix.
26
 *
27
 * This tag holds that error information until a using application is able to display it. The object wil just behave
28
 * like any normal tag. So the normal application flow will not break.
29
 */
30
final class InvalidTag implements Tag
31
{
32
    /** @var string */
33
    private $name;
34
35
    /** @var string */
36
    private $body;
37
38
    /** @var Throwable|null */
39
    private $throwable;
40
41
    private function __construct(string $name, string $body)
42
    {
43
        $this->name = $name;
44
        $this->body = $body;
45
    }
46
47
    public function getException() : ?Throwable
48
    {
49
        return $this->throwable;
50
    }
51
52
    public function getName() : string
53
    {
54
        return $this->name;
55
    }
56
57
    /**
58
     * @return self
59
     *
60
     * @inheritDoc
61
     */
62
    public static function create(string $body, string $name = '')
63
    {
64
        return new self($name, $body);
65
    }
66
67
    public function withError(Throwable $exception) : self
68
    {
69
        $this->flattenExceptionBacktrace($exception);
70
        $tag            = new self($this->name, $this->body);
71
        $tag->throwable = $exception;
72
73
        return $tag;
74
    }
75
76
    /**
77
     * Removes all complex types from backtrace
78
     *
79
     * Not all objects are serializable. So we need to remove them from the
80
     * stored exception to be sure that we do not break existing library usage.
81
     */
82
    private function flattenExceptionBacktrace(Throwable $exception) : void
83
    {
84
        $traceProperty = (new ReflectionClass('Exception'))->getProperty('trace');
85
        $traceProperty->setAccessible(true);
86
87
        $flatten =
88
            /** @param mixed $value */
89
            static function (&$value) : void {
90
                if ($value instanceof Closure) {
91
                    $closureReflection = new ReflectionFunction($value);
92
                    $value             = sprintf(
93
                        '(Closure at %s:%s)',
94
                        $closureReflection->getFileName(),
95
                        $closureReflection->getStartLine()
96
                    );
97
                } elseif (is_object($value)) {
98
                    $value = sprintf('object(%s)', get_class($value));
99
                } elseif (is_resource($value)) {
100
                    $value = sprintf('resource(%s)', get_resource_type($value));
101
                }
102
            };
103
104
        do {
105
            $trace = array_map(
106
                static function (array $call) use ($flatten) : array {
107
                    $call['args'] = $call['args'] ?? [];
108
109
                    array_walk_recursive($call['args'], $flatten);
110
111
                    return $call;
112
                },
113
                $exception->getTrace()
114
            );
115
            $traceProperty->setValue($exception, $trace);
116
            $exception = $exception->getPrevious();
117
        } while ($exception !== null);
118
119
        $traceProperty->setAccessible(false);
120
    }
121
122
    public function render(?Formatter $formatter = null) : string
123
    {
124
        if ($formatter === null) {
125
            $formatter = new Formatter\PassthroughFormatter();
126
        }
127
128
        return $formatter->format($this);
129
    }
130
131
    public function __toString() : string
132
    {
133
        return $this->body;
134
    }
135
}
136