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\Report;
12
13use SebastianBergmann\CodeCoverage\CodeCoverage;
14use SebastianBergmann\CodeCoverage\Node\File;
15
16/**
17 * Generates a Clover XML logfile from a code coverage object.
18 */
19class Clover
20{
21    /**
22     * @param CodeCoverage $coverage
23     * @param string       $target
24     * @param string       $name
25     *
26     * @return string
27     */
28    public function process(CodeCoverage $coverage, $target = null, $name = null)
29    {
30        $xmlDocument               = new \DOMDocument('1.0', 'UTF-8');
31        $xmlDocument->formatOutput = true;
32
33        $xmlCoverage = $xmlDocument->createElement('coverage');
34        $xmlCoverage->setAttribute('generated', (int) $_SERVER['REQUEST_TIME']);
35        $xmlDocument->appendChild($xmlCoverage);
36
37        $xmlProject = $xmlDocument->createElement('project');
38        $xmlProject->setAttribute('timestamp', (int) $_SERVER['REQUEST_TIME']);
39
40        if (is_string($name)) {
41            $xmlProject->setAttribute('name', $name);
42        }
43
44        $xmlCoverage->appendChild($xmlProject);
45
46        $packages = [];
47        $report   = $coverage->getReport();
48        unset($coverage);
49
50        foreach ($report as $item) {
51            if (!$item instanceof File) {
52                continue;
53            }
54
55            /* @var File $item */
56
57            $xmlFile = $xmlDocument->createElement('file');
58            $xmlFile->setAttribute('name', $item->getPath());
59
60            $classes   = $item->getClassesAndTraits();
61            $coverage  = $item->getCoverageData();
62            $lines     = [];
63            $namespace = 'global';
64
65            foreach ($classes as $className => $class) {
66                $classStatements        = 0;
67                $coveredClassStatements = 0;
68                $coveredMethods         = 0;
69                $classMethods           = 0;
70
71                foreach ($class['methods'] as $methodName => $method) {
72                    if ($method['executableLines']  == 0) {
73                        continue;
74                    }
75
76                    $classMethods++;
77                    $classStatements        += $method['executableLines'];
78                    $coveredClassStatements += $method['executedLines'];
79
80                    if ($method['coverage'] == 100) {
81                        $coveredMethods++;
82                    }
83
84                    $methodCount = 0;
85
86                    foreach (range($method['startLine'], $method['endLine']) as $line) {
87                        if (isset($coverage[$line]) && ($coverage[$line] !== null)) {
88                            $methodCount = max($methodCount, count($coverage[$line]));
89                        }
90                    }
91
92                    $lines[$method['startLine']] = [
93                        'ccn'         => $method['ccn'],
94                        'count'       => $methodCount,
95                        'crap'        => $method['crap'],
96                        'type'        => 'method',
97                        'visibility'  => $method['visibility'],
98                        'name'        => $methodName
99                    ];
100                }
101
102                if (!empty($class['package']['namespace'])) {
103                    $namespace = $class['package']['namespace'];
104                }
105
106                $xmlClass = $xmlDocument->createElement('class');
107                $xmlClass->setAttribute('name', $className);
108                $xmlClass->setAttribute('namespace', $namespace);
109
110                if (!empty($class['package']['fullPackage'])) {
111                    $xmlClass->setAttribute(
112                        'fullPackage',
113                        $class['package']['fullPackage']
114                    );
115                }
116
117                if (!empty($class['package']['category'])) {
118                    $xmlClass->setAttribute(
119                        'category',
120                        $class['package']['category']
121                    );
122                }
123
124                if (!empty($class['package']['package'])) {
125                    $xmlClass->setAttribute(
126                        'package',
127                        $class['package']['package']
128                    );
129                }
130
131                if (!empty($class['package']['subpackage'])) {
132                    $xmlClass->setAttribute(
133                        'subpackage',
134                        $class['package']['subpackage']
135                    );
136                }
137
138                $xmlFile->appendChild($xmlClass);
139
140                $xmlMetrics = $xmlDocument->createElement('metrics');
141                $xmlMetrics->setAttribute('complexity', $class['ccn']);
142                $xmlMetrics->setAttribute('methods', $classMethods);
143                $xmlMetrics->setAttribute('coveredmethods', $coveredMethods);
144                $xmlMetrics->setAttribute('conditionals', 0);
145                $xmlMetrics->setAttribute('coveredconditionals', 0);
146                $xmlMetrics->setAttribute('statements', $classStatements);
147                $xmlMetrics->setAttribute('coveredstatements', $coveredClassStatements);
148                $xmlMetrics->setAttribute('elements', $classMethods + $classStatements /* + conditionals */);
149                $xmlMetrics->setAttribute('coveredelements', $coveredMethods + $coveredClassStatements /* + coveredconditionals */);
150                $xmlClass->appendChild($xmlMetrics);
151            }
152
153            foreach ($coverage as $line => $data) {
154                if ($data === null || isset($lines[$line])) {
155                    continue;
156                }
157
158                $lines[$line] = [
159                    'count' => count($data), 'type' => 'stmt'
160                ];
161            }
162
163            ksort($lines);
164
165            foreach ($lines as $line => $data) {
166                $xmlLine = $xmlDocument->createElement('line');
167                $xmlLine->setAttribute('num', $line);
168                $xmlLine->setAttribute('type', $data['type']);
169
170                if (isset($data['name'])) {
171                    $xmlLine->setAttribute('name', $data['name']);
172                }
173
174                if (isset($data['visibility'])) {
175                    $xmlLine->setAttribute('visibility', $data['visibility']);
176                }
177
178                if (isset($data['ccn'])) {
179                    $xmlLine->setAttribute('complexity', $data['ccn']);
180                }
181
182                if (isset($data['crap'])) {
183                    $xmlLine->setAttribute('crap', $data['crap']);
184                }
185
186                $xmlLine->setAttribute('count', $data['count']);
187                $xmlFile->appendChild($xmlLine);
188            }
189
190            $linesOfCode = $item->getLinesOfCode();
191
192            $xmlMetrics = $xmlDocument->createElement('metrics');
193            $xmlMetrics->setAttribute('loc', $linesOfCode['loc']);
194            $xmlMetrics->setAttribute('ncloc', $linesOfCode['ncloc']);
195            $xmlMetrics->setAttribute('classes', $item->getNumClassesAndTraits());
196            $xmlMetrics->setAttribute('methods', $item->getNumMethods());
197            $xmlMetrics->setAttribute('coveredmethods', $item->getNumTestedMethods());
198            $xmlMetrics->setAttribute('conditionals', 0);
199            $xmlMetrics->setAttribute('coveredconditionals', 0);
200            $xmlMetrics->setAttribute('statements', $item->getNumExecutableLines());
201            $xmlMetrics->setAttribute('coveredstatements', $item->getNumExecutedLines());
202            $xmlMetrics->setAttribute('elements', $item->getNumMethods() + $item->getNumExecutableLines() /* + conditionals */);
203            $xmlMetrics->setAttribute('coveredelements', $item->getNumTestedMethods() + $item->getNumExecutedLines() /* + coveredconditionals */);
204            $xmlFile->appendChild($xmlMetrics);
205
206            if ($namespace == 'global') {
207                $xmlProject->appendChild($xmlFile);
208            } else {
209                if (!isset($packages[$namespace])) {
210                    $packages[$namespace] = $xmlDocument->createElement(
211                        'package'
212                    );
213
214                    $packages[$namespace]->setAttribute('name', $namespace);
215                    $xmlProject->appendChild($packages[$namespace]);
216                }
217
218                $packages[$namespace]->appendChild($xmlFile);
219            }
220        }
221
222        $linesOfCode = $report->getLinesOfCode();
223
224        $xmlMetrics = $xmlDocument->createElement('metrics');
225        $xmlMetrics->setAttribute('files', count($report));
226        $xmlMetrics->setAttribute('loc', $linesOfCode['loc']);
227        $xmlMetrics->setAttribute('ncloc', $linesOfCode['ncloc']);
228        $xmlMetrics->setAttribute('classes', $report->getNumClassesAndTraits());
229        $xmlMetrics->setAttribute('methods', $report->getNumMethods());
230        $xmlMetrics->setAttribute('coveredmethods', $report->getNumTestedMethods());
231        $xmlMetrics->setAttribute('conditionals', 0);
232        $xmlMetrics->setAttribute('coveredconditionals', 0);
233        $xmlMetrics->setAttribute('statements', $report->getNumExecutableLines());
234        $xmlMetrics->setAttribute('coveredstatements', $report->getNumExecutedLines());
235        $xmlMetrics->setAttribute('elements', $report->getNumMethods() + $report->getNumExecutableLines() /* + conditionals */);
236        $xmlMetrics->setAttribute('coveredelements', $report->getNumTestedMethods() + $report->getNumExecutedLines() /* + coveredconditionals */);
237        $xmlProject->appendChild($xmlMetrics);
238
239        $buffer = $xmlDocument->saveXML();
240
241        if ($target !== null) {
242            if (!is_dir(dirname($target))) {
243                mkdir(dirname($target), 0777, true);
244            }
245
246            file_put_contents($target, $buffer);
247        }
248
249        return $buffer;
250    }
251}
252