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