Passed
Pull Request — master (#15)
by ANTHONIUS
03:02
created

Session::refresh()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 0
dl 0
loc 11
ccs 0
cts 10
cp 0
crap 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the doyo/code-coverage project.
5
 *
6
 * (c) Anthonius Munthi <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Doyo\Bridge\CodeCoverage\Session;
15
16
use Doyo\Bridge\CodeCoverage\Exception\SessionException;
17
use Doyo\Bridge\CodeCoverage\Processor;
18
use Doyo\Bridge\CodeCoverage\ProcessorInterface;
19
use Doyo\Bridge\CodeCoverage\TestCase;
20
use SebastianBergmann\CodeCoverage\CodeCoverage;
21
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
22
23
abstract class Session implements \Serializable, SessionInterface
24
{
25
    const CACHE_KEY = 'session';
26
27
    /**
28
     * @var string|null
29
     */
30
    protected $name;
31
32
    /**
33
     * @var TestCase
34
     */
35
    protected $testCase;
36
37
    /**
38
     * @var FilesystemAdapter|null
39
     */
40
    protected $adapter;
41
42
    /**
43
     * @var array
44
     */
45
    protected $data = [];
46
47
    /**
48
     * @var ProcessorInterface
49
     */
50
    protected $processor;
51
52
    /**
53
     * @var \Exception[]
54
     */
55
    protected $exceptions = [];
56
57
    /**
58
     * @var bool
59
     */
60
    protected $hasStarted = false;
61
62
    /**
63
     * Code coverage for this session.
64
     *
65
     * @var CodeCoverage
66
     */
67
    protected $codeCoverage;
68
69
    /**
70
     * @var array
71
     */
72
    protected $config;
73
74
    public function __construct($name, array $config = array())
75
    {
76
        $dir = sys_get_temp_dir().'/doyo/behat-coverage-extension';
77
        $adapter = new FilesystemAdapter($name, 0, $dir);
78
        $this->adapter = $adapter;
79
        $this->name = $name;
80
        $this->config = $config;
81
        $this->refresh();
82
    }
83
84
    /**
85
     * {@inheritdoc}
86
     */
87
    public function reset()
88
    {
89
        $this->testCase   = null;
90
        $this->exceptions = [];
91
        $this->processor->clear();
92
93
        $this->save();
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99
    public function serialize()
100
    {
101
        $data = [
102
            $this->name,
103
            $this->testCase,
104
            $this->exceptions,
105
            $this->processor,
106
            $this->patchXdebug,
107
        ];
108
109
        return serialize($data);
110
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115
    public function unserialize($serialized)
116
    {
117
        list(
118
            $this->name,
119
            $this->testCase,
120
            $this->exceptions,
121
            $this->processor,
122
            $this->patchXdebug
0 ignored issues
show
Bug Best Practice introduced by
The property patchXdebug does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
123
        ) = unserialize($serialized);
124
    }
125
126
    /**
127
     * {@inheritdoc}
128
     */
129
    public function refresh()
130
    {
131
        $adapter = $this->adapter;
132
        $cached  = $adapter->getItem(static::CACHE_KEY)->get();
0 ignored issues
show
Bug introduced by
The method getItem() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

132
        $cached  = $adapter->/** @scrutinizer ignore-call */ getItem(static::CACHE_KEY)->get();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
133
134
        if ($cached instanceof self) {
135
            $this->name        = $cached->getName();
136
            $this->testCase    = $cached->getTestCase();
137
            $this->exceptions  = $cached->getExceptions();
138
            $this->processor   = $cached->getProcessor();
139
            $this->patchXdebug = $cached->getPatchXdebug();
0 ignored issues
show
Bug Best Practice introduced by
The property patchXdebug does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
140
        }
141
    }
142
143
    public function setPatchXdebug(bool $flag)
144
    {
145
        $this->patchXdebug = $flag;
0 ignored issues
show
Bug Best Practice introduced by
The property patchXdebug does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
146
    }
147
148
    /**
149
     * @return bool
150
     */
151
    public function getPatchXdebug(): bool
152
    {
153
        return $this->patchXdebug;
154
    }
155
156
    /**
157
     * @return string|null
158
     */
159
    public function getName()
160
    {
161
        return $this->name;
162
    }
163
164
    /**
165
     * @return TestCase
166
     */
167
    public function getTestCase()
168
    {
169
        return $this->testCase;
170
    }
171
172
    /**
173
     * {@inheritdoc}
174
     */
175
    public function setTestCase(TestCase $testCase = null)
176
    {
177
        $this->testCase = $testCase;
178
179
        return $this;
180
    }
181
182
    /**
183
     * @return FilesystemAdapter|null
184
     */
185
    public function getAdapter(): FilesystemAdapter
186
    {
187
        return $this->adapter;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->adapter could return the type null which is incompatible with the type-hinted return Symfony\Component\Cache\Adapter\FilesystemAdapter. Consider adding an additional type-check to rule them out.
Loading history...
188
    }
189
190
    /**
191
     * @param FilesystemAdapter|null $adapter
192
     */
193
    public function setAdapter(FilesystemAdapter $adapter)
194
    {
195
        $this->adapter = $adapter;
196
    }
197
198
    /**
199
     * @param ProcessorInterface $processor
200
     */
201
    public function setProcessor(ProcessorInterface $processor = null)
202
    {
203
        $this->processor = $processor;
204
    }
205
206
    /**
207
     * @return ProcessorInterface|null
208
     */
209
    public function getProcessor()
210
    {
211
        return $this->processor;
212
    }
213
214
    /**
215
     * {@inheritdoc}
216
     */
217
    public function hasExceptions()
218
    {
219
        return \count($this->exceptions) > 0;
220
    }
221
222
    /**
223
     * {@inheritdoc}
224
     */
225
    public function getExceptions()
226
    {
227
        return $this->exceptions;
228
    }
229
230
    /**
231
     * @param \Exception $e
232
     */
233
    public function addException(\Exception $e)
234
    {
235
        $id                    = md5($e->getMessage());
236
        $this->exceptions[$id] = $e;
237
    }
238
239
    /**
240
     * @codeCoverageIgnore
241
     */
242
    public function xdebugPatch()
243
    {
244
        if (!$this->patchXdebug) {
245
            return;
246
        }
247
248
        $filter    = $this->getProcessor()->getCodeCoverageFilter();
249
        $options   = $filter->getWhitelistedFiles();
250
        $filterKey = 'whitelistedFiles';
251
252
        if (
253
            !\extension_loaded('xdebug')
254
            || !\function_exists('xdebug_set_filter')
255
            || !isset($options[$filterKey])
256
        ) {
257
            return;
258
        }
259
260
        $dirs = [];
261
        foreach ($options[$filterKey] as $fileName => $status) {
262
            $dir = \dirname($fileName);
263
            if (!\in_array($dir, $dirs, true)) {
264
                $dirs[] = $dir;
265
            }
266
        }
267
268
        xdebug_set_filter(
269
            XDEBUG_FILTER_CODE_COVERAGE,
0 ignored issues
show
Bug introduced by
The constant Doyo\Bridge\CodeCoverage...UG_FILTER_CODE_COVERAGE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
270
            XDEBUG_PATH_WHITELIST,
0 ignored issues
show
Bug introduced by
The constant Doyo\Bridge\CodeCoverage...n\XDEBUG_PATH_WHITELIST was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
271
            $dirs
272
        );
273
    }
274
275
    /**
276
     * {@inheritdoc}
277
     */
278
    final public function start($driver = null)
279
    {
280
        if (null === $this->testCase) {
281
            return;
282
        }
283
        try {
284
            $this->hasStarted   = false;
285
            $this->codeCoverage = $this->createCodeCoverage($driver);
286
            $this->xdebugPatch();
287
            $this->codeCoverage->start($this->testCase->getName());
288
            $this->hasStarted = true;
289
        } catch (\Exception $e) {
290
            $message = sprintf(
291
                "Can not start code coverage with error message:\n%s",
292
                $e->getMessage()
293
            );
294
            $exception = new SessionException($message);
295
            throw $exception;
296
        }
297
    }
298
299
    public function stop()
300
    {
301
        try {
302
            $codeCoverage = $this->codeCoverage;
303
            $processor    = $this->processor;
304
305
            $codeCoverage->stop();
306
307
            $processor->merge($codeCoverage);
308
        } catch (\Exception $e) {
309
            throw new SessionException(
310
                sprintf(
311
                    'Failed to stop coverage for session %s: %s',
312
                    $this->name,
313
                    $e->getMessage()
314
                )
315
            );
316
        }
317
        $this->hasStarted = false;
318
    }
319
320
    /**
321
     * {@inheritdoc}
322
     */
323
    public function save()
324
    {
325
        $adapter = $this->adapter;
326
        $item    = $adapter->getItem(static::CACHE_KEY);
327
328
        $item->set($this);
329
        $adapter->save($item);
330
    }
331
332
    public function shutdown()
333
    {
334
        if ($this->hasStarted && null !== $this->processor) {
335
            try {
336
                $this->stop();
337
            } catch (\Exception $e) {
338
                $this->addException(new SessionException($e->getMessage()));
339
            }
340
        }
341
        $this->hasStarted   = false;
342
        $this->save();
343
    }
344
345
    /**
346
     * @param mixed|null $driver
347
     *
348
     * @return CodeCoverage
349
     */
350
    protected function createCodeCoverage($driver): CodeCoverage
351
    {
352
        $filter   = $this->processor->getCodeCoverageFilter();
353
        $options  = $this->processor->getCodeCoverageOptions();
354
        $coverage = new CodeCoverage($driver, $filter);
355
        foreach ($options as $method => $option) {
356
            if (method_exists($coverage, $method)) {
357
                $method = 'set'.ucfirst($method);
358
                \call_user_func_array([$coverage, $method], [$option]);
359
            }
360
        }
361
362
        return $coverage;
363
    }
364
}
365