Completed
Pull Request — master (#1773)
by
unknown
01:34
created

AbstractCommand::getMigrationTemplateFilename()   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 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\Console\Command;
9
10
use InvalidArgumentException;
11
use Phinx\Config\Config;
12
use Phinx\Config\ConfigInterface;
13
use Phinx\Db\Adapter\AdapterInterface;
14
use Phinx\Migration\Manager;
15
use Phinx\Util\Util;
16
use Symfony\Component\Config\FileLocator;
17
use Symfony\Component\Console\Command\Command;
18
use Symfony\Component\Console\Input\InputInterface;
19
use Symfony\Component\Console\Input\InputOption;
20
use Symfony\Component\Console\Output\OutputInterface;
21
use UnexpectedValueException;
22
23
/**
24
 * Abstract command, contains bootstrapping info
25
 *
26
 * @author Rob Morgan <[email protected]>
27
 */
28
abstract class AbstractCommand extends Command
29
{
30
    public const FORMAT_JSON = 'json';
31
    public const FORMAT_YML_ALIAS = 'yaml';
32
    public const FORMAT_YML = 'yml';
33
    public const FORMAT_PHP = 'php';
34
35
    /**
36
     * The location of the default migration template.
37
     */
38
    protected const DEFAULT_MIGRATION_TEMPLATE = '/../../Migration/Migration.template.php.dist';
39
40
    /**
41
     * The location of the default seed template.
42
     */
43
    protected const DEFAULT_SEED_TEMPLATE = '/../../Seed/Seed.template.php.dist';
44
45
    /**
46
     * @var \Phinx\Config\ConfigInterface
47
     */
48
    protected $config;
49
50
    /**
51
     * @var \Phinx\Db\Adapter\AdapterInterface
52
     */
53
    protected $adapter;
54
55
    /**
56
     * @var \Phinx\Migration\Manager
57
     */
58
    protected $manager;
59
60
    /**
61
     * Exit code for when command executes successfully
62
     * @var int
63
     */
64
    public const CODE_SUCCESS = 0;
65
66
    /**
67
     * Exit code for when command hits a non-recoverable error during execution
68
     * @var int
69
     */
70
    public const CODE_ERROR = 1;
71
72
    /**
73
     * Exit code for when status command is run and there are missing migrations
74
     * @var int
75
     */
76
    public const CODE_STATUS_MISSING = 2;
77 54
78
    /**
79 54
     * Exit code for when status command is run and there are no missing migations,
80 54
     * but does have down migrations
81 54
     * @var int
82
     */
83
    public const CODE_STATUS_DOWN = 3;
84
85
    /**
86
     * {@inheritDoc}
87
     *
88
     * @return void
89
     */
90 32
    protected function configure()
91
    {
92 32
        $this->addOption('--configuration', '-c', InputOption::VALUE_REQUIRED, 'The configuration file to load');
93
        $this->addOption('--parser', '-p', InputOption::VALUE_REQUIRED, 'Parser used to read the config file. Defaults to YAML');
94
    }
95
96 32
    /**
97
     * Bootstrap Phinx.
98
     *
99 32
     * @param \Symfony\Component\Console\Input\InputInterface $input Input
100
     * @param \Symfony\Component\Console\Output\OutputInterface $output Output
101 32
     *
102
     * @return void
103 32
     */
104 32
    public function bootstrap(InputInterface $input, OutputInterface $output)
105 32
    {
106
        /** @var \Phinx\Config\ConfigInterface|null $config */
107
        $config = $this->getConfig();
108 32
        if (!$config) {
109
            $this->loadConfig($input, $output);
110 6
        }
111
112 6
        $this->loadManager($input, $output);
113 6
114 6
        if ($bootstrap = $this->getConfig()->getBootstrapFile()) {
115 32
            $output->writeln('<info>using bootstrap</info> .' . str_replace(getcwd(), '', realpath($bootstrap)) . ' ');
116
            Util::loadPhpFile($bootstrap);
117
        }
118 32
119
        // report the paths
120
        $paths = $this->getConfig()->getMigrationPaths();
121
122
        $output->writeln('<info>using migration paths</info> ');
123
124
        foreach (Util::globAll($paths) as $path) {
125
            $output->writeln('<info> - ' . realpath($path) . '</info>');
126 32
        }
127
128 32
        try {
129 32
            $paths = $this->getConfig()->getSeedPaths();
130
131
            $output->writeln('<info>using seed paths</info> ');
132
133
            foreach (Util::globAll($paths) as $path) {
134
                $output->writeln('<info> - ' . realpath($path) . '</info>');
135
            }
136
        } catch (UnexpectedValueException $e) {
137 32
            // do nothing as seeds are optional
138
        }
139 32
    }
140
141
    /**
142
     * Sets the config.
143
     *
144
     * @param \Phinx\Config\ConfigInterface $config Config
145
     *
146
     * @return $this
147
     */
148
    public function setConfig(ConfigInterface $config)
149
    {
150
        $this->config = $config;
151
152
        return $this;
153
    }
154
155
    /**
156
     * Gets the config.
157
     *
158
     * @return \Phinx\Config\ConfigInterface
159
     */
160
    public function getConfig()
161
    {
162
        return $this->config;
163
    }
164
165
    /**
166
     * Sets the database adapter.
167
     *
168
     * @param \Phinx\Db\Adapter\AdapterInterface $adapter Adapter
169
     *
170 32
     * @return $this
171
     */
172 32
    public function setAdapter(AdapterInterface $adapter)
173 32
    {
174
        $this->adapter = $adapter;
175
176
        return $this;
177
    }
178
179
    /**
180
     * Gets the database adapter.
181 32
     *
182
     * @return \Phinx\Db\Adapter\AdapterInterface
183 32
     */
184
    public function getAdapter()
185
    {
186
        return $this->adapter;
187
    }
188
189
    /**
190
     * Sets the migration manager.
191
     *
192 10
     * @param \Phinx\Migration\Manager $manager Manager
193
     *
194 10
     * @return $this
195
     */
196 10
    public function setManager(Manager $manager)
197
    {
198 10
        $this->manager = $manager;
199 4
200 4
        return $this;
201
    }
202 10
203
    /**
204
     * Gets the migration manager.
205
     *
206 10
     * @return \Phinx\Migration\Manager|null
207
     */
208 10
    public function getManager()
209
    {
210 10
        return $this->manager;
211
    }
212 6
213
    /**
214
     * Returns config file path
215 4
     *
216 4
     * @param \Symfony\Component\Console\Input\InputInterface $input Input
217
     *
218 4
     * @return string
219 3
     */
220 3
    protected function locateConfigFile(InputInterface $input)
221
    {
222 3
        $configFile = $input->getOption('configuration');
223 1
224
        $useDefault = false;
225
226
        if ($configFile === null || $configFile === false) {
227
            $useDefault = true;
228
        }
229
230
        $cwd = getcwd();
231
232
        // locate the phinx config file (default: phinx.yml)
233
        // In future walk the tree in reverse (max 10 levels)
234
        $locator = new FileLocator([
235
            $cwd . DIRECTORY_SEPARATOR,
236
        ]);
237
238
        if (!$useDefault) {
239
            // Locate() throws an exception if the file does not exist
240
            return $locator->locate($configFile, $cwd, true);
241
        }
242
243
        $possibleConfigFiles = ['phinx.php', 'phinx.json', 'phinx.yaml', 'phinx.yml'];
244
        foreach ($possibleConfigFiles as $configFile) {
245
            try {
246
                return $locator->locate($configFile, $cwd, true);
247
            } catch (InvalidArgumentException $exception) {
248
                $lastException = $exception;
249
            }
250
        }
251
        throw $lastException;
252
    }
253
254
    /**
255
     * Parse the config file and load it into the config object
256
     *
257
     * @param \Symfony\Component\Console\Input\InputInterface $input Input
258
     * @param \Symfony\Component\Console\Output\OutputInterface $output Output
259
     *
260
     * @throws \InvalidArgumentException
261
     *
262
     * @return void
263
     */
264
    protected function loadConfig(InputInterface $input, OutputInterface $output)
265
    {
266
        $configFilePath = $this->locateConfigFile($input);
267
        $output->writeln('<info>using config file</info> .' . str_replace(getcwd(), '', realpath($configFilePath)));
268
269
        $parser = $input->getOption('parser');
270
271
        // If no parser is specified try to determine the correct one from the file extension.  Defaults to YAML
272
        if ($parser === null) {
273
            $extension = pathinfo($configFilePath, PATHINFO_EXTENSION);
274
275
            switch (strtolower($extension)) {
276
                case self::FORMAT_JSON:
277
                    $parser = self::FORMAT_JSON;
278
                    break;
279
                case self::FORMAT_PHP:
280
                    $parser = self::FORMAT_PHP;
281
                    break;
282
                case self::FORMAT_YML_ALIAS:
283 32
                case self::FORMAT_YML:
284
                default:
285 32
                    $parser = self::FORMAT_YML;
286
            }
287
        }
288
289 32
        switch (strtolower($parser)) {
290 32
            case self::FORMAT_JSON:
291 32
                $config = Config::fromJson($configFilePath);
0 ignored issues
show
Bug introduced by
It seems like $configFilePath defined by $this->locateConfigFile($input) on line 266 can also be of type array; however, Phinx\Config\Config::fromJson() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
292
                break;
293 32
            case self::FORMAT_PHP:
294
                $config = Config::fromPhp($configFilePath);
0 ignored issues
show
Bug introduced by
It seems like $configFilePath defined by $this->locateConfigFile($input) on line 266 can also be of type array; however, Phinx\Config\Config::fromPhp() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
295
                break;
296
            case self::FORMAT_YML_ALIAS:
297
            case self::FORMAT_YML:
298
                $config = Config::fromYaml($configFilePath);
0 ignored issues
show
Bug introduced by
It seems like $configFilePath defined by $this->locateConfigFile($input) on line 266 can also be of type array; however, Phinx\Config\Config::fromYaml() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
299
                break;
300
            default:
301
                throw new InvalidArgumentException(sprintf('\'%s\' is not a valid parser.', $parser));
302 13
        }
303
304 13
        $output->writeln('<info>using config parser</info> ' . $parser);
305
306
        $this->setConfig($config);
307
    }
308
309
    /**
310
     * Load the migrations manager and inject the config
311 13
     *
312
     * @param \Symfony\Component\Console\Input\InputInterface $input Input
313
     * @param \Symfony\Component\Console\Output\OutputInterface $output Output
314
     *
315
     * @return void
316
     */
317 13
    protected function loadManager(InputInterface $input, OutputInterface $output)
318
    {
319
        if ($this->getManager() === null) {
320
            $manager = new Manager($this->getConfig(), $input, $output);
321
            $container = $this->getConfig()->getContainer();
322
            if ($container !== null) {
323
                $manager->setContainer($container);
324
            }
325
            $this->setManager($manager);
326 2
        } else {
327
            $manager = $this->getManager();
328 2
            $manager->setInput($input);
329
            $manager->setOutput($output);
330
        }
331
    }
332
333
    /**
334
     * Verify that the migration directory exists and is writable.
335 2
     *
336
     * @param string $path Path
337
     *
338
     * @throws \InvalidArgumentException
339
     *
340
     * @return void
341 2
     */
342
    protected function verifyMigrationDirectory($path)
343
    {
344
        if (!is_dir($path)) {
345
            throw new InvalidArgumentException(sprintf(
346
                'Migration directory "%s" does not exist',
347
                $path
348 2
            ));
349
        }
350 2
351
        if (!is_writable($path)) {
352
            throw new InvalidArgumentException(sprintf(
353
                'Migration directory "%s" is not writable',
354
                $path
355
            ));
356
        }
357
    }
358 1
359
    /**
360 1
     * Verify that the seed directory exists and is writable.
361
     *
362
     * @param string $path Path
363
     *
364
     * @throws \InvalidArgumentException
365
     *
366
     * @return void
367
     */
368
    protected function verifySeedDirectory($path)
369
    {
370
        if (!is_dir($path)) {
371
            throw new InvalidArgumentException(sprintf(
372
                'Seed directory "%s" does not exist',
373
                $path
374
            ));
375
        }
376
377
        if (!is_writable($path)) {
378
            throw new InvalidArgumentException(sprintf(
379
                'Seed directory "%s" is not writable',
380
                $path
381
            ));
382
        }
383
    }
384
385
    /**
386
     * Returns the migration template filename.
387
     *
388
     * @return string
389
     */
390
    protected function getMigrationTemplateFilename()
391
    {
392
        return __DIR__ . self::DEFAULT_MIGRATION_TEMPLATE;
393
    }
394
395
    /**
396
     * Returns the seed template filename.
397
     *
398
     * @return string
399
     */
400
    protected function getSeedTemplateFilename()
401
    {
402
        return __DIR__ . self::DEFAULT_SEED_TEMPLATE;
403
    }
404
405
    /**
406
     * Gets environment to use for command.
407
     *
408
     * @return string|null name of environment, null if could not be found
409
     */
410
    protected function getEnvironment(InputInterface $input, OutputInterface $output): ?string
411
    {
412
        $environment = $input->getOption('environment');
413
414
        if ($environment === null) {
415
            $environment = $this->getConfig()->getDefaultEnvironment();
416
            $output->writeln('<comment>warning</comment> no environment specified, defaulting to: ' . $environment);
417
        } else {
418
            $output->writeln('<info>using environment</info> ' . $environment);
419
        }
420
421
        if (!$this->getConfig()->hasEnvironment($environment)) {
422
            $output->writeln(sprintf('<error>The environment "%s" does not exist</error>', $environment));
423
424
            return null;
425
        }
426
427
        return $environment;
428
    }
429
430
    /**
431
     * Prints out general runtime information about loaded environment.
432
     *
433
     * @param array|null $envOptions environment options
434
     * @param \Symfony\Component\Console\Output\OutputInterface $output Output
435
     * @return bool
436
     */
437
    protected function checkEnvironmentOptions(?array $envOptions, OutputInterface $output): bool
438
    {
439
        if (isset($envOptions['adapter'])) {
440
            $output->writeln('<info>using adapter</info> ' . $envOptions['adapter']);
441
        }
442
443
        if (isset($envOptions['wrapper'])) {
444
            $output->writeln('<info>using wrapper</info> ' . $envOptions['wrapper']);
445
        }
446
447
        if (
448
            isset($envOptions['adapter'])
449
            && $envOptions['adapter'] === 'sqlite'
450
            && isset($envOptions['memory'])
451
            && $envOptions['memory'] === true
452
        ) {
453
            $output->writeln('<info>using :memory: database</info>');
454
        } elseif (isset($envOptions['name'])) {
455
            $output->writeln('<info>using database</info> ' . $envOptions['name']);
456
        } else {
457
            $output->writeln(
458
                '<error>Could not determine database name! Please specify a database name in your config file.</error>'
459
            );
460
461
            return false;
462
        }
463
464
        if (isset($envOptions['table_prefix'])) {
465
            $output->writeln('<info>using table prefix</info> ' . $envOptions['table_prefix']);
466
        }
467
        if (isset($envOptions['table_suffix'])) {
468
            $output->writeln('<info>using table suffix</info> ' . $envOptions['table_suffix']);
469
        }
470
471
        return true;
472
    }
473
}
474