8889841cSource.php000066600000002173150515552240006530 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\CodeCoverage\Report\Xml; use DOMElement; use TheSeer\Tokenizer\NamespaceUri; use TheSeer\Tokenizer\Tokenizer; use TheSeer\Tokenizer\XMLSerializer; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ final class Source { /** @var DOMElement */ private $context; public function __construct(DOMElement $context) { $this->context = $context; } public function setSourceCode(string $source): void { $context = $this->context; $tokens = (new Tokenizer)->parse($source); $srcDom = (new XMLSerializer(new NamespaceUri($context->namespaceURI)))->toDom($tokens); $context->parentNode->replaceChild( $context->ownerDocument->importNode($srcDom->documentElement, true), $context ); } } Unit.php000066600000004133150515552240006205 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\CodeCoverage\Report\Xml; use DOMElement; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ final class Unit { /** * @var DOMElement */ private $contextNode; public function __construct(DOMElement $context, string $name) { $this->contextNode = $context; $this->setName($name); } public function setLines(int $start, int $executable, int $executed): void { $this->contextNode->setAttribute('start', (string) $start); $this->contextNode->setAttribute('executable', (string) $executable); $this->contextNode->setAttribute('executed', (string) $executed); } public function setCrap(float $crap): void { $this->contextNode->setAttribute('crap', (string) $crap); } public function setNamespace(string $namespace): void { $node = $this->contextNode->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', 'namespace' )->item(0); if (!$node) { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'namespace' ) ); } $node->setAttribute('name', $namespace); } public function addMethod(string $name): Method { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'method' ) ); return new Method($node, $name); } private function setName(string $name): void { $this->contextNode->setAttribute('name', $name); } } Totals.php000066600000010543150515552240006536 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\CodeCoverage\Report\Xml; use function sprintf; use DOMElement; use DOMNode; use SebastianBergmann\CodeCoverage\Util\Percentage; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ final class Totals { /** * @var DOMNode */ private $container; /** * @var DOMElement */ private $linesNode; /** * @var DOMElement */ private $methodsNode; /** * @var DOMElement */ private $functionsNode; /** * @var DOMElement */ private $classesNode; /** * @var DOMElement */ private $traitsNode; public function __construct(DOMElement $container) { $this->container = $container; $dom = $container->ownerDocument; $this->linesNode = $dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'lines' ); $this->methodsNode = $dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'methods' ); $this->functionsNode = $dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'functions' ); $this->classesNode = $dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'classes' ); $this->traitsNode = $dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'traits' ); $container->appendChild($this->linesNode); $container->appendChild($this->methodsNode); $container->appendChild($this->functionsNode); $container->appendChild($this->classesNode); $container->appendChild($this->traitsNode); } public function container(): DOMNode { return $this->container; } public function setNumLines(int $loc, int $cloc, int $ncloc, int $executable, int $executed): void { $this->linesNode->setAttribute('total', (string) $loc); $this->linesNode->setAttribute('comments', (string) $cloc); $this->linesNode->setAttribute('code', (string) $ncloc); $this->linesNode->setAttribute('executable', (string) $executable); $this->linesNode->setAttribute('executed', (string) $executed); $this->linesNode->setAttribute( 'percent', $executable === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($executed, $executable)->asFloat()) ); } public function setNumClasses(int $count, int $tested): void { $this->classesNode->setAttribute('count', (string) $count); $this->classesNode->setAttribute('tested', (string) $tested); $this->classesNode->setAttribute( 'percent', $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat()) ); } public function setNumTraits(int $count, int $tested): void { $this->traitsNode->setAttribute('count', (string) $count); $this->traitsNode->setAttribute('tested', (string) $tested); $this->traitsNode->setAttribute( 'percent', $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat()) ); } public function setNumMethods(int $count, int $tested): void { $this->methodsNode->setAttribute('count', (string) $count); $this->methodsNode->setAttribute('tested', (string) $tested); $this->methodsNode->setAttribute( 'percent', $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat()) ); } public function setNumFunctions(int $count, int $tested): void { $this->functionsNode->setAttribute('count', (string) $count); $this->functionsNode->setAttribute('tested', (string) $tested); $this->functionsNode->setAttribute( 'percent', $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat()) ); } } Coverage.php000066600000003476150515552240007032 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\CodeCoverage\Report\Xml; use DOMElement; use SebastianBergmann\CodeCoverage\ReportAlreadyFinalizedException; use XMLWriter; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ final class Coverage { /** * @var XMLWriter */ private $writer; /** * @var DOMElement */ private $contextNode; /** * @var bool */ private $finalized = false; public function __construct(DOMElement $context, string $line) { $this->contextNode = $context; $this->writer = new XMLWriter; $this->writer->openMemory(); $this->writer->startElementNS(null, $context->nodeName, 'https://schema.phpunit.de/coverage/1.0'); $this->writer->writeAttribute('nr', $line); } /** * @throws ReportAlreadyFinalizedException */ public function addTest(string $test): void { if ($this->finalized) { throw new ReportAlreadyFinalizedException; } $this->writer->startElement('covered'); $this->writer->writeAttribute('by', $test); $this->writer->endElement(); } public function finalize(): void { $this->writer->endElement(); $fragment = $this->contextNode->ownerDocument->createDocumentFragment(); $fragment->appendXML($this->writer->outputMemory()); $this->contextNode->parentNode->replaceChild( $fragment, $this->contextNode ); $this->finalized = true; } } File.php000066600000004107150515552240006146 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\CodeCoverage\Report\Xml; use DOMDocument; use DOMElement; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ class File { /** * @var DOMDocument */ private $dom; /** * @var DOMElement */ private $contextNode; public function __construct(DOMElement $context) { $this->dom = $context->ownerDocument; $this->contextNode = $context; } public function totals(): Totals { $totalsContainer = $this->contextNode->firstChild; if (!$totalsContainer) { $totalsContainer = $this->contextNode->appendChild( $this->dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'totals' ) ); } return new Totals($totalsContainer); } public function lineCoverage(string $line): Coverage { $coverage = $this->contextNode->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', 'coverage' )->item(0); if (!$coverage) { $coverage = $this->contextNode->appendChild( $this->dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'coverage' ) ); } $lineNode = $coverage->appendChild( $this->dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'line' ) ); return new Coverage($lineNode, $line); } protected function contextNode(): DOMElement { return $this->contextNode; } protected function dom(): DOMDocument { return $this->dom; } } BuildInformation.php000066600000005130150515552240010531 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\CodeCoverage\Report\Xml; use function constant; use function phpversion; use DateTimeImmutable; use DOMElement; use SebastianBergmann\Environment\Runtime; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ final class BuildInformation { /** * @var DOMElement */ private $contextNode; public function __construct(DOMElement $contextNode) { $this->contextNode = $contextNode; } public function setRuntimeInformation(Runtime $runtime): void { $runtimeNode = $this->nodeByName('runtime'); $runtimeNode->setAttribute('name', $runtime->getName()); $runtimeNode->setAttribute('version', $runtime->getVersion()); $runtimeNode->setAttribute('url', $runtime->getVendorUrl()); $driverNode = $this->nodeByName('driver'); if ($runtime->hasPHPDBGCodeCoverage()) { $driverNode->setAttribute('name', 'phpdbg'); $driverNode->setAttribute('version', constant('PHPDBG_VERSION')); } if ($runtime->hasXdebug()) { $driverNode->setAttribute('name', 'xdebug'); $driverNode->setAttribute('version', phpversion('xdebug')); } if ($runtime->hasPCOV()) { $driverNode->setAttribute('name', 'pcov'); $driverNode->setAttribute('version', phpversion('pcov')); } } public function setBuildTime(DateTimeImmutable $date): void { $this->contextNode->setAttribute('time', $date->format('D M j G:i:s T Y')); } public function setGeneratorVersions(string $phpUnitVersion, string $coverageVersion): void { $this->contextNode->setAttribute('phpunit', $phpUnitVersion); $this->contextNode->setAttribute('coverage', $coverageVersion); } private function nodeByName(string $name): DOMElement { $node = $this->contextNode->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', $name )->item(0); if (!$node) { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( 'https://schema.phpunit.de/coverage/1.0', $name ) ); } return $node; } } Directory.php000066600000000737150515552240007240 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\CodeCoverage\Report\Xml; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ final class Directory extends Node { } Method.php000066600000003121150515552240006502 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\CodeCoverage\Report\Xml; use DOMElement; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ final class Method { /** * @var DOMElement */ private $contextNode; public function __construct(DOMElement $context, string $name) { $this->contextNode = $context; $this->setName($name); } public function setSignature(string $signature): void { $this->contextNode->setAttribute('signature', $signature); } public function setLines(string $start, ?string $end = null): void { $this->contextNode->setAttribute('start', $start); if ($end !== null) { $this->contextNode->setAttribute('end', $end); } } public function setTotals(string $executable, string $executed, string $coverage): void { $this->contextNode->setAttribute('executable', $executable); $this->contextNode->setAttribute('executed', $executed); $this->contextNode->setAttribute('coverage', $coverage); } public function setCrap(string $crap): void { $this->contextNode->setAttribute('crap', $crap); } private function setName(string $name): void { $this->contextNode->setAttribute('name', $name); } } Tests.php000066600000003407150515552240006373 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\CodeCoverage\Report\Xml; use DOMElement; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ final class Tests { private $contextNode; private $codeMap = [ -1 => 'UNKNOWN', // PHPUnit_Runner_BaseTestRunner::STATUS_UNKNOWN 0 => 'PASSED', // PHPUnit_Runner_BaseTestRunner::STATUS_PASSED 1 => 'SKIPPED', // PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED 2 => 'INCOMPLETE', // PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE 3 => 'FAILURE', // PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE 4 => 'ERROR', // PHPUnit_Runner_BaseTestRunner::STATUS_ERROR 5 => 'RISKY', // PHPUnit_Runner_BaseTestRunner::STATUS_RISKY 6 => 'WARNING', // PHPUnit_Runner_BaseTestRunner::STATUS_WARNING ]; public function __construct(DOMElement $context) { $this->contextNode = $context; } public function addTest(string $test, array $result): void { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'test' ) ); $node->setAttribute('name', $test); $node->setAttribute('size', $result['size']); $node->setAttribute('result', (string) $result['status']); $node->setAttribute('status', $this->codeMap[(int) $result['status']]); } } Node.php000066600000004326150515552240006157 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\CodeCoverage\Report\Xml; use DOMDocument; use DOMElement; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ abstract class Node { /** * @var DOMDocument */ private $dom; /** * @var DOMElement */ private $contextNode; public function __construct(DOMElement $context) { $this->setContextNode($context); } public function dom(): DOMDocument { return $this->dom; } public function totals(): Totals { $totalsContainer = $this->contextNode()->firstChild; if (!$totalsContainer) { $totalsContainer = $this->contextNode()->appendChild( $this->dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'totals' ) ); } return new Totals($totalsContainer); } public function addDirectory(string $name): Directory { $dirNode = $this->dom()->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'directory' ); $dirNode->setAttribute('name', $name); $this->contextNode()->appendChild($dirNode); return new Directory($dirNode); } public function addFile(string $name, string $href): File { $fileNode = $this->dom()->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'file' ); $fileNode->setAttribute('name', $name); $fileNode->setAttribute('href', $href); $this->contextNode()->appendChild($fileNode); return new File($fileNode); } protected function setContextNode(DOMElement $context): void { $this->dom = $context->ownerDocument; $this->contextNode = $context; } protected function contextNode(): DOMElement { return $this->contextNode; } } Report.php000066600000005060150515552240006541 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\CodeCoverage\Report\Xml; use function basename; use function dirname; use DOMDocument; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ final class Report extends File { public function __construct(string $name) { $dom = new DOMDocument; $dom->loadXML(''); $contextNode = $dom->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', 'file' )->item(0); parent::__construct($contextNode); $this->setName($name); } public function asDom(): DOMDocument { return $this->dom(); } public function functionObject($name): Method { $node = $this->contextNode()->appendChild( $this->dom()->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'function' ) ); return new Method($node, $name); } public function classObject($name): Unit { return $this->unitObject('class', $name); } public function traitObject($name): Unit { return $this->unitObject('trait', $name); } public function source(): Source { $source = $this->contextNode()->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', 'source' )->item(0); if (!$source) { $source = $this->contextNode()->appendChild( $this->dom()->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'source' ) ); } return new Source($source); } private function setName(string $name): void { $this->contextNode()->setAttribute('name', basename($name)); $this->contextNode()->setAttribute('path', dirname($name)); } private function unitObject(string $tagName, $name): Unit { $node = $this->contextNode()->appendChild( $this->dom()->createElementNS( 'https://schema.phpunit.de/coverage/1.0', $tagName ) ); return new Unit($node, $name); } } Project.php000066600000004630150515552240006676 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\CodeCoverage\Report\Xml; use DOMDocument; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ final class Project extends Node { public function __construct(string $directory) { $this->init(); $this->setProjectSourceDirectory($directory); } public function projectSourceDirectory(): string { return $this->contextNode()->getAttribute('source'); } public function buildInformation(): BuildInformation { $buildNode = $this->dom()->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', 'build' )->item(0); if (!$buildNode) { $buildNode = $this->dom()->documentElement->appendChild( $this->dom()->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'build' ) ); } return new BuildInformation($buildNode); } public function tests(): Tests { $testsNode = $this->contextNode()->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', 'tests' )->item(0); if (!$testsNode) { $testsNode = $this->contextNode()->appendChild( $this->dom()->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'tests' ) ); } return new Tests($testsNode); } public function asDom(): DOMDocument { return $this->dom(); } private function init(): void { $dom = new DOMDocument; $dom->loadXML(''); $this->setContextNode( $dom->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', 'project' )->item(0) ); } private function setProjectSourceDirectory(string $name): void { $this->contextNode()->setAttribute('source', $name); } } Facade.php000066600000021620150515552240006431 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\CodeCoverage\Report\Xml; use const DIRECTORY_SEPARATOR; use const PHP_EOL; use function count; use function dirname; use function file_get_contents; use function file_put_contents; use function is_array; use function is_dir; use function is_file; use function is_writable; use function libxml_clear_errors; use function libxml_get_errors; use function libxml_use_internal_errors; use function sprintf; use function strlen; use function substr; use DateTimeImmutable; use DOMDocument; use SebastianBergmann\CodeCoverage\CodeCoverage; use SebastianBergmann\CodeCoverage\Driver\PathExistsButIsNotDirectoryException; use SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException; use SebastianBergmann\CodeCoverage\Node\AbstractNode; use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; use SebastianBergmann\CodeCoverage\Node\File as FileNode; use SebastianBergmann\CodeCoverage\Util\Filesystem as DirectoryUtil; use SebastianBergmann\CodeCoverage\Version; use SebastianBergmann\CodeCoverage\XmlException; use SebastianBergmann\Environment\Runtime; final class Facade { /** * @var string */ private $target; /** * @var Project */ private $project; /** * @var string */ private $phpUnitVersion; public function __construct(string $version) { $this->phpUnitVersion = $version; } /** * @throws XmlException */ public function process(CodeCoverage $coverage, string $target): void { if (substr($target, -1, 1) !== DIRECTORY_SEPARATOR) { $target .= DIRECTORY_SEPARATOR; } $this->target = $target; $this->initTargetDirectory($target); $report = $coverage->getReport(); $this->project = new Project( $coverage->getReport()->name() ); $this->setBuildInformation(); $this->processTests($coverage->getTests()); $this->processDirectory($report, $this->project); $this->saveDocument($this->project->asDom(), 'index'); } private function setBuildInformation(): void { $buildNode = $this->project->buildInformation(); $buildNode->setRuntimeInformation(new Runtime); $buildNode->setBuildTime(new DateTimeImmutable); $buildNode->setGeneratorVersions($this->phpUnitVersion, Version::id()); } /** * @throws PathExistsButIsNotDirectoryException * @throws WriteOperationFailedException */ private function initTargetDirectory(string $directory): void { if (is_file($directory)) { if (!is_dir($directory)) { throw new PathExistsButIsNotDirectoryException($directory); } if (!is_writable($directory)) { throw new WriteOperationFailedException($directory); } } DirectoryUtil::createDirectory($directory); } /** * @throws XmlException */ private function processDirectory(DirectoryNode $directory, Node $context): void { $directoryName = $directory->name(); if ($this->project->projectSourceDirectory() === $directoryName) { $directoryName = '/'; } $directoryObject = $context->addDirectory($directoryName); $this->setTotals($directory, $directoryObject->totals()); foreach ($directory->directories() as $node) { $this->processDirectory($node, $directoryObject); } foreach ($directory->files() as $node) { $this->processFile($node, $directoryObject); } } /** * @throws XmlException */ private function processFile(FileNode $file, Directory $context): void { $fileObject = $context->addFile( $file->name(), $file->id() . '.xml' ); $this->setTotals($file, $fileObject->totals()); $path = substr( $file->pathAsString(), strlen($this->project->projectSourceDirectory()) ); $fileReport = new Report($path); $this->setTotals($file, $fileReport->totals()); foreach ($file->classesAndTraits() as $unit) { $this->processUnit($unit, $fileReport); } foreach ($file->functions() as $function) { $this->processFunction($function, $fileReport); } foreach ($file->lineCoverageData() as $line => $tests) { if (!is_array($tests) || count($tests) === 0) { continue; } $coverage = $fileReport->lineCoverage((string) $line); foreach ($tests as $test) { $coverage->addTest($test); } $coverage->finalize(); } $fileReport->source()->setSourceCode( file_get_contents($file->pathAsString()) ); $this->saveDocument($fileReport->asDom(), $file->id()); } private function processUnit(array $unit, Report $report): void { if (isset($unit['className'])) { $unitObject = $report->classObject($unit['className']); } else { $unitObject = $report->traitObject($unit['traitName']); } $unitObject->setLines( $unit['startLine'], $unit['executableLines'], $unit['executedLines'] ); $unitObject->setCrap((float) $unit['crap']); $unitObject->setNamespace($unit['namespace']); foreach ($unit['methods'] as $method) { $methodObject = $unitObject->addMethod($method['methodName']); $methodObject->setSignature($method['signature']); $methodObject->setLines((string) $method['startLine'], (string) $method['endLine']); $methodObject->setCrap($method['crap']); $methodObject->setTotals( (string) $method['executableLines'], (string) $method['executedLines'], (string) $method['coverage'] ); } } private function processFunction(array $function, Report $report): void { $functionObject = $report->functionObject($function['functionName']); $functionObject->setSignature($function['signature']); $functionObject->setLines((string) $function['startLine']); $functionObject->setCrap($function['crap']); $functionObject->setTotals((string) $function['executableLines'], (string) $function['executedLines'], (string) $function['coverage']); } private function processTests(array $tests): void { $testsObject = $this->project->tests(); foreach ($tests as $test => $result) { $testsObject->addTest($test, $result); } } private function setTotals(AbstractNode $node, Totals $totals): void { $loc = $node->linesOfCode(); $totals->setNumLines( $loc['linesOfCode'], $loc['commentLinesOfCode'], $loc['nonCommentLinesOfCode'], $node->numberOfExecutableLines(), $node->numberOfExecutedLines() ); $totals->setNumClasses( $node->numberOfClasses(), $node->numberOfTestedClasses() ); $totals->setNumTraits( $node->numberOfTraits(), $node->numberOfTestedTraits() ); $totals->setNumMethods( $node->numberOfMethods(), $node->numberOfTestedMethods() ); $totals->setNumFunctions( $node->numberOfFunctions(), $node->numberOfTestedFunctions() ); } private function targetDirectory(): string { return $this->target; } /** * @throws XmlException */ private function saveDocument(DOMDocument $document, string $name): void { $filename = sprintf('%s/%s.xml', $this->targetDirectory(), $name); $document->formatOutput = true; $document->preserveWhiteSpace = false; $this->initTargetDirectory(dirname($filename)); file_put_contents($filename, $this->documentAsString($document)); } /** * @throws XmlException * * @see https://bugs.php.net/bug.php?id=79191 */ private function documentAsString(DOMDocument $document): string { $xmlErrorHandling = libxml_use_internal_errors(true); $xml = $document->saveXML(); if ($xml === false) { $message = 'Unable to generate the XML'; foreach (libxml_get_errors() as $error) { $message .= PHP_EOL . $error->message; } throw new XmlException($message); } libxml_clear_errors(); libxml_use_internal_errors($xmlErrorHandling); return $xml; } }