1<?php
2/*
3 * This file is part of the php-code-coverage package.
4 *
5 * (c) Sebastian Bergmann <sebastian@phpunit.de>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10
11namespace SebastianBergmann\CodeCoverage\Driver;
12
13use SebastianBergmann\CodeCoverage\RuntimeException;
14
15/**
16 * Driver for Xdebug's code coverage functionality.
17 *
18 * @codeCoverageIgnore
19 */
20class Xdebug implements Driver
21{
22    /**
23     * Cache the number of lines for each file
24     *
25     * @var array
26     */
27    private $cacheNumLines = [];
28
29    /**
30     * Constructor.
31     */
32    public function __construct()
33    {
34        if (!extension_loaded('xdebug')) {
35            throw new RuntimeException('This driver requires Xdebug');
36        }
37
38        if (version_compare(phpversion('xdebug'), '2.2.1', '>=') &&
39            !ini_get('xdebug.coverage_enable')) {
40            throw new RuntimeException(
41                'xdebug.coverage_enable=On has to be set in php.ini'
42            );
43        }
44    }
45
46    /**
47     * Start collection of code coverage information.
48     *
49     * @param bool $determineUnusedAndDead
50     */
51    public function start($determineUnusedAndDead = true)
52    {
53        if ($determineUnusedAndDead) {
54            xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);
55        } else {
56            xdebug_start_code_coverage();
57        }
58    }
59
60    /**
61     * Stop collection of code coverage information.
62     *
63     * @return array
64     */
65    public function stop()
66    {
67        $data = xdebug_get_code_coverage();
68        xdebug_stop_code_coverage();
69
70        return $this->cleanup($data);
71    }
72
73    /**
74     * @param array $data
75     *
76     * @return array
77     */
78    private function cleanup(array $data)
79    {
80        foreach (array_keys($data) as $file) {
81            unset($data[$file][0]);
82
83            if (strpos($file, 'xdebug://debug-eval') !== 0 && file_exists($file)) {
84                $numLines = $this->getNumberOfLinesInFile($file);
85
86                foreach (array_keys($data[$file]) as $line) {
87                    if ($line > $numLines) {
88                        unset($data[$file][$line]);
89                    }
90                }
91            }
92        }
93
94        return $data;
95    }
96
97    /**
98     * @param string $file
99     *
100     * @return int
101     */
102    private function getNumberOfLinesInFile($file)
103    {
104        if (!isset($this->cacheNumLines[$file])) {
105            $buffer = file_get_contents($file);
106            $lines  = substr_count($buffer, "\n");
107
108            if (substr($buffer, -1) !== "\n") {
109                $lines++;
110            }
111
112            $this->cacheNumLines[$file] = $lines;
113        }
114
115        return $this->cacheNumLines[$file];
116    }
117}
118