Completed
Push — master ( d30cdc...16fa4d )
by Filip
05:56 queued 05:01
created

Config::loadFromString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
1
<?php
2
3
namespace Noodlehaus;
4
5
use Noodlehaus\Exception\FileNotFoundException;
6
use Noodlehaus\Exception\UnsupportedFormatException;
7
use Noodlehaus\Exception\EmptyDirectoryException;
8
use InvalidArgumentException;
9
use Noodlehaus\Parser\ParserInterface;
10
use Noodlehaus\Writer\WriterInterface;
11
12
/**
13
 * Configuration reader and writer for PHP.
14
 *
15
 * @package    Config
16
 * @author     Jesus A. Domingo <[email protected]>
17
 * @author     Hassan Khan <[email protected]>
18
 * @author     Filip Š <[email protected]>
19
 * @link       https://github.com/noodlehaus/config
20
 * @license    MIT
21
 */
22
class Config extends AbstractConfig
23
{
24
    /**
25
     * All formats supported by Config.
26
     *
27
     * @var array
28
     */
29
    protected $supportedParsers = [
30
        'Noodlehaus\Parser\Php',
31
        'Noodlehaus\Parser\Ini',
32
        'Noodlehaus\Parser\Json',
33
        'Noodlehaus\Parser\Xml',
34
        'Noodlehaus\Parser\Yaml'
35
    ];
36
37
    /**
38
     * All formats supported by Config.
39
     *
40
     * @var array
41
     */
42
    protected $supportedWriters = [
43
        'Noodlehaus\Writer\Ini',
44
        'Noodlehaus\Writer\Json',
45
        'Noodlehaus\Writer\Xml',
46
        'Noodlehaus\Writer\Yaml'
47
    ];
48
49
    /**
50
     * Static method for loading a Config instance.
51
     *
52
     * @param  string|array    $values Filenames or string with configuration
53
     * @param  ParserInterface $parser Configuration parser
54
     * @param  bool            $string Enable loading from string
55
     *
56
     * @return Config
57
     */
58 3
    public static function load($values, $parser = null, $string = false)
59
    {
60 3
        return new static($values, $parser, $string);
61
    }
62
63
    /**
64
     * Loads a Config instance.
65
     *
66
     * @param  string|array    $values Filenames or string with configuration
67
     * @param  ParserInterface $parser Configuration parser
68
     * @param  bool            $string Enable loading from string
69
     */
70 39
    public function __construct($values, ParserInterface $parser = null, $string = false)
71
    {
72 39
        if ($string === true) {
73 3
            $this->loadFromString($values, $parser);
0 ignored issues
show
Bug introduced by
It seems like $values defined by parameter $values on line 70 can also be of type array; however, Noodlehaus\Config::loadFromString() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Bug introduced by
It seems like $parser defined by parameter $parser on line 70 can be null; however, Noodlehaus\Config::loadFromString() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
74
        } else {
75 36
            $this->loadFromFile($values, $parser);
76
        }
77
78 27
        parent::__construct($this->data);
0 ignored issues
show
Bug introduced by
It seems like $this->data can also be of type null; however, Noodlehaus\AbstractConfig::__construct() 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...
79 27
    }
80
81
    /**
82
     * Loads configuration from file.
83
     *
84
     * @param  string|array     $path   Filenames or directories with configuration
85
     * @param  ParserInterface  $parser Configuration parser
86
     *
87
     * @throws EmptyDirectoryException If `$path` is an empty directory
88
     */
89 39
    protected function loadFromFile($path, ParserInterface $parser = null)
90
    {
91 39
        $paths      = $this->getValidPath($path);
92 30
        $this->data = [];
93
94 30
        foreach ($paths as $path) {
95 30
            if ($parser === null) {
96
                // Get file information
97 27
                $info      = pathinfo($path);
98 27
                $parts     = explode('.', $info['basename']);
99 27
                $extension = array_pop($parts);
100
101
                // Skip the `dist` extension
102 27
                if ($extension === 'dist') {
103 3
                    $extension = array_pop($parts);
104
                }
105
106
                // Get file parser
107 27
                $parser = $this->getParser($extension);
108
109
                // Try to load file
110 21
                $this->data = array_replace_recursive($this->data, $parser->parseFile($path));
111
112
                // Clean parser
113 21
                $parser = null;
114
            } else {
115
                // Try to load file using specified parser
116 3
                $this->data = array_replace_recursive($this->data, $parser->parseFile($path));
117
            }
118
        }
119 24
    }
120
121
    /**
122
     * Writes configuration to file.
123
     *
124
     * @param  string           $filename   Filename to save configuration to
125
     * @param  WriterInterface  $writer Configuration writer
126
     *
127
     * @throws WriteException if the data could not be written to the file
128
     */
129 3
    public function toFile($filename, WriterInterface $writer = null)
130
    {
131 3
        if ($writer === null) {
132
            // Get file information
133 3
            $info      = pathinfo($filename);
134 3
            $parts     = explode('.', $info['basename']);
135 3
            $extension = array_pop($parts);
136
137
            // Skip the `dist` extension
138 3
            if ($extension === 'dist') {
139
                $extension = array_pop($parts);
140
            }
141
142
            // Get file writer
143 3
            $writer = $this->getWriter($extension);
144
145
            // Try to save file
146 3
            $writer->toFile($this->all(), $filename);
147
148
            // Clean writer
149 3
            $writer = null;
0 ignored issues
show
Unused Code introduced by
$writer 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...
150
        } else {
151
            // Try to load file using specified writer
152
            $writer->toFile($this->all(), $filename);
0 ignored issues
show
Bug introduced by
It seems like $this->all() targeting Noodlehaus\AbstractConfig::all() can also be of type null; however, Noodlehaus\Writer\WriterInterface::toFile() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
153
        }
154 3
    }
155
156
    /**
157
     * Loads configuration from string.
158
     *
159
     * @param string          $configuration String with configuration
160
     * @param ParserInterface $parser        Configuration parser
161
     */
162 3
    protected function loadFromString($configuration, ParserInterface $parser)
163
    {
164 3
        $this->data = [];
165
166
        // Try to parse string
167 3
        $this->data = array_replace_recursive($this->data, $parser->parseString($configuration));
168 3
    }
169
170
    /**
171
     * Writes configuration to string.
172
     *
173
     * @param  WriterInterface  $writer Configuration writer
174
     * @param boolean           $pretty Encode pretty
175
     */
176 3
    public function toString(WriterInterface $writer, $pretty = true)
177
    {
178 3
        return $writer->toString($this->all(), $pretty);
0 ignored issues
show
Bug introduced by
It seems like $this->all() targeting Noodlehaus\AbstractConfig::all() can also be of type null; however, Noodlehaus\Writer\WriterInterface::toString() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
179
    }
180
181
    /**
182
     * Gets a parser for a given file extension.
183
     *
184
     * @param  string $extension
185
     *
186
     * @return Noodlehaus\Parser\ParserInterface
187
     *
188
     * @throws UnsupportedFormatException If `$extension` is an unsupported file format
189
     */
190 27
    protected function getParser($extension)
191
    {
192 27
        foreach ($this->supportedParsers as $parser) {
193 27
            if (in_array($extension, $parser::getSupportedExtensions())) {
194 21
                return new $parser();
195
            }
196
        }
197
198
        // If none exist, then throw an exception
199 6
        throw new UnsupportedFormatException('Unsupported configuration format');
200
    }
201
202
    /**
203
     * Gets a writer for a given file extension.
204
     *
205
     * @param  string $extension
206
     *
207
     * @return Noodlehaus\Writer\WriterInterface
208
     *
209
     * @throws UnsupportedFormatException If `$extension` is an unsupported file format
210
     */
211 3
    protected function getWriter($extension)
212
    {
213 3
        foreach ($this->supportedWriters as $writer) {
214 3
            if (in_array($extension, $writer::getSupportedExtensions())) {
215 3
                return new $writer();
216
            }
217
        }
218
219
        // If none exist, then throw an exception
220
        throw new UnsupportedFormatException('Unsupported configuration format'.$extension);
221
    }
222
223
    /**
224
     * Gets an array of paths
225
     *
226
     * @param  array $path
227
     *
228
     * @return array
229
     *
230
     * @throws FileNotFoundException   If a file is not found at `$path`
231
     */
232 12
    protected function getPathFromArray($path)
233
    {
234 12
        $paths = [];
235
236 12
        foreach ($path as $unverifiedPath) {
237
            try {
238
                // Check if `$unverifiedPath` is optional
239
                // If it exists, then it's added to the list
240
                // If it doesn't, it throws an exception which we catch
241 12
                if ($unverifiedPath[0] !== '?') {
242 12
                    $paths = array_merge($paths, $this->getValidPath($unverifiedPath));
243 12
                    continue;
244
                }
245
246 6
                $optionalPath = ltrim($unverifiedPath, '?');
247 6
                $paths = array_merge($paths, $this->getValidPath($optionalPath));
248 6
            } catch (FileNotFoundException $e) {
249
                // If `$unverifiedPath` is optional, then skip it
250 6
                if ($unverifiedPath[0] === '?') {
251 3
                    continue;
252
                }
253
254
                // Otherwise rethrow the exception
255 3
                throw $e;
256
            }
257
        }
258
259 9
        return $paths;
260
    }
261
262
    /**
263
     * Checks `$path` to see if it is either an array, a directory, or a file.
264
     *
265
     * @param  string|array $path
266
     *
267
     * @return array
268
     *
269
     * @throws EmptyDirectoryException If `$path` is an empty directory
270
     *
271
     * @throws FileNotFoundException   If a file is not found at `$path`
272
     */
273 33
    protected function getValidPath($path)
274
    {
275
        // If `$path` is array
276 33
        if (is_array($path)) {
277 12
            return $this->getPathFromArray($path);
278
        }
279
280
        // If `$path` is a directory
281 33
        if (is_dir($path)) {
282 6
            $paths = glob($path . '/*.*');
283 6
            if (empty($paths)) {
284 3
                throw new EmptyDirectoryException("Configuration directory: [$path] is empty");
285
            }
286
287 3
            return $paths;
288
        }
289
290
        // If `$path` is not a file, throw an exception
291 27
        if (!file_exists($path)) {
292 9
            throw new FileNotFoundException("Configuration file: [$path] cannot be found");
293
        }
294
295 24
        return [$path];
296
    }
297
}
298