Passed
Push — master ( 5c8446...b5213f )
by mark
02:55 queued 13s
created

AbstractCommand::bootstrap()   B

Complexity

Conditions 7
Paths 128

Size

Total Lines 38
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 7.9936

Importance

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

284
            switch (strtolower(/** @scrutinizer ignore-type */ $extension)) {
Loading history...
285 32
                case self::FORMAT_JSON:
286
                    $parser = self::FORMAT_JSON;
287
                    break;
288
                case self::FORMAT_YML_ALIAS:
289 32
                case self::FORMAT_YML:
290 32
                    $parser = self::FORMAT_YML;
291 32
                    break;
292
                case self::FORMAT_PHP:
293 32
                default:
294
                    $parser = self::FORMAT_DEFAULT;
295
                    break;
296
            }
297
        }
298
299
        switch (strtolower($parser)) {
300
            case self::FORMAT_JSON:
301
                $config = Config::fromJson($configFilePath);
302 13
                break;
303
            case self::FORMAT_PHP:
304 13
                $config = Config::fromPhp($configFilePath);
305
                break;
306
            case self::FORMAT_YML_ALIAS:
307
            case self::FORMAT_YML:
308
                $config = Config::fromYaml($configFilePath);
309
                break;
310
            default:
311 13
                throw new InvalidArgumentException(sprintf('\'%s\' is not a valid parser.', $parser));
312
        }
313
314
        $output->writeln('<info>using config parser</info> ' . $parser, $this->verbosityLevel);
315
316
        $this->setConfig($config);
317 13
    }
318
319
    /**
320
     * Load the migrations manager and inject the config
321
     *
322
     * @param \Symfony\Component\Console\Input\InputInterface $input Input
323
     * @param \Symfony\Component\Console\Output\OutputInterface $output Output
324
     * @return void
325
     */
326 2
    protected function loadManager(InputInterface $input, OutputInterface $output)
327
    {
328 2
        if ($this->getManager() === null) {
329
            $manager = new Manager($this->getConfig(), $input, $output);
330
            $manager->setVerbosityLevel($this->verbosityLevel);
331
            $container = $this->getConfig()->getContainer();
332
            if ($container !== null) {
333
                $manager->setContainer($container);
334
            }
335 2
            $this->setManager($manager);
336
        } else {
337
            $manager = $this->getManager();
338
            $manager->setInput($input);
339
            $manager->setOutput($output);
340
        }
341 2
    }
342
343
    /**
344
     * Verify that the migration directory exists and is writable.
345
     *
346
     * @param string $path Path
347
     * @throws \InvalidArgumentException
348 2
     * @return void
349
     */
350 2
    protected function verifyMigrationDirectory($path)
351
    {
352
        if (!is_dir($path)) {
353
            throw new InvalidArgumentException(sprintf(
354
                'Migration directory "%s" does not exist',
355
                $path
356
            ));
357
        }
358 1
359
        if (!is_writable($path)) {
360 1
            throw new InvalidArgumentException(sprintf(
361
                'Migration directory "%s" is not writable',
362
                $path
363
            ));
364
        }
365
    }
366
367
    /**
368
     * Verify that the seed directory exists and is writable.
369
     *
370
     * @param string $path Path
371
     * @throws \InvalidArgumentException
372
     * @return void
373
     */
374
    protected function verifySeedDirectory($path)
375
    {
376
        if (!is_dir($path)) {
377
            throw new InvalidArgumentException(sprintf(
378
                'Seed directory "%s" does not exist',
379
                $path
380
            ));
381
        }
382
383
        if (!is_writable($path)) {
384
            throw new InvalidArgumentException(sprintf(
385
                'Seed directory "%s" is not writable',
386
                $path
387
            ));
388
        }
389
    }
390
391
    /**
392
     * Returns the migration template filename.
393
     *
394
     * @return string
395
     */
396
    protected function getMigrationTemplateFilename()
397
    {
398
        return __DIR__ . self::DEFAULT_MIGRATION_TEMPLATE;
399
    }
400
401
    /**
402
     * Returns the seed template filename.
403
     *
404
     * @return string
405
     */
406
    protected function getSeedTemplateFilename()
407
    {
408
        return __DIR__ . self::DEFAULT_SEED_TEMPLATE;
409
    }
410
}
411