Skip to content

Commit

Permalink
Merge pull request #283 from UFOMelkor/feature/package-metrics
Browse files Browse the repository at this point in the history
Package metrics
  • Loading branch information
UFOMelkor authored May 8, 2018
2 parents e1173d1 + b04581a commit cdf0e45
Show file tree
Hide file tree
Showing 29 changed files with 1,683 additions and 31 deletions.
13 changes: 13 additions & 0 deletions src/Hal/Application/Analyze.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
use Hal\Metric\Class_\Text\HalsteadVisitor;
use Hal\Metric\Class_\Text\LengthVisitor;
use Hal\Metric\Metrics;
use Hal\Metric\Package\PackageAbstraction;
use Hal\Metric\Package\PackageCollectingVisitor;
use Hal\Metric\Package\PackageDependencies;
use Hal\Metric\Package\PackageDistance;
use Hal\Metric\Package\PackageInstability;
use Hal\Metric\System\Changes\GitChanges;
use Hal\Metric\System\Coupling\Coupling;
use Hal\Metric\System\Coupling\DepthOfInheritanceTree;
Expand Down Expand Up @@ -88,6 +93,7 @@ public function run($files)
$traverser->addVisitor(new MaintainabilityIndexVisitor($metrics));
$traverser->addVisitor(new KanDefectVisitor($metrics));
$traverser->addVisitor(new SystemComplexityVisitor($metrics));
$traverser->addVisitor(new PackageCollectingVisitor($metrics));

// create a new progress bar (50 units)
$progress = new ProgressBar($this->output, sizeof($files));
Expand Down Expand Up @@ -118,6 +124,13 @@ public function run($files)
(new Coupling())->calculate($metrics);
(new DepthOfInheritanceTree())->calculate($metrics);

//
// Package analyses
(new PackageDependencies())->calculate($metrics);
(new PackageAbstraction())->calculate($metrics);
(new PackageInstability())->calculate($metrics);
(new PackageDistance())->calculate($metrics);

//
// File analyses
(new GitChanges($this->config, $files))->calculate($metrics);
Expand Down
3 changes: 2 additions & 1 deletion src/Hal/Metric/Class_/ClassEnumVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,16 @@ public function leaveNode(Node $node)
|| $node instanceof Stmt\Interface_
|| $node instanceof Stmt\Trait_
) {

if ($node instanceof Stmt\Interface_) {
$class = new InterfaceMetric($node->namespacedName->toString());
$class->set('interface', true);
$class->set('abstract', true);
} else {

$name = (string) (isset($node->namespacedName) ? $node->namespacedName : 'anonymous@'.spl_object_hash($node));
$class = new ClassMetric($name);
$class->set('interface', false);
$class->set('abstract', $node->isAbstract());
}

$methods = [];
Expand Down
46 changes: 46 additions & 0 deletions src/Hal/Metric/Consolidated.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class Consolidated
*/
private $project = [];

/** @var array */
private $packages;

/**
* Consolided constructor.
* @param Metrics $metrics
Expand All @@ -42,6 +45,7 @@ public function __construct(Metrics $metrics)
$classes = [];
$functions = [];
$files = [];
$packages = [];
$project = [];
$nbInterfaces = 0;
foreach ($metrics->all() as $key => $item) {
Expand All @@ -57,6 +61,8 @@ public function __construct(Metrics $metrics)
$files[$key] = $item->all();
} elseif (ProjectMetric::class === $classItem) {
$project[$key] = $item->all();
} elseif (PackageMetric::class === $classItem) {
$packages[$key] = $item->all();
}
}

Expand Down Expand Up @@ -97,6 +103,7 @@ public function __construct(Metrics $metrics)
}
$sum->nbClasses = count($classes);
$sum->nbInterfaces = $nbInterfaces;
$sum->nbPackages = count($packages);

foreach ($avg as &$a) {
if (sizeof($a) > 0) {
Expand All @@ -106,6 +113,29 @@ public function __construct(Metrics $metrics)
}
}

$avg->distance = 0;
$avg->incomingCDep = 0;
$avg->incomingPDep = 0;
$avg->outgoingCDep = 0;
$avg->outgoingPDep = 0;
$avg->classesPerPackage = 0;
foreach (array_keys($packages) as $eachName) {
/* @var $eachPackage PackageMetric */
$eachPackage = $metrics->get($eachName);
$avg->distance += $eachPackage->getDistance();
$avg->incomingCDep += count($eachPackage->getIncomingClassDependencies());
$avg->incomingPDep += count($eachPackage->getIncomingPackageDependencies());
$avg->outgoingCDep += count($eachPackage->getOutgoingClassDependencies());
$avg->outgoingPDep += count($eachPackage->getOutgoingPackageDependencies());
$avg->classesPerPackage += count($eachPackage->getClasses());
}
$avg->distance = round($avg->distance / count($packages), 2);
$avg->incomingCDep = round($avg->incomingCDep / count($packages), 2);
$avg->incomingPDep = round($avg->incomingPDep / count($packages), 2);
$avg->outgoingCDep = round($avg->outgoingCDep / count($packages), 2);
$avg->outgoingPDep = round($avg->outgoingPDep / count($packages), 2);
$avg->classesPerPackage = round($avg->classesPerPackage / count($packages), 2);

// sums of violations
$violations = [
'total' => 0,
Expand All @@ -127,6 +157,13 @@ public function __construct(Metrics $metrics)
$violations[$name]++;
}
}
foreach ($packages as $package) {
foreach ($package['violations'] as $violation) {
$violations['total']++;
$name = $map[$violation->getLevel()];
$violations[$name]++;
}
}
$sum->violations = (object)$violations;


Expand All @@ -135,6 +172,7 @@ public function __construct(Metrics $metrics)
$this->classes = $classes;
$this->files = $files;
$this->project = $project;
$this->packages = $packages;
}

/**
Expand Down Expand Up @@ -176,4 +214,12 @@ public function getProject()
{
return $this->project;
}

/**
* @return array
*/
public function getPackages()
{
return $this->packages;
}
}
26 changes: 26 additions & 0 deletions src/Hal/Metric/Package/PackageAbstraction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Hal\Metric\Package;

use Hal\Metric\Metrics;
use Hal\Metric\PackageMetric;

class PackageAbstraction
{
public function calculate(Metrics $metrics)
{
/* @var $packages PackageMetric[] */
foreach ($metrics->all() as $eachPackage) {
if (! $eachPackage instanceof PackageMetric) {
continue;
}
$abstractClassCount = 0;
$classCount = count($eachPackage->getClasses());
foreach ($eachPackage->getClasses() as $eachClassName) {
$eachClass = $metrics->get($eachClassName);
$abstractClassCount += $eachClass->get('abstract');
}
$eachPackage->setAbstraction($abstractClassCount / $classCount);
}
}
}
61 changes: 61 additions & 0 deletions src/Hal/Metric/Package/PackageCollectingVisitor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace Hal\Metric\Package;

use Hal\Metric\Metrics;
use Hal\Metric\PackageMetric;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Trait_;
use PhpParser\NodeVisitorAbstract;

class PackageCollectingVisitor extends NodeVisitorAbstract
{
/** @var string */
private $namespace = '';

/** @var Metrics */
private $metrics;

public function __construct(Metrics $metrics)
{
$this->metrics = $metrics;
}

public function enterNode(Node $node)
{
if ($node instanceof Namespace_) {
$this->namespace = (string) $node->name;
}
}

public function leaveNode(Node $node)
{
if ($node instanceof Class_ || $node instanceof Interface_ || $node instanceof Trait_) {
$package = $this->namespace;

$docComment = $node->getDocComment();
$docBlockText = $docComment ? $docComment->getText() : '';
if (preg_match('/^\s*\* @package (.*)/m', $docBlockText, $matches)) {
$package = $matches[1];
}
if (preg_match('/^\s*\* @subpackage (.*)/m', $docBlockText, $matches)) {
$package = $package . '\\' . $matches[1];
}

$packageName = $package . '\\';
if (! $packageMetric = $this->metrics->get($packageName)) {
$packageMetric = new PackageMetric($packageName);
$this->metrics->attach($packageMetric);
}
/* @var PackageMetric $packageMetric */
$elementName = isset($node->namespacedName) ? $node->namespacedName : 'anonymous@'.spl_object_hash($node);
$elementName = (string) $elementName;
$packageMetric->addClass($elementName);

$this->metrics->get($elementName)->set('package', $packageName);
}
}
}
61 changes: 61 additions & 0 deletions src/Hal/Metric/Package/PackageDependencies.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace Hal\Metric\Package;

use Hal\Metric\ClassMetric;
use Hal\Metric\InterfaceMetric;
use Hal\Metric\Metric;
use Hal\Metric\Metrics;
use Hal\Metric\PackageMetric;

class PackageDependencies
{
public function calculate(Metrics $metrics)
{
$classes = array_filter($metrics->all(), function (Metric $metric) {
return $metric instanceof ClassMetric || $metric instanceof InterfaceMetric;
});

foreach ($classes as $each) {
$this->increaseDependencies($each, $metrics);
}
}

/**
* @param ClassMetric|InterfaceMetric|Metric $class
* @param Metrics $metrics
*/
private function increaseDependencies(Metric $class, Metrics $metrics)
{
if (! $class->has('package') || ! $class->has('externals')) {
return;
}
$incomingPackage = $metrics->get($class->get('package')); /* @var $incomingPackage PackageMetric */
foreach ($class->get('externals') as $outgoingClassName) {
// same package?
if (in_array($outgoingClassName, $incomingPackage->getClasses())) {
continue;
}
$outgoingPackageName = $this->getPackageOfClass($outgoingClassName, $metrics);
$incomingPackage->addOutgoingClassDependency($outgoingClassName, $outgoingPackageName);
$outgoingPackage = $metrics->get($outgoingPackageName);

if ($outgoingPackage instanceof PackageMetric) {
$outgoingPackage->addIncomingClassDependency($class->getName(), $incomingPackage->getName());
}
}
}

private function getPackageOfClass($className, Metrics $metrics)
{
if ($metrics->has($className) && $metrics->get($className)->has('package')) {
return $metrics->get($className)->get('package');
}
if (strpos($className, '\\') === false) {
return '\\';
}
$parts = explode('\\', $className);
array_pop($parts);
return implode('\\', $parts) . '\\';
}
}
18 changes: 18 additions & 0 deletions src/Hal/Metric/Package/PackageDistance.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Hal\Metric\Package;

use Hal\Metric\Metrics;
use Hal\Metric\PackageMetric;

class PackageDistance
{
public function calculate(Metrics $metrics)
{
foreach ($metrics->all() as $each) {
if ($each instanceof PackageMetric && $each->getAbstraction() !== null && $each->getInstability() !== null) {
$each->setNormalizedDistance(abs($each->getAbstraction() + $each->getInstability() - 1));
}
}
}
}
42 changes: 42 additions & 0 deletions src/Hal/Metric/Package/PackageInstability.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Hal\Metric\Package;

use Hal\Metric\Metrics;
use Hal\Metric\PackageMetric;

class PackageInstability
{
public function calculate(Metrics $metrics)
{
/* @var $packages PackageMetric[] */
$packages = array_filter($metrics->all(), function ($metric) {
return $metric instanceof PackageMetric;
});

// Calculate instability
$instabilitiesByPackage = [];
foreach ($packages as $eachPackage) {
$afferentCoupling = count($eachPackage->getIncomingClassDependencies());
$efferentCoupling = count($eachPackage->getOutgoingClassDependencies());
if ($afferentCoupling + $efferentCoupling !== 0) {
$eachPackage->setInstability(
$efferentCoupling / ($afferentCoupling + $efferentCoupling)
);
$instabilitiesByPackage[$eachPackage->getName()] = $eachPackage->getInstability();
}
}
// Set depending instabilities
foreach ($packages as $eachPackage) {
$dependentInstabilities = array_map(function ($packageName) use ($instabilitiesByPackage) {
return isset($instabilitiesByPackage[$packageName]) ? $instabilitiesByPackage[$packageName] : null;
}, $eachPackage->getOutgoingPackageDependencies());
$dependentInstabilities = array_combine(
$eachPackage->getOutgoingPackageDependencies(),
$dependentInstabilities
);
$dependentInstabilities = array_filter($dependentInstabilities, 'is_float');
$eachPackage->setDependentInstabilities($dependentInstabilities);
}
}
}
Loading

0 comments on commit cdf0e45

Please sign in to comment.