| Current Path : /home/rtorresani/www/vendor/pdepend/pdepend/src/main/php/PDepend/Report/Overview/ |
| Current File : //home/rtorresani/www/vendor/pdepend/pdepend/src/main/php/PDepend/Report/Overview/Pyramid.php |
<?php
/**
* This file is part of PDepend.
*
* PHP Version 5
*
* Copyright (c) 2008-2017 Manuel Pichler <mapi@pdepend.org>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Manuel Pichler nor the names of his
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright 2008-2017 Manuel Pichler. All rights reserved.
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
*/
namespace PDepend\Report\Overview;
use DOMDocument;
use PDepend\Metrics\Analyzer;
use PDepend\Metrics\Analyzer\CouplingAnalyzer;
use PDepend\Metrics\Analyzer\CyclomaticComplexityAnalyzer;
use PDepend\Metrics\Analyzer\InheritanceAnalyzer;
use PDepend\Metrics\Analyzer\NodeCountAnalyzer;
use PDepend\Metrics\Analyzer\NodeLocAnalyzer;
use PDepend\Report\FileAwareGenerator;
use PDepend\Report\NoLogOutputException;
use PDepend\Util\FileUtil;
use PDepend\Util\ImageConvert;
use RuntimeException;
/**
* This logger generates a system overview pyramid, as described in the book
* <b>Object-Oriented Metrics in Practice</b>.
*
* http://www.springer.com/computer/programming/book/978-3-540-24429-5
*
* @copyright 2008-2017 Manuel Pichler. All rights reserved.
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
*/
class Pyramid implements FileAwareGenerator
{
/**
* The output file name.
*
* @var string
*/
private $logFile = null;
/**
* The used coupling analyzer.
*
* @var CouplingAnalyzer
*/
private $coupling = null;
/**
* The used cyclomatic complexity analyzer.
*
* @var CyclomaticComplexityAnalyzer
*/
private $cyclomaticComplexity = null;
/**
* The used inheritance analyzer.
*
* @var InheritanceAnalyzer
*/
private $inheritance = null;
/**
* The used node count analyzer.
*
* @var NodeCountAnalyzer
*/
private $nodeCount = null;
/**
* The used node loc analyzer.
*
* @var NodeLocAnalyzer
*/
private $nodeLoc = null;
/**
* Holds defined thresholds for the computed proportions. This set is based
* on java thresholds, we should find better values for php projects.
*
* @var array<string, array<int, float>>
*/
private $thresholds = array(
'cyclo-loc' => array(0.16, 0.20, 0.24),
'loc-nom' => array(7, 10, 13),
'nom-noc' => array(4, 7, 10),
'noc-nop' => array(6, 17, 26),
'calls-nom' => array(2.01, 2.62, 3.2),
'fanout-calls' => array(0.56, 0.62, 0.68),
'andc' => array(0.25, 0.41, 0.57),
'ahh' => array(0.09, 0.21, 0.32)
);
/**
* Sets the output log file.
*
* @param string $logFile The output log file.
*
* @return void
*/
public function setLogFile($logFile)
{
$this->logFile = $logFile;
}
/**
* Returns an <b>array</b> with accepted analyzer types. These types can be
* concrete analyzer classes or one of the descriptive analyzer interfaces.
*
* @return array<string>
*/
public function getAcceptedAnalyzers()
{
return array(
'pdepend.analyzer.coupling',
'pdepend.analyzer.cyclomatic_complexity',
'pdepend.analyzer.inheritance',
'pdepend.analyzer.node_count',
'pdepend.analyzer.node_loc',
);
}
/**
* Adds an analyzer to log. If this logger accepts the given analyzer it
* with return <b>true</b>, otherwise the return value is <b>false</b>.
*
* @param Analyzer $analyzer The analyzer to log.
*
* @return bool
*/
public function log(Analyzer $analyzer)
{
if ($analyzer instanceof CyclomaticComplexityAnalyzer) {
$this->cyclomaticComplexity = $analyzer;
} elseif ($analyzer instanceof CouplingAnalyzer) {
$this->coupling = $analyzer;
} elseif ($analyzer instanceof InheritanceAnalyzer) {
$this->inheritance = $analyzer;
} elseif ($analyzer instanceof NodeCountAnalyzer) {
$this->nodeCount = $analyzer;
} elseif ($analyzer instanceof NodeLocAnalyzer) {
$this->nodeLoc = $analyzer;
} else {
return false;
}
return true;
}
/**
* Closes the logger process and writes the output file.
*
* @throws NoLogOutputException
*
* @return void
*/
public function close()
{
// Check for configured log file
if ($this->logFile === null) {
throw new NoLogOutputException($this);
}
$metrics = $this->collectMetrics();
$proportions = $this->computeProportions($metrics);
$svg = new DOMDocument('1.0', 'UTF-8');
$svg->loadXML(file_get_contents(dirname(__FILE__) . '/pyramid.svg'));
$items = array_merge($metrics, $proportions);
foreach ($items as $name => $value) {
$svg->getElementById("pdepend.{$name}")->nodeValue = $value;
if (($threshold = $this->computeThreshold($name, $value)) === null) {
continue;
}
if (($color = $svg->getElementById("threshold.{$threshold}")) === null) {
continue;
}
if (($rect = $svg->getElementById("rect.{$name}")) === null) {
continue;
}
preg_match('/fill:(#[^;"]+)/', $color->getAttribute('style'), $match);
$style = $rect->getAttribute('style');
$style = preg_replace('/fill:#[^;"]+/', "fill:{$match[1]}", $style);
$rect->setAttribute('style', $style);
}
$temp = FileUtil::getSysTempDir();
$temp .= '/' . uniqid('pdepend_') . '.svg';
$svg->save($temp);
ImageConvert::convert($temp, $this->logFile);
// Remove temp file
unlink($temp);
}
/**
* Computes the threshold (low, average, high) for the given value and metric.
* If no threshold is defined for the given name, this method will return
* <b>null</b>.
*
* @param string $name The metric/field identfier.
* @param mixed $value The metric/field value.
*
* @return string|null
*/
private function computeThreshold($name, $value)
{
if (!isset($this->thresholds[$name])) {
return null;
}
$threshold = $this->thresholds[$name];
if ($value <= $threshold[0]) {
return 'low';
} elseif ($value >= $threshold[2]) {
return 'high';
} else {
$low = $value - $threshold[0];
$avg = $threshold[1] - $value;
if ($low < $avg) {
return 'low';
}
}
return 'average';
}
/**
* Computes the proportions between the given metrics.
*
* @param array<string, float> $metrics The aggregated project metrics.
*
* @return array<string, float>
*/
private function computeProportions(array $metrics)
{
$orders = array(
array('cyclo', 'loc', 'nom', 'noc', 'nop'),
array('fanout', 'calls', 'nom')
);
$proportions = array();
foreach ($orders as $names) {
for ($i = 1, $c = count($names); $i < $c; ++$i) {
$value1 = $metrics[$names[$i]];
$value2 = $metrics[$names[$i - 1]];
$identifier = "{$names[$i - 1]}-{$names[$i]}";
$proportions[$identifier] = 0;
if ($value1 > 0) {
$proportions[$identifier] = round($value2 / $value1, 3);
}
}
}
return $proportions;
}
/**
* Aggregates the required metrics from the registered analyzers.
*
* @throws RuntimeException If one of the required analyzers isn't set.
*
* @return array<string, mixed>
*/
private function collectMetrics()
{
if ($this->coupling === null) {
throw new RuntimeException('Missing Coupling analyzer.');
}
if ($this->cyclomaticComplexity === null) {
throw new RuntimeException('Missing Cyclomatic Complexity analyzer.');
}
if ($this->inheritance === null) {
throw new RuntimeException('Missing Inheritance analyzer.');
}
if ($this->nodeCount === null) {
throw new RuntimeException('Missing Node Count analyzer.');
}
if ($this->nodeLoc === null) {
throw new RuntimeException('Missing Node LOC analyzer.');
}
$coupling = $this->coupling->getProjectMetrics();
$cyclomatic = $this->cyclomaticComplexity->getProjectMetrics();
$inheritance = $this->inheritance->getProjectMetrics();
$nodeCount = $this->nodeCount->getProjectMetrics();
$nodeLoc = $this->nodeLoc->getProjectMetrics();
return array(
'cyclo' => $cyclomatic['ccn2'],
'loc' => $nodeLoc['eloc'],
'nom' => ($nodeCount['nom'] + $nodeCount['nof']),
'noc' => $nodeCount['noc'],
'nop' => $nodeCount['nop'],
'ahh' => round($inheritance['ahh'], 3),
'andc' => round($inheritance['andc'], 3),
'fanout' => $coupling['fanout'],
'calls' => $coupling['calls']
);
}
}