Completed
Push — master ( 8f169f...ab4119 )
by Mathieu
03:25
created

AbstractConfig::loadYamlFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 7
rs 9.4285
cc 1
eloc 5
nc 1
nop 1
1
<?php
2
3
namespace Charcoal\Config;
4
5
// Dependencies from `PHP`
6
use \ArrayIterator;
7
use \InvalidArgumentException;
8
use \IteratorAggregate;
9
use \Traversable;
10
11
// Dependencies from `symfony/yaml`
12
use Symfony\Component\Yaml\Parser as YamlParser;
13
14
// Dependencies from `container-interop/container-interop`
15
use Interop\Container\ContainerInterface;
16
17
// Local namespace dependencies
18
use \Charcoal\Config\ConfigInterface;
19
20
/**
21
 * Configuration container / registry.
22
 *
23
 * An abstract class that fulfills the full ConfigInterface.
24
 *
25
 * This class also implements the `ArrayAccess` interface, so each member can be accessed with `[]`.
26
 */
27
abstract class AbstractConfig extends AbstractEntity implements
28
    ConfigInterface,
29
    ContainerInterface,
30
    IteratorAggregate
31
{
32
    const DEFAULT_SEPARATOR = '.';
33
34
    /**
35
     * Default separator for config is "."
36
     * @var string $separator
37
     */
38
    protected $separator = self::DEFAULT_SEPARATOR;
39
40
    /**
41
     * Create the configuration.
42
     *
43
     * @param mixed             $data      Optional default data. Either a filename, an array, or a Config object.
44
     * @param ConfigInterface[] $delegates An array of delegates (config) to set.
45
     * @throws InvalidArgumentException If $data is invalid.
46
     */
47
    final public function __construct($data = null, array $delegates = null)
48
    {
49
        // Always set the default data first.
50
        $this->merge($this->defaults());
51
52
        // Set the delegates, if necessary.
53
        if (isset($delegates)) {
54
            $this->setDelegates($delegates);
55
        }
56
57
        if ($data === null) {
58
            return;
59
        }
60
61
        if (is_string($data)) {
62
            // Treat the parameter as a filename
63
            $this->addFile($data);
64
        } elseif (is_array($data)) {
65
            $this->merge($data);
66
        } elseif ($data instanceof ConfigInterface) {
67
            $this->merge($data);
0 ignored issues
show
Documentation introduced by
$data is of type object<Charcoal\Config\ConfigInterface>, but the function expects a array|object<Traversable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
68
        } else {
69
            throw new InvalidArgumentException(
70
                'Data must be an array, a file string or a ConfigInterface object.'
71
            );
72
        }
73
    }
74
75
    /**
76
     * Config gives public access to its separator.
77
     *
78
     * @return string
79
     */
80
    public function separator()
81
    {
82
        return $this->separator;
83
    }
84
85
    /**
86
     * Add a configuration file. The file type is determined by its extension.
87
     *
88
     * Supported file types are `ini`, `json`, `php`
89
     *
90
     * @param string $filename A supported configuration file.
91
     * @throws InvalidArgumentException If the file is invalid.
92
     * @return AbstractConfig (Chainable)
93
     */
94
    public function addFile($filename)
95
    {
96
        $content = $this->loadFile($filename);
97
        if (is_array($content)) {
98
            $this->merge($content);
99
        }
100
        return $this;
101
    }
102
103
    /**
104
     * Load a configuration file. The file type is determined by its extension.
105
     *
106
     * Supported file types are `ini`, `json`, `php`
107
     *
108
     * @param string $filename A supported configuration file.
109
     * @throws InvalidArgumentException If the filename is invalid.
110
     * @return mixed The file content.
111
     */
112
    public function loadFile($filename)
113
    {
114
        if (!is_string($filename)) {
115
            throw new InvalidArgumentException(
116
                'Configuration file must be a string.'
117
            );
118
        }
119
        if (!file_exists($filename)) {
120
            throw new InvalidArgumentException(
121
                sprintf('Configuration file "%s" does not exist', $filename)
122
            );
123
        }
124
125
        $ext = pathinfo($filename, PATHINFO_EXTENSION);
126
127
        if ($ext == 'php') {
128
            return $this->loadPhpFile($filename);
129
        } elseif ($ext == 'json') {
130
            return $this->loadJsonFile($filename);
131
        } elseif ($ext == 'ini') {
132
            return $this->loadIniFile($filename);
133
        } elseif ($ext == 'yml' || $ext == 'yaml') {
134
            return $this->loadYamlFile($filename);
135
        } else {
136
            throw new InvalidArgumentException(
137
                'Only JSON, INI and PHP files are accepted as a Configuration file.'
138
            );
139
        }
140
    }
141
142
     /**
143
      * For each key, calls `set()`, which calls `offsetSet()`  (from ArrayAccess).
144
      *
145
      * The provided `$data` can be a simple array or an object which implements `Traversable`
146
      * (such as a `ConfigInterface` instance).
147
      *
148
      * @param array|Traversable $data The data to set.
149
      * @return AbstractConfig Chainable
150
      * @see self::set()
151
      * @see self::offsetSet()
152
      */
153
    public function merge($data)
154
    {
155
        foreach ($data as $k => $v) {
156
                $this->set($k, $v);
0 ignored issues
show
Unused Code introduced by
The call to the method Charcoal\Config\AbstractConfig::set() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
157
        }
158
        return $this;
159
    }
160
161
    /**
162
     * A stub for when the default data is empty.
163
     *
164
     * Make sure to reimplement in children ConfigInterface classes if any default data should be set.
165
     *
166
     * @see ConfigInterface::defaults()
167
     * @return array
168
     */
169
    public function defaults()
170
    {
171
        return [];
172
    }
173
174
    /**
175
     * IteratorAggregate > getIterator()
176
     *
177
     * @return ArrayIterator
178
     */
179
    public function getIterator()
180
    {
181
        return new ArrayIterator($this->data());
182
    }
183
184
    /**
185
     * Add a `.ini` file to the configuration.
186
     *
187
     * @param string $filename A INI configuration file.
188
     * @throws InvalidArgumentException If the file or invalid.
189
     * @return AbstractConfig Chainable
190
     */
191
    private function loadIniFile($filename)
192
    {
193
        $config = parse_ini_file($filename, true);
194
        if ($config === false) {
195
            throw new InvalidArgumentException(
196
                sprintf('Ini file "%s" is empty or invalid.')
197
            );
198
        }
199
        return $config;
200
    }
201
202
    /**
203
     * Add a `.json` file to the configuration.
204
     *
205
     * @param string $filename A JSON configuration file.
206
     * @throws InvalidArgumentException If the file or invalid.
207
     * @return AbstractConfig Chainable
208
     */
209
    private function loadJsonFile($filename)
210
    {
211
        $fileContent = file_get_contents($filename);
212
        $config = json_decode($fileContent, true);
213
        $errCode = json_last_error();
214
        if ($errCode == JSON_ERROR_NONE) {
215
            return $config;
216
        }
217
        // Handle JSON error
218
        switch ($errCode) {
219
            case JSON_ERROR_NONE:
220
                break;
221
            case JSON_ERROR_DEPTH:
222
                $errMsg = 'Maximum stack depth exceeded';
223
                break;
224
            case JSON_ERROR_STATE_MISMATCH:
225
                $errMsg = 'Underflow or the modes mismatch';
226
                break;
227
            case JSON_ERROR_CTRL_CHAR:
228
                $errMsg = 'Unexpected control character found';
229
                break;
230
            case JSON_ERROR_SYNTAX:
231
                $errMsg = 'Syntax error, malformed JSON';
232
                break;
233
            case JSON_ERROR_UTF8:
234
                $errMsg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
235
                break;
236
            default:
237
                $errMsg = 'Unknown error';
238
                break;
239
        }
240
241
        throw new InvalidArgumentException(
242
            sprintf('JSON file "%s" could not be parsed: "%s"', $filename, $errMsg)
243
        );
244
245
    }
246
247
    /**
248
     * Add a PHP file to the configuration
249
     *
250
     * @param string $filename A PHP configuration file.
251
     * @return AbstractConfig Chainable
252
     */
253
    private function loadPhpFile($filename)
254
    {
255
        // `$this` is bound to the current configuration object (Current `$this`)
256
        $config = include $filename;
257
        return $config;
258
    }
259
260
    /**
261
     * Add a YAML file to the configuration
262
     *
263
     * @param string $filename A YAML configuration file.
264
     * @return AbstractConfig Chainable
265
     */
266
    private function loadYamlFile($filename)
267
    {
268
        $parser = new YamlParser();
269
        $fileContent = file_get_contents($filename);
270
        $config = $parser->parse($fileContent);
271
        return $config;
272
    }
273
}
274