Filter::topologicalSort()   D
last analyzed

Complexity

Conditions 10
Paths 72

Size

Total Lines 35
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 47.966

Importance

Changes 2
Bugs 2 Features 0
Metric Value
c 2
b 2
f 0
dl 0
loc 35
ccs 8
cts 29
cp 0.2759
rs 4.8196
cc 10
eloc 20
nc 72
nop 2
crap 47.966

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * The MIT License (MIT)
4
 *
5
 * Copyright (c) 2016 Marco Muths
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy
8
 * of this software and associated documentation files (the "Software"), to deal
9
 * in the Software without restriction, including without limitation the rights
10
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
 * copies of the Software, and to permit persons to whom the Software is
12
 * furnished to do so, subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in all
15
 * copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
 * SOFTWARE.
24
 */
25
26
namespace Squeeze;
27
28
use Composer\Autoload\ClassLoader;
29
use PhpParser\NodeTraverser;
30
use PhpParser\ParserAbstract;
31
use Symfony\Component\Finder\SplFileInfo;
32
33
class Filter
34
{
35
    /** @var ParserAbstract */
36
    private $parser;
37
38
    /** @var NodeTraverser */
39
    private $traverser;
40
41
    /** @var Collector */
42
    private $collector;
43
44
    /** @var ClassLoader */
45
    private $classloader;
46
47
    /**
48
     * @param ParserAbstract $parser
49
     * @param NodeTraverser  $traverser
50
     * @param Collector      $collector
51
     * @param ClassLoader    $classloader
52
     */
53 3
    public function __construct(
54
        ParserAbstract $parser,
55
        NodeTraverser $traverser,
56
        Collector $collector,
57
        ClassLoader $classloader
58
    ) {
59 3
        $this->parser = $parser;
60 3
        $this->traverser = $traverser;
61 3
        $this->collector = $collector;
62 3
        $this->classloader = $classloader;
63 3
    }
64
65
    /**
66
     * @param \Iterator|SplFileInfo[] $files
67
     * @return array
68
     */
69 3
    public function extractClassMapFrom(\Iterator $files)
70
    {
71 3
        foreach ($files as $file) {
72
            if ($stmts = $this->parser->parse($file->getContents())) {
73
                $this->traverser->traverse($stmts);
74
                $this->collector->reset();
75
            }
76 3
        }
77
78 3
        $classMap = $this->collector->getClassMap();
79 3
        $classMap = $this->removeUnloadableClassesFrom($classMap);
80 3
        $classMap = $this->sort($classMap);
81
82 3
        $classFileMap = array();
83 3
        foreach ($classMap as $class) {
84
            $classFileMap[$class] = $this->classloader->findFile($class);
85 3
        }
86
87
88 3
        return $classFileMap;
89
    }
90
91
    /**
92
     * @param array $classMap
93
     * @return array
94
     */
95 3
    private function removeUnloadableClassesFrom(array $classMap)
96
    {
97 3
        foreach ($classMap as $class => $dependencies) {
98 View Code Duplication
            if (!$this->classloader->findFile($class)) {
99
                unset($classMap[$class]);
100
                $classMap = $this->removeUnloadableClassesFrom($classMap);
101
                break;
102
            }
103
            foreach ($dependencies as $dependency) {
104 View Code Duplication
                if (!isset($classMap[$dependency]) || !$this->classloader->findFile($dependency)) {
105
                    unset($classMap[$class]);
106
                    $classMap = $this->removeUnloadableClassesFrom($classMap);
107
                    break 2;
108
                }
109
            }
110 3
        }
111
112 3
        return $classMap;
113
    }
114
115
    /**
116
     * @param array $classMap
117
     * @return array
118
     */
119 3
    private function sort(array $classMap)
120
    {
121 3
        $edges = array();
122 3
        foreach ($classMap as $class => $dependencies) {
123
            foreach ($dependencies as $dependency) {
124
                $edges[] = array($dependency, $class);
125
            }
126 3
        }
127
128 3
        return $this->topologicalSort(
129 3
            array_keys($classMap),
130
            $edges
131 3
        );
132
    }
133
134
    /**
135
     * @param array $nodeIds
136
     * @param array $edges
137
     * @return array
138
     */
139 3
    private function topologicalSort(array $nodeIds, array $edges)
140
    {
141 3
        $sorted = $edgelessNodes = $nodes = array();
142
143 3
        foreach ($nodeIds as $id) {
144
            $nodes[$id] = array('in' => array(), 'out' => array());
145
            foreach ($edges as $edge) {
146
                if ($id == $edge[0]) {
147
                    $nodes[$id]['out'][] = $edge[1];
148
                }
149
                if ($id == $edge[1]) {
150
                    $nodes[$id]['in'][] = $edge[0];
151
                }
152
            }
153 3
        }
154
155 3
        foreach ($nodes as $id => $node) {
156
            if (empty($node['in'])) {
157
                $edgelessNodes[] = $id;
158
            }
159 3
        }
160
161 3
        while (!empty($edgelessNodes)) {
162
            $sorted[] = $id = array_shift($edgelessNodes);
163
            foreach ($nodes[$id]['out'] as $out) {
164
                $nodes[$out]['in'] = array_diff($nodes[$out]['in'], array($id));
165
                if (empty($nodes[$out]['in'])) {
166
                    $edgelessNodes[] = $out;
167
                }
168
            }
169
            $nodes[$id]['out'] = array();
170
        }
171
172 3
        return $sorted;
173
    }
174
}
175