Completed
Push — master ( 05902f...9e6322 )
by mark
01:41 queued 11s
created

Config::getVersionOrder()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
490
            unset($options['dsn']);
491
        }
492
493
        $options = array_merge($parsed, $options);
494
495
        return $options;
496
    }
497
498
    /**
499
     * @inheritDoc
500
     */
501
    public function offsetSet($id, $value)
502
    {
503
        $this->values[$id] = $value;
504
    }
505
506
    /**
507
     * @inheritDoc
508
     *
509
     * @throws \InvalidArgumentException
510
     */
511
    public function offsetGet($id)
512
    {
513
        if (!array_key_exists($id, $this->values)) {
514
            throw new InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
515
        }
516
517
        return $this->values[$id] instanceof Closure ? $this->values[$id]($this) : $this->values[$id];
518
    }
519
520
    /**
521
     * @inheritDoc
522
     */
523
    public function offsetExists($id)
524
    {
525
        return isset($this->values[$id]);
526
    }
527
528
    /**
529
     * @inheritDoc
530
     */
531
    public function offsetUnset($id)
532
    {
533
        unset($this->values[$id]);
534
    }
535
}
536