Completed
Pull Request — master (#1710)
by Flávio
01:25
created

Config::offsetExists()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
1
<?php
2
3
/**
4
 * MIT License
5
 * For full license information, please view the LICENSE file that was distributed with this source code.
6
 */
7
8
namespace Phinx\Config;
9
10
use Closure;
11
use InvalidArgumentException;
12
use RuntimeException;
13
use Symfony\Component\Yaml\Yaml;
14
use UnexpectedValueException;
15
16
/**
17
 * Phinx configuration class.
18
 *
19
 * @package Phinx
20
 * @author Rob Morgan
21
 */
22
class Config implements ConfigInterface, NamespaceAwareInterface
23
{
24
    use NamespaceAwareTrait;
25
26
    /**
27
     * The value that identifies a version order by creation time.
28
     */
29
    const VERSION_ORDER_CREATION_TIME = 'creation';
30
31
    /**
32
     * The value that identifies a version order by execution time.
33
     */
34
    const VERSION_ORDER_EXECUTION_TIME = 'execution';
35
36
    /**
37
     * @var array
38
     */
39
    private $values = [];
40
41
    /**
42
     * @var string
43
     */
44
    protected $configFilePath;
45
46
    /**
47
     * @inheritDoc
48
     */
49
    public function __construct(array $configArray, $configFilePath = null)
50
    {
51
        $this->configFilePath = $configFilePath;
52
        $this->values = $this->replaceTokens($configArray);
53
    }
54
55
    /**
56
     * Create a new instance of the config class using a Yaml file path.
57
     *
58
     * @param string $configFilePath Path to the Yaml File
59
     *
60
     * @throws \RuntimeException
61
     *
62
     * @return \Phinx\Config\Config
63
     */
64 View Code Duplication
    public static function fromYaml($configFilePath)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
65
    {
66 448
        $configFile = file_get_contents($configFilePath);
67
        $configArray = Yaml::parse($configFile);
68 448
69 448
        if (!is_array($configArray)) {
70 448
            throw new RuntimeException(sprintf(
71
                'File \'%s\' must be valid YAML',
72
                $configFilePath
73
            ));
74
        }
75
76
        return new static($configArray, $configFilePath);
77
    }
78
79 2
    /**
80
     * Create a new instance of the config class using a JSON file path.
81 2
     *
82 2
     * @param string $configFilePath Path to the JSON File
83
     *
84 2
     * @throws \RuntimeException
85 1
     *
86 1
     * @return \Phinx\Config\Config
87
     */
88 1
    public static function fromJson($configFilePath)
89
    {
90 1
        if (!function_exists('json_decode')) {
91
            throw new RuntimeException("Need to install JSON PHP extension to use JSON config");
92
        }
93
94
        $configArray = json_decode(file_get_contents($configFilePath), true);
95
        if (!is_array($configArray)) {
96
            throw new RuntimeException(sprintf(
97
                'File \'%s\' must be valid JSON',
98
                $configFilePath
99
            ));
100 2
        }
101
102 2
        return new static($configArray, $configFilePath);
103 2
    }
104 1
105 1
    /**
106
     * Create a new instance of the config class using a PHP file path.
107 1
     *
108
     * @param string $configFilePath Path to the PHP File
109 1
     *
110
     * @throws \RuntimeException
111
     *
112
     * @return \Phinx\Config\Config
113
     */
114 View Code Duplication
    public static function fromPhp($configFilePath)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
115
    {
116
        ob_start();
117
        /** @noinspection PhpIncludeInspection */
118
        $configArray = include($configFilePath);
119 3
120
        // Hide console output
121 3
        ob_end_clean();
122
123 3
        if (!is_array($configArray)) {
124
            throw new RuntimeException(sprintf(
125
                'PHP file \'%s\' must return an array',
126 3
                $configFilePath
127
            ));
128 3
        }
129 2
130 2
        return new static($configArray, $configFilePath);
131
    }
132 2
133
    /**
134
     * @inheritDoc
135 1
     */
136
    public function getEnvironments()
137
    {
138
        if (isset($this->values) && isset($this->values['environments'])) {
139
            $environments = [];
140
            foreach ($this->values['environments'] as $key => $value) {
141 25
                if (is_array($value)) {
142
                    $environments[$key] = $value;
143 25
                }
144 24
            }
145 24
146 24
            return $environments;
147 24
        }
148 24
149 24
        return null;
150
    }
151 24
152
    /**
153
     * @inheritDoc
154 1
     */
155
    public function getEnvironment($name)
156
    {
157
        $environments = $this->getEnvironments();
158
159
        if (isset($environments[$name])) {
160 25
            if (isset($this->values['environments']['default_migration_table'])) {
161
                $environments[$name]['default_migration_table'] =
162 25
                    $this->values['environments']['default_migration_table'];
163
            }
164 25
165 21
            return $this->parseAgnosticDsn($environments[$name]);
166 21
        }
167 21
168 21
        return null;
169
    }
170 21
171
    /**
172
     * @inheritDoc
173 5
     */
174
    public function hasEnvironment($name)
175
    {
176
        return ($this->getEnvironment($name) !== null);
177
    }
178
179 9
    /**
180
     * @inheritDoc
181 9
     */
182
    public function getDefaultEnvironment()
183
    {
184
        // The $PHINX_ENVIRONMENT variable overrides all other default settings
185
        $env = getenv('PHINX_ENVIRONMENT');
186
        if (!empty($env)) {
187 20
            if ($this->hasEnvironment($env)) {
188
                return $env;
189
            }
190 20
191 20
            throw new RuntimeException(sprintf(
192 2
                'The environment configuration (read from $PHINX_ENVIRONMENT) for \'%s\' is missing',
193 1
                $env
194
            ));
195
        }
196 1
197 1
        // if the user has configured a default database then use it,
198
        // providing it actually exists!
199 1
        if (isset($this->values['environments']['default_database'])) {
200
            if ($this->getEnvironment($this->values['environments']['default_database'])) {
201
                return $this->values['environments']['default_database'];
202
            }
203
204 19
            throw new RuntimeException(sprintf(
205 17
                'The environment configuration for \'%s\' is missing',
206 16
                $this->values['environments']['default_database']
207
            ));
208
        }
209 1
210 1
        // else default to the first available one
211 1
        if (is_array($this->getEnvironments()) && count($this->getEnvironments()) > 0) {
212 1
            $names = array_keys($this->getEnvironments());
213
214
            return $names[0];
215
        }
216 2
217 1
        throw new RuntimeException('Could not find a default environment');
218 1
    }
219
220
    /**
221 1
     * @inheritDoc
222
     */
223
    public function getAlias($alias)
224
    {
225
        return !empty($this->values['aliases'][$alias]) ? $this->values['aliases'][$alias] : null;
226
    }
227 7
228
    /**
229 7
     * @inheritDoc
230
     */
231
    public function getAliases()
232
    {
233
        return !empty($this->values['aliases']) ? $this->values['aliases'] : [];
234
    }
235 448
236
    /**
237 448
     * @inheritDoc
238
     */
239
    public function getConfigFilePath()
240
    {
241
        return $this->configFilePath;
242
    }
243 423
244
    /**
245 423
     * @inheritDoc
246 1
     *
247
     * @throws \UnexpectedValueException
248
     */
249 422 View Code Duplication
    public function getMigrationPaths()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
250 219
    {
251 219
        if (!isset($this->values['paths']['migrations'])) {
252
            throw new UnexpectedValueException('Migrations path missing from config file');
253 422
        }
254
255
        if (is_string($this->values['paths']['migrations'])) {
256
            $this->values['paths']['migrations'] = [$this->values['paths']['migrations']];
257
        }
258
259
        return $this->values['paths']['migrations'];
260
    }
261
262 14
    /**
263
     * Gets the base class name for migrations.
264 14
     *
265
     * @param bool $dropNamespace Return the base migration class name without the namespace.
266 14
     *
267
     * @return string
268
     */
269
    public function getMigrationBaseClassName($dropNamespace = true)
270
    {
271
        $className = !isset($this->values['migration_base_class']) ? 'Phinx\Migration\AbstractMigration' : $this->values['migration_base_class'];
272 48
273
        return $dropNamespace ? substr(strrchr($className, '\\'), 1) ?: $className : $className;
274 48
    }
275 28
276
    /**
277
     * @inheritDoc
278 20
     *
279 13
     * @throws \UnexpectedValueException
280 13
     */
281 View Code Duplication
    public function getSeedPaths()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
282 20
    {
283
        if (!isset($this->values['paths']['seeds'])) {
284
            throw new UnexpectedValueException('Seeds path missing from config file');
285
        }
286
287
        if (is_string($this->values['paths']['seeds'])) {
288
            $this->values['paths']['seeds'] = [$this->values['paths']['seeds']];
289
        }
290 14
291
        return $this->values['paths']['seeds'];
292 14
    }
293 13
294
    /**
295
     * Get the template file name.
296 1
     *
297
     * @return string|false
298
     */
299 View Code Duplication
    public function getTemplateFile()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
300
    {
301
        if (!isset($this->values['templates']['file'])) {
302
            return false;
303
        }
304 14
305
        return $this->values['templates']['file'];
306 14
    }
307 10
308
    /**
309
     * Get the template class name.
310 4
     *
311
     * @return string|false
312
     */
313 View Code Duplication
    public function getTemplateClass()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
314
    {
315
        if (!isset($this->values['templates']['class'])) {
316
            return false;
317
        }
318 384
319
        return $this->values['templates']['class'];
320 384
    }
321 162
322
    /**
323
     * Get the version order.
324 222
     *
325
     * @return string
326
     */
327
    public function getVersionOrder()
328
    {
329
        if (!isset($this->values['version_order'])) {
330
            return self::VERSION_ORDER_CREATION_TIME;
331
        }
332 357
333
        return $this->values['version_order'];
334 357
    }
335
336 357
    /**
337
     * Is version order creation time?
338
     *
339
     * @return bool
340
     */
341
    public function isVersionOrderCreationTime()
342
    {
343
        $versionOrder = $this->getVersionOrder();
344
345
        return $versionOrder == self::VERSION_ORDER_CREATION_TIME;
346
    }
347 448
348
    /**
349
     * Get the bootstrap file path
350
     *
351 448
     * @return string|false
352 448
     */
353 448
    public function getBootstrapFile()
354 2
    {
355 2
        if (!isset($this->values['paths']['bootstrap'])) {
356 448
            return false;
357
        }
358
359 448
        return $this->values['paths']['bootstrap'];
360 448
    }
361
362
    /**
363 448
     * Replace tokens in the specified array.
364
     *
365
     * @param array $arr Array to replace
366
     *
367
     * @return array
368
     */
369
    protected function replaceTokens(array $arr)
370
    {
371
        // Get environment variables
372
        // Depending on configuration of server / OS and variables_order directive,
373 448
        // environment variables either end up in $_SERVER (most likely) or $_ENV,
374
        // so we search through both
375 448
        $tokens = [];
376 448
        foreach (array_merge($_ENV, $_SERVER) as $varname => $varvalue) {
377 447
            if (strpos($varname, 'PHINX_') === 0) {
378 446
                $tokens['%%' . $varname . '%%'] = $varvalue;
379 446
            }
380
        }
381 446
382 446
        // Phinx defined tokens (override env tokens)
383 446
        $tokens['%%PHINX_CONFIG_PATH%%'] = $this->getConfigFilePath();
384 446
        $tokens['%%PHINX_CONFIG_DIR%%'] = dirname($this->getConfigFilePath());
385 446
386 446
        // Recurse the array and replace tokens
387
        return $this->recurseArrayForTokens($arr, $tokens);
388 43
    }
389 448
390 448
    /**
391
     * Recurse an array for the specified tokens and replace them.
392
     *
393
     * @param array $arr Array to recurse
394
     * @param array $tokens Array of tokens to search for
395
     *
396 213
     * @return array
397
     */
398 213
    protected function recurseArrayForTokens($arr, $tokens)
399 213
    {
400
        $out = [];
401
        foreach ($arr as $name => $value) {
402
            if (is_array($value)) {
403
                $out[$name] = $this->recurseArrayForTokens($value, $tokens);
404 2
                continue;
405
            }
406 2
            if (is_string($value)) {
407 1
                foreach ($tokens as $token => $tval) {
408
                    $value = str_replace($token, $tval, $value);
409
                }
410 1
                $out[$name] = $value;
411
                continue;
412
            }
413
            $out[$name] = $value;
414
        }
415
416 1
        return $out;
417
    }
418 1
419
    /**
420
     * Parse a database-agnostic DSN into individual options.
421
     *
422
     * @param array $options Options
423
     *
424 1
     * @return array
425
     */
426 1
    protected function parseAgnosticDsn(array $options)
427 1
    {
428
        if (isset($options['dsn']) && is_string($options['dsn'])) {
429
            $regex = '#^(?P<adapter>[^\\:]+)\\://(?:(?P<user>[^\\:@]+)(?:\\:(?P<pass>[^@]*))?@)?'
430
                   . '(?P<host>[^\\:@/]+)(?:\\:(?P<port>[1-9]\\d*))?/(?P<name>[^\?]+)(?:\?(?P<query>.*))?$#';
431
            if (preg_match($regex, trim($options['dsn']), $parsedOptions)) {
432
                $additionalOpts = [];
433
                if (isset($parsedOptions['query'])) {
434
                    parse_str($parsedOptions['query'], $additionalOpts);
435
                }
436
                $validOptions = ['adapter', 'user', 'pass', 'host', 'port', 'name'];
437
                $parsedOptions = array_filter(array_intersect_key($parsedOptions, array_flip($validOptions)));
438
                $options = array_merge($additionalOpts, $parsedOptions, $options);
439
                unset($options['dsn']);
440
            }
441
        }
442
443
        return $options;
444
    }
445
446
    /**
447
     * @inheritDoc
448
     */
449
    public function offsetSet($id, $value)
450
    {
451
        $this->values[$id] = $value;
452
    }
453
454
    /**
455
     * @inheritDoc
456
     *
457
     * @throws \InvalidArgumentException
458
     */
459
    public function offsetGet($id)
460
    {
461
        if (!array_key_exists($id, $this->values)) {
462
            throw new InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
463
        }
464
465
        return $this->values[$id] instanceof Closure ? $this->values[$id]($this) : $this->values[$id];
466
    }
467
468
    /**
469
     * @inheritDoc
470
     */
471
    public function offsetExists($id)
472
    {
473
        return isset($this->values[$id]);
474
    }
475
476
    /**
477
     * @inheritDoc
478
     */
479
    public function offsetUnset($id)
480
    {
481
        unset($this->values[$id]);
482
    }
483
}
484