Passed
Pull Request — master (#1928)
by Corey
03:52 queued 01:10
created

AbstractCommand::locateConfigFile()   A

Complexity

Conditions 6
Paths 8

Size

Total Lines 32
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 30.1171

Importance

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

291
        switch (strtolower(/** @scrutinizer ignore-type */ $parser)) {
Loading history...
292
            case self::FORMAT_JSON:
293 32
                $config = Config::fromJson($configFilePath);
294
                break;
295
            case self::FORMAT_PHP:
296
                $config = Config::fromPhp($configFilePath);
297
                break;
298
            case self::FORMAT_YML_ALIAS:
299
            case self::FORMAT_YML:
300
                $config = Config::fromYaml($configFilePath);
301
                break;
302 13
            default:
303
                throw new InvalidArgumentException(sprintf('\'%s\' is not a valid parser.', $parser));
0 ignored issues
show
Bug introduced by
It seems like $parser can also be of type string[]; however, parameter $args of sprintf() 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

303
                throw new InvalidArgumentException(sprintf('\'%s\' is not a valid parser.', /** @scrutinizer ignore-type */ $parser));
Loading history...
304 13
        }
305
306
        $output->writeln('<info>using config parser</info> ' . $parser);
0 ignored issues
show
Bug introduced by
Are you sure $parser of type boolean|string|string[] can be used in concatenation? ( Ignorable by Annotation )

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

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