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