Passed
Push — test ( fa7c9a...75ff79 )
by Tom
02:41
created

ExceptionHandler::showError()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 2
nc 2
nop 2
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 3
rs 10
c 1
b 0
f 0
1
<?php
2
3
/* this file is part of pipelines */
4
5
namespace Ktomk\Pipelines\Utility;
6
7
use Exception;
8
use Ktomk\Pipelines\Cli\ArgsException;
9
use Ktomk\Pipelines\Cli\Streams;
10
use Ktomk\Pipelines\File\ParseException;
11
12
class ExceptionHandler
13
{
14
    /**
15
     * @var bool
16
     */
17
    private $showStacktraceFlag;
18
19
    /**
20
     * @var Help
21
     */
22
    private $help;
23
    /**
24
     * @var Streams
25
     */
26
    private $streams;
27
28
    /**
29
     * ExceptionHandler constructor.
30
     *
31
     * @param Streams $streams
32
     * @param Help $help
33
     * @param bool $showStacktrace
34
     */
35 1
    public function __construct(Streams $streams, Help $help, $showStacktrace)
36
    {
37 1
        $this->streams = $streams;
38 1
        $this->help = $help;
39 1
        $this->showStacktraceFlag = $showStacktrace;
40 1
    }
41
42
    /**
43
     * @param Runnable $runnable
44
     *
45
     * @return null|mixed
46
     */
47 4
    public function handle(Runnable $runnable)
48
    {
49
        try {
50 4
            $status = $runnable->run();
51 2
        } catch (Exception $e) {
52 2
            $status = $this->handleException($e);
53
        }
54
55 4
        return $status;
56
    }
57
58
    /**
59
     * @param StatusRunnable $runnable
60
     *
61
     * @return int
62
     */
63 1
    public function handleStatus(StatusRunnable $runnable)
64
    {
65 1
        return (int)$this->handle($runnable);
66
    }
67
68
    /**
69
     * @param Exception $e
70
     *
71
     * @return int
72
     */
73 2
    private function handleException(Exception $e)
74
    {
75 2
        $status = $e->getCode();
76 2
        $message = $e->getMessage();
77
78
        // "catch" unexpected exceptions for user-friendly message
79 2
        if (!is_int($status) || $this->isUnexpectedException($e)) {
80 1
            $status = 2;
81 1
            $message = sprintf('fatal: %s', $e->getMessage());
82
        }
83
84 2
        $this->showError($status, $message);
85 2
        $this->showUsage($e);
86 2
        $this->showStacktrace($e);
87
88 2
        return $status;
89
    }
90
91
    /**
92
     * Show error message
93
     *
94
     * @param int $status
95
     * @param string $message
96
     *
97
     * @return void
98
     */
99 2
    private function showError($status, $message)
100
    {
101 2
        if (0 !== $status && '' !== $message) {
102 2
            $this->error(sprintf('pipelines: %s', $message));
103
        }
104 2
    }
105
106
    /**
107
     * Some exceptions can show usage
108
     *
109
     * @param Exception $e
110
     *
111
     * @return void
112
     */
113 2
    private function showUsage(Exception $e)
114
    {
115 2
        if ($e instanceof ArgsException) {
116 1
            $this->help->showUsage();
117
        }
118 2
    }
119
120
    /**
121
     * Show a stacktrace for debugging purposes (`--debug`` flag)
122
     *
123
     * @param Exception $e
124
     *
125
     * @return void
126
     */
127 2
    private function showStacktrace(Exception $e)
128
    {
129 2
        if ($this->showStacktraceFlag) {
130 2
            $this->debugException($e);
131
        }
132 2
    }
133
134
    /**
135
     * Some exceptions are not unexpected
136
     *
137
     * The pipelines utility uses *some* exceptions to signal program
138
     * flow in context of the command line utility. E.g. to not call
139
     * exit() somewhere deep in the code (StatusException), lazy command
140
     * line argument handling (ArgsException) and lazy pipelines.yml
141
     * file parsing (ParseException).
142
     *
143
     * @param Exception $e
144
     *
145
     * @return bool
146
     */
147 2
    private function isUnexpectedException(Exception $e)
148
    {
149
        return (
150 2
            !($e instanceof ArgsException)
151 2
            && !($e instanceof StatusException)
152 2
            && !($e instanceof ParseException)
153
        );
154
    }
155
156
    /**
157
     * @param Exception $e
158
     *
159
     * @return void
160
     */
161 2
    private function debugException(Exception $e)
162
    {
163 2
        $this->debugInfoOfPipelinesItself();
164
165 2
        for (; $e; $e = $e->getPrevious()) {
166 2
            $this->error('--------');
167 2
            $this->error(sprintf('class....: %s', get_class($e)));
168 2
            $this->error(sprintf('message..: %s', $e->getMessage()));
169 2
            $this->error(sprintf('code.....: %s', $e->getCode()));
170 2
            $this->error(sprintf('file.....: %s', $e->getFile()));
171 2
            $this->error(sprintf('line.....: %s', $e->getLine()));
172 2
            $this->error('backtrace:');
173 2
            $this->error($e->getTraceAsString());
174
        }
175 2
        $this->error('--------');
176 2
    }
177
178 2
    private function debugInfoOfPipelinesItself()
179
    {
180 2
        $this->error(sprintf(
181 2
            'pipelines: version %s w/ php %s (libyaml: %s)',
182 2
            Version::resolve(App::VERSION),
183 2
            PHP_VERSION,
184 2
            \phpversion('yaml') ?: 'n/a'
185
        ));
186 2
    }
187
188
    /**
189
     * @param string $message
190
     *
191
     * @return void
192
     */
193 2
    private function error($message)
194
    {
195 2
        $this->streams->err(
196 2
            sprintf("%s\n", $message)
197
        );
198 2
    }
199
}
200