src/Enrichers/Clover/CloverEnricher.php
<?php
declare(strict_types=1);
namespace Klitsche\Dog\Enrichers\Clover;
use Klitsche\Dog\Elements\Class_;
use Klitsche\Dog\Elements\File;
use Klitsche\Dog\Elements\Interface_;
use Klitsche\Dog\Elements\Method;
use Klitsche\Dog\Elements\Trait_;
use Klitsche\Dog\Enrichers\DataAwareInterface;
use Klitsche\Dog\Enrichers\Enricher;
use Klitsche\Dog\Exceptions\EnricherException;
use Klitsche\Dog\ProjectInterface;
use Symfony\Component\Filesystem\Filesystem;
/**
* Reads coverage clover xml files generated by tools like {@link https://phpunit.de/ phpunit} and
* {@link https://codeception.com/ codeception} and enriches Project and Elements like File, Class, Trait and Method.
*/
class CloverEnricher extends Enricher
{
protected \SimpleXMLElement $clover;
public function prepare(): void
{
$filesystem = new Filesystem();
$file = $this->config->getEnrichers()[$this->id]['file'] ?? '';
if ($filesystem->isAbsolutePath($file) === false) {
$file = $this->config->getWorkingDir() . '/' . $file;
}
if (file_exists($file) === false) {
throw EnricherException::create($this, sprintf('File %s not found.', $file));
}
try {
$this->clover = new \SimpleXMLElement($file, LIBXML_NOXMLDECL, true);
} catch (\Throwable $exception) {
throw EnricherException::create(
$this,
sprintf(
'Cannot parse file %s. Details: %s',
$file,
$exception->getMessage()
)
);
}
}
public function enrich(DataAwareInterface $dataHolder): void
{
switch (true) {
case $dataHolder instanceof ProjectInterface:
$elements = $this->clover->xpath('//project');
if (isset($elements[0]) === true) {
$this->addCoveredClasses($elements[0], $this->clover);
$dataHolder->setData($this->id, ProjectMetrics::createFromElement($elements[0]));
}
break;
case $dataHolder instanceof File:
$fileName = $dataHolder->getId();
$elements = $this->clover->xpath('//file[@name="' . $fileName . '"]');
if (isset($elements[0]) === true) {
$this->addCoveredClasses($elements[0], $elements[0]);
$dataHolder->setData($this->id, FileMetrics::createFromElement($elements[0]));
}
break;
case $dataHolder instanceof Class_:
case $dataHolder instanceof Trait_:
case $dataHolder instanceof Interface_:
$className = ltrim($dataHolder->getId(), '\\');
$elements = $this->clover->xpath('//class[@name="' . $className . '"]');
if (isset($elements[0]) === true) {
$dataHolder->setData($this->id, ClassMetrics::createFromElement($elements[0]));
}
break;
case $dataHolder instanceof Method:
$methodName = $dataHolder->getName();
$fileName = ltrim($dataHolder->getFile()->getId(), '\\');
// todo: only works for single classes/traits per file
$elements = $this->clover->xpath(
'//file[@name="' . $fileName . '"]/line[@type="method" and @name="' . $methodName . '"]'
);
if (isset($elements[0]) === true) {
$dataHolder->setData($this->id, MethodMetrics::createFromElement($elements[0]));
}
break;
}
}
private function addCoveredClasses(\SimpleXMLElement $element, \SimpleXMLElement $context): void
{
$coveredclasses = 0;
foreach ($context->xpath('//class') as $class) {
$methods = (int) $class->metrics['methods'];
$coveredmethods = (int) $class->metrics['coveredmethods'];
if ($methods > 0 && $methods === $coveredmethods) {
$coveredclasses++;
}
}
$element->metrics->addAttribute('coveredclasses', (string) $coveredclasses);
}
}