Completed
Push — master ( 9aa1ed...20ad21 )
by Filipe
03:50
created

Configuration::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 1
cts 1
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
1
<?php
2
3
/**
4
 * This file is part of slick/configuration
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
namespace Slick\Configuration;
11
12
use Slick\Configuration\Driver\Environment;
13
use Slick\Configuration\Driver\Ini;
14
use Slick\Configuration\Driver\Php;
15
use Slick\Configuration\Exception\InvalidArgumentException;
16
17
/**
18
 * Configuration
19
 *
20
 * @package Slick\Configuration
21
*/
22
final class Configuration
23
{
24
    /**@#+
25
     * Known configuration drivers
26
     */
27
    const DRIVER_INI = Ini::class;
28
    const DRIVER_PHP = Php::class;
29
    const DRIVER_ENV = Environment::class;
30
    /**@#- */
31
32
    private $extensionToDriver = [
33
        'ini' => self::DRIVER_INI,
34
        'php' => self::DRIVER_PHP,
35
    ];
36
37
    /**
38
     * @var string
39
     */
40
    private $file;
41
42
    /**
43
     * @var null|string
44
     */
45
    private $driverClass;
46
47
    private static $paths = [
48
        './'
49
    ];
50
51
    /**
52
     * @var null|ConfigurationInterface
53
     */
54
    private static $instance;
55
56
    /**
57
     * Creates a configuration factory
58
     *
59
     * @param string|array $options
60
     * @param null         $driverClass
61
     */
62
    public function __construct($options = null, $driverClass = null)
63
    {
64
        $this->file = $options;
0 ignored issues
show
Documentation Bug introduced by
It seems like $options can also be of type array. 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...
65
        $this->driverClass = $driverClass;
66 18
        self::addPath(getcwd());
67
    }
68 18
69 12
    /**
70
     * Returns the last ConfigurationInterface
71
     *
72
     * If there is no configuration created it will use passed arguments to create one
73
     *
74
     * @param string|array $fileName
75
     * @param null         $driverClass
76
     *
77 3
     * @return ConfigurationInterface|PriorityConfigurationChain
78
     */
79 3
    public static function get($fileName, $driverClass = null)
80 3
    {
81 3
        if (self::$instance === null) {
82 3
            self::$instance = self::create($fileName, $driverClass);
83 3
        }
84
        return self::$instance;
85
    }
86
87
    /**
88
     * Creates a ConfigurationInterface with passed arguments
89
     *
90
     * @param string|array $fileName
91
     * @param null         $driverClass
92
     *
93 9
     * @return ConfigurationInterface|PriorityConfigurationChain
94
     */
95 9
    public static function create($fileName, $driverClass = null)
96
    {
97 9
        $configuration = new Configuration($fileName, $driverClass);
98 9
        return $configuration->initialize();
99 9
    }
100 9
101 9
    /**
102
     * @return PriorityConfigurationChain|ConfigurationInterface
103
     */
104
    public function initialize()
105
    {
106
        $chain = new PriorityConfigurationChain();
107
108
        $options = $this->fixOptions();
109
110
        foreach ($options as $option) {
111
            $priority = $this->setProperties($option);
112 9
            $chain->add($this->createConfigurationDriver(), $priority);
113
        }
114 9
115 9
        return $chain;
116 9
    }
117
118 9
    /**
119 9
     * Prepends a searchable path to available paths list.
120 9
     *
121 3
     * @param string $path
122 3
     */
123
    public static function addPath($path)
124 9
    {
125 9
        if (!in_array($path, self::$paths)) {
126
            array_unshift(self::$paths, $path);
127
        }
128
    }
129
130
    /**
131 18
     * Returns the driver class to be initialized
132
     *
133 18
     * @return mixed|null|string
134 3
     */
135 3
    private function driverClass()
136 3
    {
137
        if (null == $this->driverClass) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $this->driverClass of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
138
            $this->driverClass = $this->determineDriver($this->file);
139 15
        }
140 15
        return $this->driverClass;
141 3
    }
142 3
143
    /**
144 3
     * Tries to determine the driver class based on given file
145
     *
146 12
     * @param string $file
147
     * @return mixed
148
     */
149
    private function determineDriver($file)
150
    {
151
        $exception = new InvalidArgumentException(
152
            "Cannot initialize the configuration driver. I could not determine ".
153
            "the correct driver class."
154
        );
155
156
        if (is_null($file) || !is_string($file)) {
157
            throw $exception;
158
        }
159
160
        $nameDivision = explode('.', $file);
161
        $extension = strtolower(end($nameDivision));
162
163
        if (!array_key_exists($extension, $this->extensionToDriver)) {
164
            throw $exception;
165
        }
166
167
        return $this->extensionToDriver[$extension];
168
    }
169
170
    /**
171
     * Compose the filename with existing paths and return when match
172
     *
173
     * If no match is found the $name is returned as is;
174
     * If no extension is given it will add it from driver class map;
175
     * By default it will try to find <$name>.php file
176
     *
177
     * @param  string $name
178
     *
179
     * @return string
180
     */
181
    private function composeFileName($name)
182
    {
183
        if (is_null($name)) {
184
            return $name;
185
        }
186
187
        $ext = $this->determineExtension();
188
        $withExtension = $this->createName($name, $ext);
189
190
        list($found, $fileName) = $this->searchFor($name, $withExtension);
191
192
        return $found ? $fileName : $name;
193
    }
194
195
    /**
196
     * Determine the extension based on the driver class
197
     *
198
     * If there is no driver class given it will default to .php
199
     *
200
     * @return string
201
     */
202
    private function determineExtension()
203
    {
204
        $ext = 'php';
205
        if (in_array($this->driverClass, $this->extensionToDriver)) {
206
            $map = array_flip($this->extensionToDriver);
207
            $ext = $map[$this->driverClass];
208
        }
209
        return $ext;
210
    }
211
212
    /**
213
     * Creates the name with the extension for known names
214
     *
215
     * @param string $name
216
     * @param string $ext
217
     *
218
     * @return string
219
     */
220
    private function createName($name, $ext)
221
    {
222
        $withExtension = $name;
223
        if (!preg_match('/.*\.(ini|php)/i', $name)) {
224
            $withExtension = "{$name}.{$ext}";
225
        }
226
        return $withExtension;
227
    }
228
229
    /**
230
     * Search for name in the list of paths
231
     *
232
     * @param string $name
233
     * @param string $withExtension
234
     *
235
     * @return array
236
     */
237
    private function searchFor($name, $withExtension)
238
    {
239
        $found = false;
240
        $fileName = $name;
241
242
        foreach (self::$paths as $path) {
243
            $fileName = "{$path}/$withExtension";
244
            if (is_file($fileName)) {
245
                $found = true;
246
                break;
247
            }
248
        }
249
250
        return [$found, $fileName];
251
    }
252
253
    /**
254
     * Creates the configuration driver from current properties
255
     *
256
     * @return ConfigurationInterface
257
     */
258
    private function createConfigurationDriver()
259
    {
260
        $reflection = new \ReflectionClass($this->driverClass());
261
262
        /** @var ConfigurationInterface $config */
263
        $config = $reflection->hasMethod('__construct')
264
            ? $reflection->newInstanceArgs([$this->file])
265
            : $reflection->newInstance();
266
        return $config;
267
    }
268
269
    /**
270
     * Sets the file and driver class
271
     *
272
     * @param array $option
273
     *
274
     * @return int
275
     */
276
    private function setProperties($option)
277
    {
278
        $priority = isset($option[2]) ? $option[2] : 0;
279
        $this->driverClass = isset($option[1]) ? $option[1] : null;
280
        $this->file = isset($option[0]) ? $this->composeFileName($option[0]) : null;
281
        return $priority;
282
    }
283
284
    /**
285
     * Fixes the file for initialization
286
     *
287
     * @return array
288
     */
289
    private function fixOptions()
290
    {
291
        $options = (is_array($this->file))
292
            ? $this->file
293
            : [[$this->file]];
294
        return $options;
295
    }
296
}
297