|
1
|
|
|
<?php |
|
2
|
|
|
namespace Hal\Metric\Class_\Structural; |
|
3
|
|
|
|
|
4
|
|
|
use Hal\Component\Tree\GraphDeduplicated; |
|
5
|
|
|
use Hal\Component\Tree\Node as TreeNode; |
|
6
|
|
|
use Hal\Metric\Helper\MetricClassNameGenerator; |
|
7
|
|
|
use Hal\Metric\MetricNullException; |
|
8
|
|
|
use Hal\Metric\Metrics; |
|
9
|
|
|
use Hal\ShouldNotHappenException; |
|
10
|
|
|
use PhpParser\Node; |
|
11
|
|
|
use PhpParser\Node\Stmt; |
|
12
|
|
|
use PhpParser\NodeVisitorAbstract; |
|
13
|
|
|
|
|
14
|
|
|
/** |
|
15
|
|
|
* Lack of cohesion of methods |
|
16
|
|
|
* |
|
17
|
|
|
* @package Hal\Metric\Class_\Coupling |
|
18
|
|
|
*/ |
|
19
|
|
|
class LcomVisitor extends NodeVisitorAbstract |
|
20
|
|
|
{ |
|
21
|
|
|
|
|
22
|
|
|
/** |
|
23
|
|
|
* @var Metrics |
|
24
|
|
|
*/ |
|
25
|
|
|
private $metrics; |
|
26
|
|
|
|
|
27
|
|
|
/** |
|
28
|
|
|
* @param Metrics $metrics |
|
29
|
|
|
*/ |
|
30
|
|
|
public function __construct(Metrics $metrics) |
|
31
|
|
|
{ |
|
32
|
|
|
$this->metrics = $metrics; |
|
33
|
|
|
} |
|
34
|
|
|
|
|
35
|
|
|
/** |
|
36
|
|
|
* @inheritdoc |
|
37
|
|
|
*/ |
|
38
|
|
|
public function leaveNode(Node $node) |
|
39
|
|
|
{ |
|
40
|
|
|
if ($node instanceof Stmt\Class_ || $node instanceof Stmt\Trait_) { |
|
41
|
|
|
// we build a graph of internal dependencies in class |
|
42
|
|
|
$graph = new GraphDeduplicated(); |
|
43
|
|
|
|
|
44
|
|
|
$name = MetricClassNameGenerator::getName($node); |
|
45
|
|
|
$class = $this->metrics->get($name); |
|
46
|
|
|
if ($class === null) { |
|
47
|
|
|
throw new MetricNullException($name, self::class); |
|
48
|
|
|
} |
|
49
|
|
|
|
|
50
|
|
|
foreach ($node->stmts as $stmt) { |
|
51
|
|
|
if ($stmt instanceof Stmt\ClassMethod) { |
|
52
|
|
|
if (!$graph->has($stmt->name . '()')) { |
|
53
|
|
|
$graph->insert(new TreeNode($stmt->name . '()')); |
|
54
|
|
|
} |
|
55
|
|
|
$from = $graph->get($stmt->name . '()'); |
|
56
|
|
|
if ($from === null) { |
|
57
|
|
|
throw new ShouldNotHappenException('Graph $from is null'); |
|
58
|
|
|
} |
|
59
|
|
|
|
|
60
|
|
|
\iterate_over_node($stmt, function ($node) use ($from, &$graph) { |
|
61
|
|
View Code Duplication |
if ($node instanceof Node\Expr\PropertyFetch && isset($node->var->name) && $node->var->name == 'this') { |
|
|
|
|
|
|
62
|
|
|
$name = (string)getNameOfNode($node); |
|
63
|
|
|
// use of attribute $this->xxx; |
|
64
|
|
|
if (!$graph->has($name)) { |
|
65
|
|
|
$graph->insert(new TreeNode($name)); |
|
66
|
|
|
} |
|
67
|
|
|
$to = $graph->get($name); |
|
68
|
|
|
if ($to === null) { |
|
69
|
|
|
throw new ShouldNotHappenException('Graph $to is null'); |
|
70
|
|
|
} |
|
71
|
|
|
$graph->addEdge($from, $to); |
|
72
|
|
|
return; |
|
73
|
|
|
} |
|
74
|
|
|
|
|
75
|
|
|
if ($node instanceof Node\Expr\MethodCall) { |
|
76
|
|
View Code Duplication |
if (!$node->var instanceof Node\Expr\New_ && isset($node->var->name) && getNameOfNode($node->var) === 'this') { |
|
|
|
|
|
|
77
|
|
|
// use of method call $this->xxx(); |
|
78
|
|
|
// use of attribute $this->xxx; |
|
79
|
|
|
$name = getNameOfNode($node->name) . '()'; |
|
80
|
|
|
if (!$graph->has($name)) { |
|
81
|
|
|
$graph->insert(new TreeNode($name)); |
|
82
|
|
|
} |
|
83
|
|
|
$to = $graph->get($name); |
|
84
|
|
|
if ($to === null) { |
|
85
|
|
|
throw new ShouldNotHappenException('Graph $to is null'); |
|
86
|
|
|
} |
|
87
|
|
|
$graph->addEdge($from, $to); |
|
88
|
|
|
return; |
|
89
|
|
|
} |
|
90
|
|
|
} |
|
91
|
|
|
}); |
|
92
|
|
|
} |
|
93
|
|
|
} |
|
94
|
|
|
|
|
95
|
|
|
// we count paths |
|
96
|
|
|
$paths = 0; |
|
97
|
|
|
foreach ($graph->all() as $node) { |
|
98
|
|
|
$paths += $this->traverse($node); |
|
99
|
|
|
} |
|
100
|
|
|
|
|
101
|
|
|
$class->set('lcom', $paths); |
|
102
|
|
|
} |
|
103
|
|
|
|
|
104
|
|
|
return null; |
|
105
|
|
|
} |
|
106
|
|
|
|
|
107
|
|
|
/** |
|
108
|
|
|
* Traverse node, and return 1 if node has not been visited yet |
|
109
|
|
|
* |
|
110
|
|
|
* @param TreeNode $node |
|
111
|
|
|
* @return int |
|
112
|
|
|
*/ |
|
113
|
|
|
private function traverse(TreeNode $node) |
|
114
|
|
|
{ |
|
115
|
|
|
if ($node->visited) { |
|
116
|
|
|
return 0; |
|
117
|
|
|
} |
|
118
|
|
|
$node->visited = true; |
|
119
|
|
|
|
|
120
|
|
|
foreach ($node->getAdjacents() as $adjacent) { |
|
121
|
|
|
$this->traverse($adjacent); |
|
122
|
|
|
} |
|
123
|
|
|
|
|
124
|
|
|
return 1; |
|
125
|
|
|
} |
|
126
|
|
|
} |
|
127
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.