Passed
Push — master ( 6e30b5...45927b )
by Marc
02:32
created

FbpParser::hasValue()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 2
crap 2
1
<?php
2
/*
3
 * This file is part of the phpflo\phpflo-fbp package.
4
 *
5
 * (c) Marc Aschmann <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace PhpFlo\Fbp;
11
12
use PhpFlo\Common\FbpDefinitionsInterface;
13
use PhpFlo\Exception\ParserDefinitionException;
14
use PhpFlo\Exception\ParserException;
15
16
/**
17
 * Class FbpParser
18
 *
19
 * @package PhpFlo\Parser
20
 * @author Marc Aschmann <[email protected]>
21
 */
22
final class FbpParser implements FbpDefinitionsInterface
23
{
24
25
    /**
26
     * @var string
27
     */
28
    private $source;
29
30
    /**
31
     * @var array
32
     */
33
    private $settings;
34
35
    /**
36
     * @var array
37
     */
38
    private $schema;
39
40
    /**
41
     * @var int
42
     */
43
    private $linecount;
44
45
    /**
46
     * @var array
47
     */
48
    private $definition;
49
50
    /**
51
     * FbpParser constructor.
52
     *
53
     * @param string $source optional for initializing
54
     * @param array $settings optional settings for parser
55
     */
56 6
    public function __construct($source = '', $settings = [])
57
    {
58 6
        $this->source   = $source;
59 6
        $this->settings = array_replace_recursive(
60 6
            [],
61
            $settings
62 6
        );
63
64 6
        $this->schema = [
65 6
            self::PROPERTIES_LABEL => [
66 6
                'name' => '',
67 6
            ],
68 6
            self::INITIALIZERS_LABEL => [],
69 6
            self::PROCESSES_LABEL => [],
70 6
            self::CONNECTIONS_LABEL => [],
71
        ];
72
73 6
        $this->definition = [];
74 6
    }
75
76
    /**
77
     * @param string $source
78
     * @return FbpDefinition
79
     * @throws ParserException
80
     */
81 6
    public function run($source = '')
82
    {
83 6
        if ('' != $source) {
84 1
            $this->source = $source;
85 1
        }
86
87 6
        if (empty($this->source)) {
88 1
            throw new ParserException("FbpParser::run(): no source data or empty string given!");
89
        }
90
91 5
        $this->definition = $this->schema; // reset
92 5
        $this->linecount = 1;
93
94
        /*
95
         * split by lines, OS-independent
96
         * work each line and parse for definitions
97
         */
98 5
        foreach (preg_split('/' . self::NEWLINES . '/m', $this->source) as $line) {
99 5
            $subset = $this->examineSubset($line);
100 5
            $this->definition[self::CONNECTIONS_LABEL] = array_merge_recursive(
101 5
                $this->definition[self::CONNECTIONS_LABEL], $subset
102 5
            );
103 5
            $this->linecount++;
104 5
        }
105
106 5
        return new FbpDefinition($this->definition);
107
    }
108
109
    /**
110
     * @param string $line
111
     * @return array
112
     * @throws ParserDefinitionException
113
     */
114 5
    private function examineSubset($line)
115
    {
116 5
        $subset = [];
117 5
        $nextSrc = null;
118 5
        $hasInitializer = false;
119
120 5
        if (1 == $this->linecount && 0 === strpos(trim($line), "'")) {
121 2
            $hasInitializer = true;
122 2
        }
123
124
        // subset
125 5
        foreach (explode(self::SOURCE_TARGET_SEPARATOR, $line) as $definition) {
126 5
            $resolved = [];
127
128 5
            if (!$hasInitializer) {
129 5
                $resolved = $this->examineDefinition($definition);
130 5
            }
131
132 5
            $hasInport = $this->hasValue($resolved, self::INPORT_LABEL);
133 5
            $hasOutport = $this->hasValue($resolved, self::OUTPORT_LABEL);
134
135
            //define states
136 5
            switch (true) {
137 5 View Code Duplication
                case !empty($step[self::DATA_LABEL]) && ($hasInport && $hasOutport):
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
138
                    // initializer + inport
139 1
                    $nextSrc = $resolved;
140 1
                    $step[self::TARGET_LABEL] = $this->addPort($resolved, self::INPORT_LABEL);
141
                    // multi def oneliner initializer resolved
142 1
                    array_push($this->definition[self::INITIALIZERS_LABEL], $step);
0 ignored issues
show
Bug introduced by
The variable $step does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
143 1
                    $step = [];
144 1
                    break;
145 5
                case !empty($nextSrc) && ($hasInport && $hasOutport):
146
                    // if there was an initializer, we get a full touple with this iteration
147
                    $step = [
148 1
                        self::SOURCE_LABEL => $this->addPort($nextSrc, self::OUTPORT_LABEL),
149 1
                        self::TARGET_LABEL => $this->addPort($resolved, self::INPORT_LABEL),
150 1
                    ];
151 1
                    $nextSrc = $resolved;
152 1
                    array_push($subset, $step);
153 1
                    $step = [];
154 1
                    break;
155 5 View Code Duplication
                case $hasInport && $hasOutport:
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
156
                    // tgt + multi def
157 2
                    $nextSrc = $resolved;
158 2
                    $step[self::TARGET_LABEL] = $this->addPort($resolved, self::INPORT_LABEL);
159
                    // check if we've already got the touple ready
160 2
                    if (!empty($step[self::SOURCE_LABEL])) {
161 2
                        array_push($subset, $step);
162 2
                        $step = [];
163 2
                    }
164 2
                    break;
165 5
                case $hasInport && $nextSrc:
166
                    // use orevious OUT as src to fill touple
167 2
                    $step[self::SOURCE_LABEL] = $this->addPort($nextSrc, self::OUTPORT_LABEL);
168 2
                    $nextSrc = null;
1 ignored issue
show
Unused Code introduced by
$nextSrc is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
169 5
                case $hasInport:
170 5
                    $step[self::TARGET_LABEL] = $this->addPort($resolved, self::INPORT_LABEL);
171
                    // resolved touple
172 5
                    if (empty($step[self::DATA_LABEL])) {
173 5
                        array_push($subset, $step);
174 5
                    } else {
175 1
                        array_push($this->definition[self::INITIALIZERS_LABEL], $step);
176
                    }
177 5
                    $nextSrc = null;
178 5
                    $step = [];
179 5
                    break;
180 5
                case $hasOutport:
181
                    // simplest case OUT -> IN
182 5
                    $step[self::SOURCE_LABEL] = $this->addPort($resolved, self::OUTPORT_LABEL);
183 5
                    break;
184 2
                case $hasInitializer:
185
                    // initialization value: at the moment we only support one
186 2
                    $step[self::DATA_LABEL] = trim($definition, " '");
187 2
                    $hasInitializer = false; // reset
188 2
                    break;
189
                default:
190
                    throw new ParserDefinitionException(
191
                        "Line ({$this->linecount}) {$line} does not contain in or out ports!"
192
                    );
193
            }
194 5
        }
195
196 5
        return $subset;
197
    }
198
199
    /**
200
     * Check if array has a specific key and is not empty.
201
     *
202
     * @param array $check
203
     * @param string $value
204
     * @return bool
205
     */
206 5
    private function hasValue(array $check, $value)
207
    {
208 5
        if (empty($check[$value])) {
209 5
            return false;
210
        }
211
212 5
        return true;
213
    }
214
215
    /**
216
     * @param array $definition
217
     * @param string $label
218
     * @return array
219
     */
220 5
    private function addPort(array $definition, $label)
221
    {
222
        return [
223 5
            self::PROCESS_LABEL => $definition[self::PROCESS_LABEL],
224 5
            self::PORT_LABEL => $definition[$label],
225 5
        ];
226
    }
227
228
    /**
229
     * @param string $line
230
     * @return array
231
     * @throws ParserDefinitionException
232
     */
233 5
    private function examineDefinition($line)
234
    {
235 5
        preg_match('/' . self::PROCESS_DEFINITION . '/', $line, $matches);
236 5
        foreach ($matches as $key => $value) {
237 5
            if (is_numeric($key)) {
238 5
                unset($matches[$key]);
239 5
            }
240 5
        }
241
242 5
        if (!empty($matches[self::PROCESS_LABEL])) {
243 5
            if (empty($matches[self::COMPONENT_LABEL])) {
244 4
                $matches[self::COMPONENT_LABEL] = $matches[self::PROCESS_LABEL];
245 4
            }
246
247 5
            $this->examineProcess($matches);
248 5
        } else {
249
            throw new ParserDefinitionException(
250
                "No process definition found in line ({$this->linecount}) {$line}"
251
            );
252
        }
253
254 5
        return $matches;
255
    }
256
257
    /**
258
     * Add entry to processes.
259
     *
260
     * @param array $process
261
     */
262 5
    private function examineProcess(array $process)
263
    {
264 5
        if (!isset($this->definition[self::PROCESSES_LABEL][$process[self::PROCESS_LABEL]])) {
265 5
            $component = $process[self::COMPONENT_LABEL];
266 5
            if (empty($component)) {
267
                $component = $process[self::PROCESS_LABEL];
268
            }
269
270 5
            $this->definition[self::PROCESSES_LABEL][$process[self::PROCESS_LABEL]] = [
271 5
                self::COMPONENT_LABEL => $component,
272 5
                self::METADATA_LABEL => [
273 5
                    'label' => $component,
274 5
                ],
275
            ];
276 5
        }
277 5
    }
278
}
279