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\Xml; 12 13use SebastianBergmann\CodeCoverage\CodeCoverage; 14use SebastianBergmann\CodeCoverage\Node\AbstractNode; 15use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; 16use SebastianBergmann\CodeCoverage\Node\File as FileNode; 17use SebastianBergmann\CodeCoverage\RuntimeException; 18use SebastianBergmann\CodeCoverage\Version; 19use SebastianBergmann\Environment\Runtime; 20 21class Facade 22{ 23 /** 24 * @var string 25 */ 26 private $target; 27 28 /** 29 * @var Project 30 */ 31 private $project; 32 33 /** 34 * @var string 35 */ 36 private $phpUnitVersion; 37 38 /** 39 * @param string $version 40 */ 41 public function __construct($version) 42 { 43 $this->phpUnitVersion = $version; 44 } 45 46 /** 47 * @param CodeCoverage $coverage 48 * @param string $target 49 * 50 * @throws RuntimeException 51 */ 52 public function process(CodeCoverage $coverage, $target) 53 { 54 if (substr($target, -1, 1) != DIRECTORY_SEPARATOR) { 55 $target .= DIRECTORY_SEPARATOR; 56 } 57 58 $this->target = $target; 59 $this->initTargetDirectory($target); 60 61 $report = $coverage->getReport(); 62 63 $this->project = new Project( 64 $coverage->getReport()->getName() 65 ); 66 67 $this->setBuildInformation(); 68 $this->processTests($coverage->getTests()); 69 $this->processDirectory($report, $this->project); 70 71 $this->saveDocument($this->project->asDom(), 'index'); 72 } 73 74 private function setBuildInformation() 75 { 76 $buildNode = $this->project->getBuildInformation(); 77 $buildNode->setRuntimeInformation(new Runtime()); 78 $buildNode->setBuildTime(\DateTime::createFromFormat('U', $_SERVER['REQUEST_TIME'])); 79 $buildNode->setGeneratorVersions($this->phpUnitVersion, Version::id()); 80 } 81 82 /** 83 * @param string $directory 84 */ 85 protected function initTargetDirectory($directory) 86 { 87 if (file_exists($directory)) { 88 if (!is_dir($directory)) { 89 throw new RuntimeException( 90 "'$directory' exists but is not a directory." 91 ); 92 } 93 94 if (!is_writable($directory)) { 95 throw new RuntimeException( 96 "'$directory' exists but is not writable." 97 ); 98 } 99 } elseif (!@mkdir($directory, 0777, true)) { 100 throw new RuntimeException( 101 "'$directory' could not be created." 102 ); 103 } 104 } 105 106 private function processDirectory(DirectoryNode $directory, Node $context) 107 { 108 $dirname = $directory->getName(); 109 if ($this->project->getProjectSourceDirectory() === $dirname) { 110 $dirname = '/'; 111 } 112 $dirObject = $context->addDirectory($dirname); 113 114 $this->setTotals($directory, $dirObject->getTotals()); 115 116 foreach ($directory->getDirectories() as $node) { 117 $this->processDirectory($node, $dirObject); 118 } 119 120 foreach ($directory->getFiles() as $node) { 121 $this->processFile($node, $dirObject); 122 } 123 } 124 125 private function processFile(FileNode $file, Directory $context) 126 { 127 $fileObject = $context->addFile( 128 $file->getName(), 129 $file->getId() . '.xml' 130 ); 131 132 $this->setTotals($file, $fileObject->getTotals()); 133 134 $path = substr( 135 $file->getPath(), 136 strlen($this->project->getProjectSourceDirectory()) 137 ); 138 $fileReport = new Report($path); 139 140 $this->setTotals($file, $fileReport->getTotals()); 141 142 foreach ($file->getClassesAndTraits() as $unit) { 143 $this->processUnit($unit, $fileReport); 144 } 145 146 foreach ($file->getFunctions() as $function) { 147 $this->processFunction($function, $fileReport); 148 } 149 150 foreach ($file->getCoverageData() as $line => $tests) { 151 if (!is_array($tests) || count($tests) === 0) { 152 continue; 153 } 154 155 $coverage = $fileReport->getLineCoverage($line); 156 157 foreach ($tests as $test) { 158 $coverage->addTest($test); 159 } 160 161 $coverage->finalize(); 162 } 163 164 $fileReport->getSource()->setSourceCode( 165 file_get_contents($file->getPath()) 166 ); 167 168 $this->saveDocument($fileReport->asDom(), $file->getId()); 169 } 170 171 private function processUnit($unit, Report $report) 172 { 173 if (isset($unit['className'])) { 174 $unitObject = $report->getClassObject($unit['className']); 175 } else { 176 $unitObject = $report->getTraitObject($unit['traitName']); 177 } 178 179 $unitObject->setLines( 180 $unit['startLine'], 181 $unit['executableLines'], 182 $unit['executedLines'] 183 ); 184 185 $unitObject->setCrap($unit['crap']); 186 187 $unitObject->setPackage( 188 $unit['package']['fullPackage'], 189 $unit['package']['package'], 190 $unit['package']['subpackage'], 191 $unit['package']['category'] 192 ); 193 194 $unitObject->setNamespace($unit['package']['namespace']); 195 196 foreach ($unit['methods'] as $method) { 197 $methodObject = $unitObject->addMethod($method['methodName']); 198 $methodObject->setSignature($method['signature']); 199 $methodObject->setLines($method['startLine'], $method['endLine']); 200 $methodObject->setCrap($method['crap']); 201 $methodObject->setTotals( 202 $method['executableLines'], 203 $method['executedLines'], 204 $method['coverage'] 205 ); 206 } 207 } 208 209 private function processFunction($function, Report $report) 210 { 211 $functionObject = $report->getFunctionObject($function['functionName']); 212 213 $functionObject->setSignature($function['signature']); 214 $functionObject->setLines($function['startLine']); 215 $functionObject->setCrap($function['crap']); 216 $functionObject->setTotals($function['executableLines'], $function['executedLines'], $function['coverage']); 217 } 218 219 private function processTests(array $tests) 220 { 221 $testsObject = $this->project->getTests(); 222 223 foreach ($tests as $test => $result) { 224 if ($test == 'UNCOVERED_FILES_FROM_WHITELIST') { 225 continue; 226 } 227 228 $testsObject->addTest($test, $result); 229 } 230 } 231 232 private function setTotals(AbstractNode $node, Totals $totals) 233 { 234 $loc = $node->getLinesOfCode(); 235 236 $totals->setNumLines( 237 $loc['loc'], 238 $loc['cloc'], 239 $loc['ncloc'], 240 $node->getNumExecutableLines(), 241 $node->getNumExecutedLines() 242 ); 243 244 $totals->setNumClasses( 245 $node->getNumClasses(), 246 $node->getNumTestedClasses() 247 ); 248 249 $totals->setNumTraits( 250 $node->getNumTraits(), 251 $node->getNumTestedTraits() 252 ); 253 254 $totals->setNumMethods( 255 $node->getNumMethods(), 256 $node->getNumTestedMethods() 257 ); 258 259 $totals->setNumFunctions( 260 $node->getNumFunctions(), 261 $node->getNumTestedFunctions() 262 ); 263 } 264 265 /** 266 * @return string 267 */ 268 protected function getTargetDirectory() 269 { 270 return $this->target; 271 } 272 273 protected function saveDocument(\DOMDocument $document, $name) 274 { 275 $filename = sprintf('%s/%s.xml', $this->getTargetDirectory(), $name); 276 277 $document->formatOutput = true; 278 $document->preserveWhiteSpace = false; 279 $this->initTargetDirectory(dirname($filename)); 280 281 $document->save($filename); 282 } 283} 284