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