Completed
Push — master ( d748e1...415000 )
by Mark
18s queued 13s
created

Config::offsetUnset()   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
        if (!class_exists('Symfony\\Component\\Yaml\\Yaml', true)) {
67
            throw new RuntimeException('Missing yaml parser, symfony/yaml package is not installed.');
68 448
        }
69 448
70 448
        $configFile = file_get_contents($configFilePath);
71
        $configArray = Yaml::parse($configFile);
72
73
        if (!is_array($configArray)) {
74
            throw new RuntimeException(sprintf(
75
                'File \'%s\' must be valid YAML',
76
                $configFilePath
77
            ));
78
        }
79 2
80
        return new static($configArray, $configFilePath);
81 2
    }
82 2
83
    /**
84 2
     * Create a new instance of the config class using a JSON file path.
85 1
     *
86 1
     * @param string $configFilePath Path to the JSON File
87
     *
88 1
     * @throws \RuntimeException
89
     *
90 1
     * @return \Phinx\Config\Config
91
     */
92 View Code Duplication
    public static function fromJson($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...
93
    {
94
        if (!function_exists('json_decode')) {
95
            throw new RuntimeException("Need to install JSON PHP extension to use JSON config");
96
        }
97
98
        $configArray = json_decode(file_get_contents($configFilePath), true);
99
        if (!is_array($configArray)) {
100 2
            throw new RuntimeException(sprintf(
101
                'File \'%s\' must be valid JSON',
102 2
                $configFilePath
103 2
            ));
104 1
        }
105 1
106
        return new static($configArray, $configFilePath);
107 1
    }
108
109 1
    /**
110
     * Create a new instance of the config class using a PHP file path.
111
     *
112
     * @param string $configFilePath Path to the PHP File
113
     *
114
     * @throws \RuntimeException
115
     *
116
     * @return \Phinx\Config\Config
117
     */
118
    public static function fromPhp($configFilePath)
119 3
    {
120
        ob_start();
121 3
        /** @noinspection PhpIncludeInspection */
122
        $configArray = include($configFilePath);
123 3
124
        // Hide console output
125
        ob_end_clean();
126 3
127
        if (!is_array($configArray)) {
128 3
            throw new RuntimeException(sprintf(
129 2
                'PHP file \'%s\' must return an array',
130 2
                $configFilePath
131
            ));
132 2
        }
133
134
        return new static($configArray, $configFilePath);
135 1
    }
136
137
    /**
138
     * @inheritDoc
139
     */
140
    public function getEnvironments()
141 25
    {
142
        if (isset($this->values) && isset($this->values['environments'])) {
143 25
            $environments = [];
144 24
            foreach ($this->values['environments'] as $key => $value) {
145 24
                if (is_array($value)) {
146 24
                    $environments[$key] = $value;
147 24
                }
148 24
            }
149 24
150
            return $environments;
151 24
        }
152
153
        return null;
154 1
    }
155
156
    /**
157
     * @inheritDoc
158
     */
159
    public function getEnvironment($name)
160 25
    {
161
        $environments = $this->getEnvironments();
162 25
163
        if (isset($environments[$name])) {
164 25
            if (isset($this->values['environments']['default_migration_table'])) {
165 21
                $environments[$name]['default_migration_table'] =
166 21
                    $this->values['environments']['default_migration_table'];
167 21
            }
168 21
169
            return $this->parseAgnosticDsn($environments[$name]);
170 21
        }
171
172
        return null;
173 5
    }
174
175
    /**
176
     * @inheritDoc
177
     */
178
    public function hasEnvironment($name)
179 9
    {
180
        return ($this->getEnvironment($name) !== null);
181 9
    }
182
183
    /**
184
     * @inheritDoc
185
     */
186
    public function getDefaultEnvironment()
187 20
    {
188
        // The $PHINX_ENVIRONMENT variable overrides all other default settings
189
        $env = getenv('PHINX_ENVIRONMENT');
190 20
        if (!empty($env)) {
191 20
            if ($this->hasEnvironment($env)) {
192 2
                return $env;
193 1
            }
194
195
            throw new RuntimeException(sprintf(
196 1
                'The environment configuration (read from $PHINX_ENVIRONMENT) for \'%s\' is missing',
197 1
                $env
198
            ));
199 1
        }
200
201
        // deprecated: to be removed 0.13
202
        if (isset($this->values['environments']['default_database'])) {
203
            $this->values['environments']['default_environment'] = $this->values['environments']['default_database'];
204 19
        }
205 17
206 16
        // if the user has configured a default environment then use it,
207
        // providing it actually exists!
208
        if (isset($this->values['environments']['default_environment'])) {
209 1
            if ($this->getEnvironment($this->values['environments']['default_environment'])) {
210 1
                return $this->values['environments']['default_environment'];
211 1
            }
212 1
213
            throw new RuntimeException(sprintf(
214
                'The environment configuration for \'%s\' is missing',
215
                $this->values['environments']['default_environment']
216 2
            ));
217 1
        }
218 1
219
        // else default to the first available one
220
        if (is_array($this->getEnvironments()) && count($this->getEnvironments()) > 0) {
221 1
            $names = array_keys($this->getEnvironments());
222
223
            return $names[0];
224
        }
225
226
        throw new RuntimeException('Could not find a default environment');
227 7
    }
228
229 7
    /**
230
     * @inheritDoc
231
     */
232
    public function getAlias($alias)
233
    {
234
        return !empty($this->values['aliases'][$alias]) ? $this->values['aliases'][$alias] : null;
235 448
    }
236
237 448
    /**
238
     * @inheritDoc
239
     */
240
    public function getAliases()
241
    {
242
        return !empty($this->values['aliases']) ? $this->values['aliases'] : [];
243 423
    }
244
245 423
    /**
246 1
     * @inheritDoc
247
     */
248
    public function getConfigFilePath()
249 422
    {
250 219
        return $this->configFilePath;
251 219
    }
252
253 422
    /**
254
     * @inheritDoc
255
     *
256
     * @throws \UnexpectedValueException
257
     */
258 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...
259
    {
260
        if (!isset($this->values['paths']['migrations'])) {
261
            throw new UnexpectedValueException('Migrations path missing from config file');
262 14
        }
263
264 14
        if (is_string($this->values['paths']['migrations'])) {
265
            $this->values['paths']['migrations'] = [$this->values['paths']['migrations']];
266 14
        }
267
268
        return $this->values['paths']['migrations'];
269
    }
270
271
    /**
272 48
     * Gets the base class name for migrations.
273
     *
274 48
     * @param bool $dropNamespace Return the base migration class name without the namespace.
275 28
     *
276
     * @return string
277
     */
278 20
    public function getMigrationBaseClassName($dropNamespace = true)
279 13
    {
280 13
        $className = !isset($this->values['migration_base_class']) ? 'Phinx\Migration\AbstractMigration' : $this->values['migration_base_class'];
281
282 20
        return $dropNamespace ? substr(strrchr($className, '\\'), 1) ?: $className : $className;
283
    }
284
285
    /**
286
     * @inheritDoc
287
     *
288
     * @throws \UnexpectedValueException
289
     */
290 14 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...
291
    {
292 14
        if (!isset($this->values['paths']['seeds'])) {
293 13
            throw new UnexpectedValueException('Seeds path missing from config file');
294
        }
295
296 1
        if (is_string($this->values['paths']['seeds'])) {
297
            $this->values['paths']['seeds'] = [$this->values['paths']['seeds']];
298
        }
299
300
        return $this->values['paths']['seeds'];
301
    }
302
303
    /**
304 14
     * Get the template file name.
305
     *
306 14
     * @return string|false
307 10
     */
308 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...
309
    {
310 4
        if (!isset($this->values['templates']['file'])) {
311
            return false;
312
        }
313
314
        return $this->values['templates']['file'];
315
    }
316
317
    /**
318 384
     * Get the template class name.
319
     *
320 384
     * @return string|false
321 162
     */
322 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...
323
    {
324 222
        if (!isset($this->values['templates']['class'])) {
325
            return false;
326
        }
327
328
        return $this->values['templates']['class'];
329
    }
330
331
    /**
332 357
     * {@inheritdoc}
333
     */
334 357
    public function getDataDomain()
335
    {
336 357
        if (!isset($this->values['data_domain'])) {
337
            return [];
338
        }
339
340
        return $this->values['data_domain'];
341
    }
342
343
    /**
344
     * Get the version order.
345
     *
346
     * @return string
347 448
     */
348
    public function getVersionOrder()
349
    {
350
        if (!isset($this->values['version_order'])) {
351 448
            return self::VERSION_ORDER_CREATION_TIME;
352 448
        }
353 448
354 2
        return $this->values['version_order'];
355 2
    }
356 448
357
    /**
358
     * Is version order creation time?
359 448
     *
360 448
     * @return bool
361
     */
362
    public function isVersionOrderCreationTime()
363 448
    {
364
        $versionOrder = $this->getVersionOrder();
365
366
        return $versionOrder == self::VERSION_ORDER_CREATION_TIME;
367
    }
368
369
    /**
370
     * Get the bootstrap file path
371
     *
372
     * @return string|false
373 448
     */
374
    public function getBootstrapFile()
375 448
    {
376 448
        if (!isset($this->values['paths']['bootstrap'])) {
377 447
            return false;
378 446
        }
379 446
380
        return $this->values['paths']['bootstrap'];
381 446
    }
382 446
383 446
    /**
384 446
     * Replace tokens in the specified array.
385 446
     *
386 446
     * @param array $arr Array to replace
387
     *
388 43
     * @return array
389 448
     */
390 448
    protected function replaceTokens(array $arr)
391
    {
392
        // Get environment variables
393
        // Depending on configuration of server / OS and variables_order directive,
394
        // environment variables either end up in $_SERVER (most likely) or $_ENV,
395
        // so we search through both
396 213
        $tokens = [];
397
        foreach (array_merge($_ENV, $_SERVER) as $varname => $varvalue) {
398 213
            if (strpos($varname, 'PHINX_') === 0) {
399 213
                $tokens['%%' . $varname . '%%'] = $varvalue;
400
            }
401
        }
402
403
        // Phinx defined tokens (override env tokens)
404 2
        $tokens['%%PHINX_CONFIG_PATH%%'] = $this->getConfigFilePath();
405
        $tokens['%%PHINX_CONFIG_DIR%%'] = dirname($this->getConfigFilePath());
406 2
407 1
        // Recurse the array and replace tokens
408
        return $this->recurseArrayForTokens($arr, $tokens);
409
    }
410 1
411
    /**
412
     * Recurse an array for the specified tokens and replace them.
413
     *
414
     * @param array $arr Array to recurse
415
     * @param array $tokens Array of tokens to search for
416 1
     *
417
     * @return array
418 1
     */
419
    protected function recurseArrayForTokens($arr, $tokens)
420
    {
421
        $out = [];
422
        foreach ($arr as $name => $value) {
423
            if (is_array($value)) {
424 1
                $out[$name] = $this->recurseArrayForTokens($value, $tokens);
425
                continue;
426 1
            }
427 1
            if (is_string($value)) {
428
                foreach ($tokens as $token => $tval) {
429
                    $value = str_replace($token, $tval, $value);
430
                }
431
                $out[$name] = $value;
432
                continue;
433
            }
434
            $out[$name] = $value;
435
        }
436
437
        return $out;
438
    }
439
440
    /**
441
     * Parse a database-agnostic DSN into individual options.
442
     *
443
     * @param array $options Options
444
     *
445
     * @return array
446
     */
447
    protected function parseAgnosticDsn(array $options)
448
    {
449
        if (isset($options['dsn']) && is_string($options['dsn'])) {
450
            $regex = '#^(?P<adapter>[^\\:]+)\\://(?:(?P<user>[^\\:@]+)(?:\\:(?P<pass>[^@]*))?@)?' .
451
                '(?P<host>[^\\:@/]+)(?:\\:(?P<port>[1-9]\\d*))?/(?P<name>[^\?]+)(?:\?(?P<query>.*))?$#';
452
            if (preg_match($regex, trim($options['dsn']), $parsedOptions)) {
453
                $additionalOpts = [];
454
                if (isset($parsedOptions['query'])) {
455
                    parse_str($parsedOptions['query'], $additionalOpts);
456
                }
457
                $validOptions = ['adapter', 'user', 'pass', 'host', 'port', 'name'];
458
                $parsedOptions = array_filter(array_intersect_key($parsedOptions, array_flip($validOptions)));
459
                $options = array_merge($additionalOpts, $parsedOptions, $options);
460
                unset($options['dsn']);
461
            }
462
        }
463
464
        return $options;
465
    }
466
467
    /**
468
     * @inheritDoc
469
     */
470
    public function offsetSet($id, $value)
471
    {
472
        $this->values[$id] = $value;
473
    }
474
475
    /**
476
     * @inheritDoc
477
     *
478
     * @throws \InvalidArgumentException
479
     */
480
    public function offsetGet($id)
481
    {
482
        if (!array_key_exists($id, $this->values)) {
483
            throw new InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
484
        }
485
486
        return $this->values[$id] instanceof Closure ? $this->values[$id]($this) : $this->values[$id];
487
    }
488
489
    /**
490
     * @inheritDoc
491
     */
492
    public function offsetExists($id)
493
    {
494
        return isset($this->values[$id]);
495
    }
496
497
    /**
498
     * @inheritDoc
499
     */
500
    public function offsetUnset($id)
501
    {
502
        unset($this->values[$id]);
503
    }
504
}
505