Passed
Push — nln-php7 ( 5c4f20...6ce259 )
by Nicolas
03:34
created

Parser   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 276
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 99.2%

Importance

Changes 0
Metric Value
wmc 41
lcom 1
cbo 6
dl 0
loc 276
ccs 124
cts 125
cp 0.992
rs 9.1199
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A setEOL() 0 6 1
A enableIncludeSupport() 0 6 1
A enableExternalSupport() 0 6 1
A enableGroupSupport() 0 6 1
A parse() 0 20 2
A parseFromMasterFile() 0 21 4
A parseFile() 0 31 4
A extractLines() 0 19 3
A trimLines() 0 4 1
A changeCurrentFile() 0 9 2
A extractSectionName() 0 12 2
A switchSectionParser() 0 4 1
A getVariables() 0 4 1
A getFileSystem() 0 4 1
A getExternalVariables() 0 12 2
A printExternalFilesStatus() 0 15 3
A getExternalFilesStatus() 0 12 2
A getGroups() 0 12 2
A postParse() 0 7 2
A isSystem() 0 12 2
A getDefaultEnvironmentsForGroups() 0 12 2

How to fix   Complexity   

Complex Class

Complex classes like Parser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Parser, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types = 1);
4
5
namespace Karma\Configuration;
6
7
use Gaufrette\Filesystem;
8
use Karma\Configuration\Collections\SectionParserCollection;
9
use Karma\Configuration\Parser\NullParser;
10
use Psr\Log\NullLogger;
11
12
class Parser implements FileParser
13
{
14
    use \Karma\Logging\LoggerAware;
15
16
    private
17
        $parsers,
0 ignored issues
show
Coding Style introduced by
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
18
        $currentParser,
19
        $parsedFiles,
20
        $fs,
21
        $eol;
22
23 280
    public function __construct(Filesystem $fs)
24
    {
25 280
        $this->logger = new NullLogger();
26 280
        $this->parsers = new SectionParserCollection();
27
28 280
        $this->parsedFiles = [];
29 280
        $this->fs = $fs;
30 280
        $this->eol = "\n";
31 280
    }
32
33 49
    public function setEOL(string $eol): self
34
    {
35 49
        $this->eol = $eol;
36
37 49
        return $this;
38
    }
39
40 280
    public function enableIncludeSupport(): self
41
    {
42 280
        $this->parsers->enableIncludeSupport();
43
44 280
        return $this;
45
    }
46
47 280
    public function enableExternalSupport(): self
48
    {
49 280
       $this->parsers->enableExternalSupport($this->fs);
50
51 280
        return $this;
52
    }
53
54 270
    public function enableGroupSupport(): self
55
    {
56 270
        $this->parsers->enableGroupSupport();
57
58 270
        return $this;
59
    }
60
61 280
    public function parse(string $masterFilePath): array
62
    {
63
        try
64
        {
65 280
            $this->parseFromMasterFile($masterFilePath);
66
67 247
            $variables = $this->getVariables();
68 247
            $this->printExternalFilesStatus();
69
70 247
            $this->postParse();
71
72 245
            return $variables;
73
        }
74 36
        catch(\RuntimeException $e)
75
        {
76 36
            $this->error($e->getMessage());
77
78 36
            throw $e;
79
        }
80
    }
81
82 280
    private function parseFromMasterFile(string $masterFilePath): void
83
    {
84 280
        $files = [$masterFilePath];
85
86 280
        while(! empty($files))
87
        {
88 280
            foreach($files as $file)
89
            {
90 280
                $this->parseFile($file);
91
            }
92
93 249
            $parser = $this->parsers->includes();
94 249
            if($parser !== null)
95
            {
96 248
                $files = $parser->getCollectedFiles();
0 ignored issues
show
Bug introduced by
The method getCollectedFiles does only exist in Karma\Configuration\Parser\IncludeParser, but not in Karma\Configuration\Pars...n\Parser\VariableParser.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
97
            }
98
99
            // Avoid loop
100 249
            $files = array_diff($files, $this->parsedFiles);
101
        }
102 247
    }
103
104 280
    private function parseFile(string $filePath): void
105
    {
106 280
        $this->parsedFiles[] = $filePath;
107 280
        $lines = $this->extractLines($filePath);
108 280
        $this->changeCurrentFile($filePath);
109
110 280
        $this->currentParser = new NullParser();
111 280
        $currentLineNumber = 0;
112
113 280
        foreach($lines as $line)
114
        {
115 280
            $currentLineNumber++;
116
117 280
            if(empty($line))
118
            {
119 217
                continue;
120
            }
121
122 275
            $sectionName = $this->extractSectionName($line);
123 275
            if($sectionName !== null)
124
            {
125 273
                $this->switchSectionParser($sectionName);
126
127 272
                continue;
128
            }
129
130 274
            $this->currentParser->parse($line, $currentLineNumber);
131
        }
132
133 250
        $this->parsers->variables()->endOfFileCheck();
134 249
    }
135
136 280
    private function extractLines(string $filePath): array
137
    {
138 280
        if(! $this->fs->has($filePath))
139
        {
140 1
            throw new \RuntimeException("$filePath does not exist");
141
        }
142
143 280
        $content = $this->fs->read($filePath);
144
145 280
        $lines = explode($this->eol, $content ?? '');
146 280
        $lines = $this->trimLines($lines);
147
148 280
        if(empty($lines))
149
        {
150
            $this->warning("Empty file ($filePath)");
151
        }
152
153 280
        return $lines;
154
    }
155
156 280
    private function trimLines(array $lines): array
157
    {
158 280
        return array_map('trim', $lines);
159
    }
160
161 280
    private function changeCurrentFile(string $filePath): void
162
    {
163 280
        $this->info("Reading $filePath");
164
165 280
        foreach($this->parsers as $parser)
166
        {
167 280
            $parser->setCurrentFile($filePath);
168
        }
169 280
    }
170
171 275
    private function extractSectionName(string $line): ?string
172
    {
173 275
        $sectionName = null;
174
175
        // [.*]
176 275
        if(preg_match('~^\[(?P<sectionName>[^\]]+)\]$~', $line, $matches))
177
        {
178 273
            $sectionName = strtolower(trim($matches['sectionName']));
179
        }
180
181 275
        return $sectionName;
182
    }
183
184 273
    private function switchSectionParser(string $sectionName): void
185
    {
186 273
        $this->currentParser = $this->parsers->get($sectionName);
187 272
    }
188
189 247
    public function getVariables(): array
190
    {
191 247
        return $this->parsers->variables()->getVariables();
192
    }
193
194 209
    public function getFileSystem(): Filesystem
195
    {
196 209
        return $this->fs;
197
    }
198
199 184
    public function getExternalVariables(): array
200
    {
201 184
        $variables = [];
202
203 184
        $parser = $this->parsers->externals();
204 184
        if($parser !== null)
205
        {
206 184
            $variables = $parser->getExternalVariables();
0 ignored issues
show
Bug introduced by
The method getExternalVariables does only exist in Karma\Configuration\Parser\ExternalParser, but not in Karma\Configuration\Pars...n\Parser\VariableParser.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
207
        }
208
209 184
        return $variables;
210
    }
211
212 247
    private function printExternalFilesStatus(): void
213
    {
214 247
        $files = $this->getExternalFilesStatus();
215
216 247
        foreach($files as $file => $status)
217
        {
218 209
            if($status['found'] === false)
219
            {
220 198
                $this->warning(sprintf(
221 198
                   'External file %s was not found',
222 209
                   $file
223
                ));
224
            }
225
        }
226 247
    }
227
228 247
    private function getExternalFilesStatus(): array
229
    {
230 247
        $files = [];
231
232 247
        $parser = $this->parsers->externals();
233 247
        if($parser !== null)
234
        {
235 246
            $files = $parser->getExternalFilesStatus();
0 ignored issues
show
Bug introduced by
The method getExternalFilesStatus does only exist in Karma\Configuration\Parser\ExternalParser, but not in Karma\Configuration\Pars...n\Parser\VariableParser.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
236
        }
237
238 247
        return $files;
239
    }
240
241 46
    public function getGroups(): array
242
    {
243 46
        $groups = [];
244
245 46
        $parser = $this->parsers->groups();
246 46
        if($parser !== null)
247
        {
248 38
            $groups = $parser->getCollectedGroups();
0 ignored issues
show
Bug introduced by
The method getCollectedGroups does only exist in Karma\Configuration\Parser\GroupParser, but not in Karma\Configuration\Pars...n\Parser\VariableParser.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
249
        }
250
251 46
        return $groups;
252
    }
253
254 247
    private function postParse(): void
255
    {
256 247
        foreach($this->parsers as $parser)
257
        {
258 247
            $parser->postParse();
259
        }
260 245
    }
261
262 4
    public function isSystem(string $variableName): bool
263
    {
264 4
        $system = false;
265
266 4
        $variables = $this->getVariables();
267 4
        if(isset($variables[$variableName]))
268
        {
269 4
            $system = $variables[$variableName]['system'];
270
        }
271
272 4
        return $system;
273
    }
274
275 41
    public function getDefaultEnvironmentsForGroups(): array
276
    {
277 41
        $defaultEnvironments = [];
278
279 41
        $parser = $this->parsers->groups();
280 41
        if($parser !== null)
281
        {
282 33
            $defaultEnvironments = $parser->getDefaultEnvironmentsForGroups();
0 ignored issues
show
Bug introduced by
The method getDefaultEnvironmentsForGroups does only exist in Karma\Configuration\Parser\GroupParser, but not in Karma\Configuration\Pars...n\Parser\VariableParser.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
283
        }
284
285 41
        return $defaultEnvironments;
286
    }
287
}
288