Completed
Push — master ( ac7f34...82cdf4 )
by Xeriab
06:26
created

Properties::deleteFields()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 0
cts 0
cp 0
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 5
nc 3
nop 2
crap 12
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
98
                continue;
99
            }
100
101
            // Property name, check for escaped equal sign
102
            if (substr_count($line, '=') > substr_count($line, '\=')) {
103
                $temp = explode('=', $line, 2);
104
                $temp = Utils::trimArrayElements($temp);
105
106
                if (count($temp) === 2) {
107
                    $temp[1] = Utils::removeQuotes($temp[1]);
108
109
                    $analysis[$lineNb] = ['property', $temp[0], $temp[1]];
110
                }
111
112
                unset($temp);
113
114
                continue;
115
            }
116
117
            // Multiline data
118
            if (substr_count($line, '=') === 0) {
119
                $analysis[$lineNb] = ['multiline', '', $line];
120
                continue;
121
            }
122
        }
123
124
        // Second pass, we associate comments to entities
125
        $counter = Utils::getNumberLinesMatching('comment', $analysis);
126
127
        while ($counter > 0) {
128
            foreach ($analysis as $lineNb => $line) {
129
                if ($line[0] === 'comment'
130
                    && isset($analysis[$lineNb + 1][0])
131
                    && $analysis[$lineNb + 1][0] === 'comment'
132
                ) {
133
                    $analysis[$lineNb][1] .= ' '.$analysis[$lineNb + 1][1];
134
                    $analysis[$lineNb + 1][0] = 'erase';
135
136
                    break;
137
                } elseif ($line[0] === 'comment'
138
                    && isset($analysis[$lineNb + 1][0])
139
                    && $analysis[$lineNb + 1][0] === 'property'
140
                ) {
141
                    $analysis[$lineNb + 1][3] = $line[1];
142
                    $analysis[$lineNb][0] = 'erase';
143
                }
144
            }
145
146
            $counter = Utils::getNumberLinesMatching('comment', $analysis);
147
            $analysis = $this->deleteFields('erase', $analysis);
148
        }
149
150
        // Third pass, we merge multiline strings
151
152
        // We remove the backslashes at end of strings if they exist
153
        $analysis = Utils::stripBackslashes($analysis);
154
155
        // Count # of multilines
156
        $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 153 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...
157
158
        while ($counter > 0) {
159
            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...
160
                if ($line[0] === 'multiline'
161
                    && isset($analysis[$lineNb - 1][0])
162
                    && $analysis[$lineNb - 1][0] === 'property'
163
                ) {
164
                    $analysis[$lineNb - 1][2] .= ' ' . trim($line[2]);
165
                    $analysis[$lineNb][0] = 'erase';
166
                    break;
167
                }
168
            }
169
170
            $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 153 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...
171
            $analysis = $this->deleteFields('erase', $analysis);
0 ignored issues
show
Bug introduced by
It seems like $analysis can also be of type null; however, Exen\Konfig\FileParser\Properties::deleteFields() 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...
172
        }
173
174
        // Step 4, we clean up strings from escaped characters in properties
175
        $analysis = $this->unescapeProperties($analysis);
0 ignored issues
show
Bug introduced by
It seems like $analysis can also be of type null; however, Exen\Konfig\FileParser\P...s::unescapeProperties() 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...
176
177
        // Step 5, we only have properties now, remove redondant field 0
178
        foreach ($analysis as $key => $value) {
179
            if (preg_match('/^[1-9][0-9]*$/', $value[2])) {
180
                $value[2] = intval($value[2]);
181
            }
182
183
            array_splice($analysis[$key], 0, 1);
184
        }
185
186
        return $analysis;
187
    }
188
189
    /**
190
     * {@inheritdoc}
191
     *
192
     * @param array $analysis Configuration items
193
     *
194
     * @return array The configuration items
195
     *
196
     * @since              0.2.4
197
     * @codeCoverageIgnore
198
     */
199
    private function unescapeProperties($analysis)
200
    {
201
        foreach ($analysis as $key => $value) {
202
            $analysis[$key][2] = str_replace('\=', '=', $value[2]);
203
        }
204
205
        return $analysis;
206
    }
207
208
    /**
209
     * {@inheritdoc}
210
     *
211
     * @param string $field    Field name
212
     * @param array  $analysis Configuration items
213
     *
214
     * @return array Configuration items after deletion
215
     *
216
     * @since              0.2.4
217
     * @codeCoverageIgnore
218
     */
219
    private function deleteFields($field, $analysis)
220
    {
221
        foreach ($analysis as $key => $value) {
222
            if ($value[0] === $field) {
223
                unset($analysis[$key]);
224
            }
225
        }
226
227
        return array_values($analysis);
228
    }
229
230
    /**
231
     * {@inheritdoc}
232
     *
233
     * @param string|null $file File path
234
     *
235
     * @return array Configuration items
236
     *
237
     * @since              0.2.4
238
     * @codeCoverageIgnore
239
     */
240
    public function getProperties($file = null)
241
    {
242
        if ($file && !is_null($file)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $file of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
243
            $this->loadFile($file);
244
        }
245
246
        $source = $this->extractData();
247
        $data = [];
248
249
        foreach ($source as $value) {
250
            Arr::set($data, $value[0], $value[1]);
251
        }
252
253
        unset($this->parsedFile);
254
255
        return $data;
256
    }
257
258
    /**
259
     * Loads in the given file and parses it.
260
     *
261
     * @param string|bool|null $file File to load
262
     *
263
     * @return array The parsed file data
264
     *
265
     * @since              0.2.4
266
     * @codeCoverageIgnore
267
     */
268
    protected function loadFile($file = null)
269
    {
270
        $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...
271
272
        $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...
273
274
        if ($this->file && !is_null($file)) {
275
            $this->parsedFile = Utils::fileContentToArray($contents);
276
        } else {
277
            $this->parsedFile = false;
278
        }
279
    }
280
281
    /**
282
     * Returns the formatted configuration file contents.
283
     *
284
     * @param array $contents configuration array
285
     *
286
     * @return string formatted configuration file contents
287
     *
288
     * @since              0.2.4
289
     * @codeCoverageIgnore
290
     */
291
    protected function exportFormat($contents = null)
292
    {
293
        throw new Exception(
294
            'Saving configuration to `Properties` is not supported at this time'
295
        );
296
    }
297
}
298
299
// END OF ./src/FileParser/Properties.php FILE
300