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;
18
19class 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