Completed
Push — master ( 82cdf4...33953d )
by Xeriab
06:21
created

Properties   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 264
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 40
lcom 1
cbo 5
dl 0
loc 264
rs 8.2608
c 0
b 0
f 0
ccs 11
cts 11
cp 1

8 Methods

Rating   Name   Duplication   Size   Complexity  
A parse() 0 17 4
A getSupportedFileExtensions() 0 4 1
D extractData() 0 101 21
A unescapeProperties() 0 8 2
A deleteFields() 0 10 3
A getProperties() 0 17 4
A loadFile() 0 12 4
A exportFormat() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like Properties 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 Properties, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Konfig.
5
 *
6
 * Yet another simple configuration loader library.
7
 *
8
 * PHP version 5
9
 *
10
 * @category Library
11
 * @package  Konfig
12
 * @author   Xeriab Nabil (aka KodeBurner) <[email protected]>
13
 * @license  https://raw.github.com/xeriab/konfig/master/LICENSE MIT
14
 * @link     https://xeriab.github.io/projects/konfig
15
 */
16
17
namespace Exen\Konfig\FileParser;
18
19
use Exen\Konfig\Arr;
20
use Exen\Konfig\Utils;
21
use Exen\Konfig\Exception\KonfigException as Exception;
22
use Exen\Konfig\Exception\ParseException;
23
24
/**
25
 * Properties
26
 * Konfig's Java-Properties parser class.
27
 *
28
 * @category FileParser
29
 * @package  Konfig
30
 * @author   Xeriab Nabil (aka KodeBurner) <[email protected]>
31
 * @license  https://raw.github.com/xeriab/konfig/master/LICENSE MIT
32
 * @link     https://xeriab.github.io/projects/konfig
33
 *
34
 * @implements Exen\Konfig\FileParser\AbstractFileParser
35
 */
36
class Properties extends AbstractFileParser
37
{
38
    protected $parsedFile;
39
40
    /**
41
     * Loads a PROPERTIES file as an array.
42
     *
43
     * @param string $path File path
44
     *
45
     * @throws ParseException If there is an error parsing PROPERTIES file
46
     *
47
     * @return array The parsed data
48
     *
49
     * @since 0.2.4
50
     */
51 6
    public function parse($path)
52
    {
53 6
        $this->loadFile($path);
54
55 6
        $data = $this->getProperties();
56
57 6
        if (!$data || empty($data) || !is_array($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...
58 3
            throw new ParseException(
59
                [
60 3
                'message' => 'Error parsing PROPERTIES file',
61 3
                'file' => $path,
62
                ]
63 2
            );
64
        }
65
66 3
        return $data;
67
    }
68
69
    /**
70
     * {@inheritdoc}
71
     *
72
     * @return array Supported extensions
73
     *
74
     * @since 0.1.0
75
     */
76 3
    public function getSupportedFileExtensions()
77
    {
78 3
        return ['properties'];
79
    }
80
81
    /**
82
     * {@inheritdoc}
83
     *
84
     * @return array The exteacted data
85
     *
86
     * @since              0.2.4
87
     * @codeCoverageIgnore
88
     */
89
    public function extractData()
90
    {
91
        $analysis = [];
92
93
        // First pass, we categorize each line
94
        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...
95
            if (Utils::stringStart('#', $line)) {
96
                $analysis[$lineNb] = ['comment', trim(substr($line, 1))];
97
                continue;
98
            }
99
100
            // Property name, check for escaped equal sign
101
            if (substr_count($line, '=') > substr_count($line, '\=')) {
102
                $temp = explode('=', $line, 2);
103
                $temp = Utils::trimArrayElements($temp);
104
105
                if (count($temp) === 2) {
106
                    $temp[1] = Utils::removeQuotes($temp[1]);
107
                    $analysis[$lineNb] = ['property', $temp[0], $temp[1]];
108
                }
109
110
                unset($temp);
111
112
                continue;
113
            } else {
114
                break;
115
            }
116
117
            // Multiline data
118
            if (substr_count($line, '=') === 0) {
0 ignored issues
show
Unused Code introduced by
// Multiline data if (su...; } else { break; } 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...
119
                $analysis[$lineNb] = ['multiline', '', $line];
120
                continue;
121
            } else {
122
                break;
123
            }
124
        }
125
126
        // Second pass, we associate comments to entities
127
        $counter = Utils::getNumberLinesMatching('comment', $analysis);
128
129
        while ($counter > 0) {
130
            foreach ($analysis as $lineNb => $line) {
131
                if ($line[0] === 'comment'
132
                    && isset($analysis[$lineNb + 1][0])
133
                    && $analysis[$lineNb + 1][0] === 'comment'
134
                ) {
135
                    $analysis[$lineNb][1] .= ' '.$analysis[$lineNb + 1][1];
136
                    $analysis[$lineNb + 1][0] = 'erase';
137
138
                    break;
139
                } elseif ($line[0] === 'comment'
140
                    && isset($analysis[$lineNb + 1][0])
141
                    && $analysis[$lineNb + 1][0] === 'property'
142
                ) {
143
                    $analysis[$lineNb + 1][3] = $line[1];
144
                    $analysis[$lineNb][0] = 'erase';
145
                }
146
            }
147
148
            $counter = Utils::getNumberLinesMatching('comment', $analysis);
149
            $analysis = $this->deleteFields('erase', $analysis);
150
        }
151
152
        // Third pass, we merge multiline strings
153
154
        // We remove the backslashes at end of strings if they exist
155
        $analysis = Utils::stripBackslashes($analysis);
156
157
        // Count # of multilines
158
        $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 155 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...
159
160
        while ($counter > 0) {
161
            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...
162
                if ($line[0] === 'multiline'
163
                    && isset($analysis[$lineNb - 1][0])
164
                    && $analysis[$lineNb - 1][0] === 'property'
165
                ) {
166
                    $analysis[$lineNb - 1][2] .= ' ' . trim($line[2]);
167
                    $analysis[$lineNb][0] = 'erase';
168
                    break;
169
                }
170
            }
171
172
            $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 155 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...
173
            $analysis = $this->deleteFields('erase', $analysis);
174
        }
175
176
        // Step 4, we clean up strings from escaped characters in properties
177
        $analysis = $this->unescapeProperties($analysis);
178
179
        // Step 5, we only have properties now, remove redondant field 0
180
        foreach ($analysis as $key => $value) {
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...
181
            if (preg_match('/^[1-9][0-9]*$/', $value[2])) {
182
                $value[2] = intval($value[2]);
183
            }
184
185
            array_splice($analysis[$key], 0, 1);
186
        }
187
188
        return $analysis;
189
    }
190
191
    /**
192
     * {@inheritdoc}
193
     *
194
     * @param array|null $analysis Configuration items
195
     *
196
     * @return array The configuration items
197
     *
198
     * @since              0.2.4
199
     * @codeCoverageIgnore
200
     */
201
    private function unescapeProperties($analysis)
202
    {
203
        foreach ($analysis as $key => $value) {
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...
204
            $analysis[$key][2] = str_replace('\=', '=', $value[2]);
205
        }
206
207
        return $analysis;
208
    }
209
210
    /**
211
     * {@inheritdoc}
212
     *
213
     * @param string $field    Field name
214
     * @param array|null  $analysis Configuration items
215
     *
216
     * @return array Configuration items after deletion
217
     *
218
     * @since              0.2.4
219
     * @codeCoverageIgnore
220
     */
221
    private function deleteFields($field, $analysis)
222
    {
223
        foreach ($analysis as $key => $value) {
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...
224
            if ($value[0] === $field) {
225
                unset($analysis[$key]);
226
            }
227
        }
228
229
        return array_values($analysis);
230
    }
231
232
    /**
233
     * {@inheritdoc}
234
     *
235
     * @param string|bool|null $file File path
236
     *
237
     * @return array Configuration items
238
     *
239
     * @since              0.2.4
240
     * @codeCoverageIgnore
241
     */
242
    public function getProperties($file = null)
243
    {
244
        if ($file && !is_null($file)) {
245
            $this->loadFile($file);
246
        }
247
248
        $source = $this->extractData();
249
        $data = [];
250
251
        foreach ($source as $value) {
0 ignored issues
show
Bug introduced by
The expression $source 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...
252
            Arr::set($data, $value[0], $value[1]);
253
        }
254
255
        unset($this->parsedFile);
256
257
        return $data;
258
    }
259
260
    /**
261
     * Loads in the given file and parses it.
262
     *
263
     * @param string|bool|null $file File to load
264
     *
265
     * @return array The parsed file data
266
     *
267
     * @since              0.2.4
268
     * @codeCoverageIgnore
269
     */
270
    protected function loadFile($file = null)
271
    {
272
        $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 boolean. 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...
273
274
        $contents = $this->parseVars(Utils::getContent($this->file));
0 ignored issues
show
Bug introduced by
It seems like $this->file can also be of type boolean; however, Exen\Konfig\Utils::getContent() does only seem to accept string|null, 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...
275
276
        if ($this->file && !is_null($file)) {
277
            $this->parsedFile = Utils::fileContentToArray($contents);
278
        } else {
279
            $this->parsedFile = false;
280
        }
281
    }
282
283
    /**
284
     * Returns the formatted configuration file contents.
285
     *
286
     * @param array $contents configuration array
287
     *
288
     * @return string formatted configuration file contents
289
     *
290
     * @since              0.2.4
291
     * @codeCoverageIgnore
292
     */
293
    protected function exportFormat($contents = null)
294
    {
295
        throw new Exception(
296
            'Saving configuration to `Properties` is not supported at this time'
297
        );
298
    }
299
}
300
301
// END OF ./src/FileParser/Properties.php FILE
302