Completed
Push — master ( 8d39d6...58e1d9 )
by Xeriab
02:50
created

Properties::extractData()   D

Complexity

Conditions 20
Paths 120

Size

Total Lines 106
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 106
rs 4.4507
cc 20
eloc 59
nc 120
nop 0

How to fix   Long Method    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
/**
4
 * Konfig
5
 *
6
 * Yet another simple configuration loader library.
7
 *
8
 * @author  Xeriab Nabil (aka KodeBurner) <[email protected]>
9
 * @license https://raw.github.com/xeriab/konfig/master/LICENSE MIT
10
 * @link    https://xeriab.github.io/projects/konfig
11
 */
12
13
namespace Exen\Konfig\FileParser;
14
15
use Exen\Konfig\Arr;
16
use Exen\Konfig\Utils;
17
use Exen\Konfig\Exception\Exception;
18
use Exen\Konfig\Exception\ParseException;
19
20
class Properties extends AbstractFileParser
21
{
22
    protected $parsedFile;
23
24
    /**
25
     * {@inheritDoc}
26
     * Loads a PROPERTIES file as an array
27
     *
28
     * @throws ParseException If there is an error parsing PROPERTIES file
29
     * @since 0.2.4
30
     */
31
    public function parse($path)
32
    {
33
        $this->loadFile($path);
34
35
        $data = $this->getProperties();
36
37
        if (!$data) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $data of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
38
            throw new ParseException([
39
                'message' => 'Error parsing PROPERTIES file',
40
                'file' => $path
41
            ]);
42
        }
43
44
        return $data;
45
    }
46
47
    /**
48
     * {@inheritDoc}
49
     */
50
    public function getSupportedFileExtensions()
51
    {
52
        return ['properties'];
53
    }
54
55
    /**
56
     * {@inheritDoc}
57
     * @codeCoverageIgnore
58
     */
59
    public function extractData()
60
    {
61
        $analysis = [];
62
63
        // First pass, we categorize each line
64
        foreach ($this->parsedFile as $lineNb => $line) {
0 ignored issues
show
Bug introduced by
The expression $this->parsedFile of type false|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
65
            if (Utils::stringStart('#', $line)) {
66
                $analysis[$lineNb] = [
67
                    'comment',
68
                    trim(substr($line, 1))
69
                ];
70
71
                continue;
72
            }
73
74
            // Property name, check for escaped equal sign
75
            if (substr_count($line, '=') > substr_count($line, '\=')) {
76
                $temp = explode('=', $line, 2);
77
                $temp = Utils::trimArrayElements($temp);
78
79
                if (count($temp) === 2) {
80
                    $temp[1] = Utils::removeQuotes($temp[1]);
81
                    
82
                    $analysis[$lineNb] = [
83
                        'property',
84
                        $temp[0],
85
                        $temp[1]
86
                    ];
87
                }
88
89
                unset($temp);
90
91
                continue;
92
            } else {
93
                break;
94
            }
95
96
            // Multiline data
97
            if (substr_count($line, '=') === 0) {
0 ignored issues
show
Unused Code introduced by
// Multiline data if (su...$line); continue; } does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
98
                $analysis[$lineNb] = [
99
                    'multiline',
100
                    '',
101
                    $line
102
                ];
103
104
                continue;
105
            }
106
        }
107
108
        // Second pass, we associate comments to entities
109
        $counter = Utils::getNumberLinesMatching('comment', $analysis);
110
111
        while ($counter > 0) {
112
            foreach ($analysis as $lineNb => $line) {
113
                if ($line[0] === 'comment' &&
114
                    isset($analysis[$lineNb + 1][0]) &&
115
                    $analysis[$lineNb + 1][0] === 'comment') {
116
                    $analysis[$lineNb][1] .= ' ' . $analysis[$lineNb + 1][1];
117
                    $analysis[$lineNb + 1][0] = 'erase';
118
119
                    break;
120
                } elseif ($line[0] === 'comment' &&
121
                    isset($analysis[$lineNb + 1][0]) &&
122
                    $analysis[$lineNb + 1][0] === 'property') {
123
                    $analysis[$lineNb + 1][3] = $line[1];
124
                    $analysis[$lineNb][0] = 'erase';
125
                }
126
            }
127
128
            $counter = Utils::getNumberLinesMatching('comment', $analysis);
129
            $analysis = $this->deleteFields('erase', $analysis);
130
        }
131
132
        // Third pass, we merge multiline strings
133
134
        // We remove the backslashes at end of strings if they exist
135
        $analysis = Utils::stripBackslashes($analysis);
136
137
        // Count # of multilines
138
        $counter = Utils::getNumberLinesMatching('multiline', $analysis);
0 ignored issues
show
Bug introduced by
It seems like $analysis defined by \Exen\Konfig\Utils::stripBackslashes($analysis) on line 135 can also be of type null; however, Exen\Konfig\Utils::getNumberLinesMatching() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
139
140
        while ($counter > 0) {
141
            foreach ($analysis as $lineNb => $line) {
0 ignored issues
show
Bug introduced by
The expression $analysis of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
142
                if ($line[0] === 'multiline'
143
                    && isset($analysis[$lineNb - 1][0])
144
                    && $analysis[$lineNb - 1][0] === 'property') {
145
                    $analysis[$lineNb - 1][2] .= ' ' . trim($line[2]);
146
                    $analysis[$lineNb][0] = 'erase';
147
                    break;
148
                }
149
            }
150
151
            $counter = Utils::getNumberLinesMatching('multiline', $analysis);
0 ignored issues
show
Bug introduced by
It seems like $analysis defined by \Exen\Konfig\Utils::stripBackslashes($analysis) on line 135 can also be of type null; however, Exen\Konfig\Utils::getNumberLinesMatching() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
152
            $analysis = $this->deleteFields('erase', $analysis);
153
        }
154
155
        // Step 4, we clean up strings from escaped characters in properties
156
        $analysis = $this->unescapeProperties($analysis);
157
158
        // Step 5, we only have properties now, remove redondant field 0
159
        foreach ($analysis as $key => $value) {
160
            array_splice($analysis[$key], 0, 1);
161
        }
162
163
        return $analysis;
164
    }
165
166
    /**
167
     * {@inheritDoc}
168
     * @codeCoverageIgnore
169
     */
170
    private function unescapeProperties($analysis)
171
    {
172
        foreach ($analysis as $key => $value) {
173
            $analysis[$key][2] = str_replace('\=', '=', $value[2]);
174
        }
175
176
        return $analysis;
177
    }
178
179
    /**
180
     * {@inheritDoc}
181
     * @codeCoverageIgnore
182
     */
183
    private function deleteFields($field, $analysis)
184
    {
185
        foreach ($analysis as $key => $value) {
186
            if ($value[0] === $field) {
187
                unset($analysis[$key]);
188
            }
189
        }
190
191
        return array_values($analysis);
192
    }
193
194
    /**
195
     * {@inheritDoc}
196
     * @since 0.2.4
197
     * @codeCoverageIgnore
198
     */
199
    public function getProperties($file = null)
200
    {
201
        if ($file && !is_null($file)) {
202
            $this->loadFile($file);
203
        }
204
205
        $source = $this->extractData();
206
        $data = [];
207
208
        foreach ($source as $value) {
209
            Arr::set($data, $value[0], $value[1]);
210
        }
211
212
        unset($this->parsedFile);
213
214
        return $data;
215
    }
216
217
    /**
218
     * Loads in the given file and parses it.
219
     *
220
     * @param   string  $file File to load
221
     * @return  array
222
     * @since 0.2.4
223
     * @codeCoverageIgnore
224
     */
225
    protected function loadFile($file = null)
226
    {
227
        $this->file = is_file($file) ? $file : false;
0 ignored issues
show
Documentation Bug introduced by
It seems like is_file($file) ? $file : false can also be of type false. However, the property $file is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
228
229
        $contents = $this->parseVars(Utils::getContent($this->file));
0 ignored issues
show
Security Bug introduced by
It seems like $this->file can also be of type false; however, Exen\Konfig\Utils::getContent() does only seem to accept string|null, did you maybe forget to handle an error condition?
Loading history...
230
231
        if ($this->file && !is_null($file)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->file of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
232
            $this->parsedFile = Utils::fileContentToArray($contents);
233
        } else {
234
            $this->parsedFile = false;
235
        }
236
    }
237
238
    /**
239
     * Returns the formatted configuration file contents.
240
     *
241
     * @param   array   $content  configuration array
0 ignored issues
show
Documentation introduced by
There is no parameter named $content. Did you maybe mean $contents?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
242
     * @return  string  formatted configuration file contents
243
     * @since 0.2.4
244
     * @codeCoverageIgnore
245
     */
246
    protected function exportFormat($contents = null)
247
    {
248
        throw new \Exception('Saving configuration to `Properties` is not supported at this time');
249
    }
250
}
251
252
// END OF ./src/FileParser/Properties.php FILE
253