Completed
Pull Request — master (#1390)
by
unknown
02:31
created

Create::execute()   F

Complexity

Conditions 33
Paths 2130

Size

Total Lines 163

Duplication

Lines 8
Ratio 4.91 %

Code Coverage

Tests 75
CRAP Score 51.5717

Importance

Changes 0
Metric Value
dl 8
loc 163
ccs 75
cts 101
cp 0.7426
rs 0
c 0
b 0
f 0
cc 33
nc 2130
nop 2
crap 51.5717

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Phinx
4
 *
5
 * (The MIT license)
6
 * Copyright (c) 2015 Rob Morgan
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated * documentation files (the "Software"), to
10
 * deal in the Software without restriction, including without limitation the
11
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12
 * sell copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24
 * IN THE SOFTWARE.
25
 *
26
 * @package    Phinx
27
 * @subpackage Phinx\Console
28
 */
29
namespace Phinx\Console\Command;
30
31
use Phinx\Config\NamespaceAwareInterface;
32
use Phinx\Util\Util;
33
use Symfony\Component\Console\Input\InputArgument;
34
use Symfony\Component\Console\Input\InputInterface;
35
use Symfony\Component\Console\Input\InputOption;
36
use Symfony\Component\Console\Output\OutputInterface;
37
use Symfony\Component\Console\Question\ChoiceQuestion;
38
use Symfony\Component\Console\Question\ConfirmationQuestion;
39
40
class Create extends AbstractCommand
41
{
42
    /**
43
     * The name of the interface that any external template creation class is required to implement.
44
     */
45
    const CREATION_INTERFACE = 'Phinx\Migration\CreationInterface';
46
47
    /**
48
     * {@inheritdoc}
49
     */
50
    protected function configure()
51 35
    {
52
        parent::configure();
53 35
54
        $this->setName($this->getName() ?: 'create')
55 35
            ->setDescription('Create a new migration')
56 35
            ->addArgument('name', InputArgument::REQUIRED, 'What is the name of the migration (in CamelCase)?')
57 35
            ->setHelp(sprintf(
58 35
                '%sCreates a new database migration%s',
59 35
                PHP_EOL,
60 35
                PHP_EOL
61
            ));
62 35
63
        // An alternative template.
64
        $this->addOption('template', 't', InputOption::VALUE_REQUIRED, 'Use an alternative template');
65 35
66
        // A classname to be used to gain access to the template content as well as the ability to
67
        // have a callback once the migration file has been created.
68
        $this->addOption('class', 'l', InputOption::VALUE_REQUIRED, 'Use a class implementing "' . self::CREATION_INTERFACE . '" to generate the template');
69 35
70
        // Allow the migration path to be chosen non-interactively.
71
        $this->addOption('path', null, InputOption::VALUE_REQUIRED, 'Specify the path in which to create this migration');
72 35
    }
73 35
74
    /**
75
     * Get the confirmation question asking if the user wants to create the
76
     * migrations directory.
77
     *
78
     * @return \Symfony\Component\Console\Question\ConfirmationQuestion
79
     */
80
    protected function getCreateMigrationDirectoryQuestion()
81
    {
82
        return new ConfirmationQuestion('Create migrations directory? [y]/n ', true);
83
    }
84
85
    /**
86
     * Get the question that allows the user to select which migration path to use.
87
     *
88
     * @param string[] $paths
89
     * @return \Symfony\Component\Console\Question\ChoiceQuestion
90
     */
91
    protected function getSelectMigrationPathQuestion(array $paths)
92
    {
93
        return new ChoiceQuestion('Which migrations path would you like to use?', $paths, 0);
94
    }
95
96
    /**
97
     * Returns the migration path to create the migration in.
98
     *
99
     * @param \Symfony\Component\Console\Input\InputInterface $input
100
     * @param \Symfony\Component\Console\Output\OutputInterface $output
101
     * @return mixed
102
     * @throws \Exception
103
     */
104 View Code Duplication
    protected function getMigrationPath(InputInterface $input, OutputInterface $output)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
105 13
    {
106
        // First, try the non-interactive option:
107
        $path = $input->getOption('path');
108 13
109
        if (!empty($path)) {
110 13
            return $path;
111
        }
112
113
        $paths = $this->getConfig()->getMigrationPaths();
114 13
115
        // No paths? That's a problem.
116
        if (empty($paths)) {
117 13
            throw new \Exception('No migration paths set in your Phinx configuration file.');
118
        }
119
120
        $paths = Util::globAll($paths);
121 13
122
        if (empty($paths)) {
123 13
            throw new \Exception(
124
                'You probably used curly braces to define migration path in your Phinx configuration file, ' .
125
                'but no directories have been matched using this pattern. ' .
126
                'You need to create a migration directory manually.'
127
            );
128
        }
129
130
        // Only one path set, so select that:
131
        if (1 === count($paths)) {
132 13
            return array_shift($paths);
133 13
        }
134
135
        // Ask the user which of their defined paths they'd like to use:
136
        $helper = $this->getHelper('question');
137
        $question = $this->getSelectMigrationPathQuestion($paths);
138
139
        return $helper->ask($input, $output, $question);
140
    }
141
142
    /**
143
     * Create the new migration.
144
     *
145
     * @param \Symfony\Component\Console\Input\InputInterface $input
146
     * @param \Symfony\Component\Console\Output\OutputInterface $output
147
     * @throws \RuntimeException
148
     * @throws \InvalidArgumentException
149
     * @return void
150
     */
151
    protected function execute(InputInterface $input, OutputInterface $output)
152 13
    {
153
        $this->bootstrap($input, $output);
154 13
155
        // get the migration path from the config
156
        $path = $this->getMigrationPath($input, $output);
157 13
158 View Code Duplication
        if (!file_exists($path)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
159 13
            $helper = $this->getHelper('question');
160
            $question = $this->getCreateMigrationDirectoryQuestion();
161
162
            if ($helper->ask($input, $output, $question)) {
163
                mkdir($path, 0755, true);
164
            }
165
        }
166
167
        $this->verifyMigrationDirectory($path);
168 13
169
        $config = $this->getConfig();
170 13
        $namespace = $config instanceof NamespaceAwareInterface ? $config->getMigrationNamespaceByPath($path) : null;
171 13
172
        $path = realpath($path);
173 13
        $className = $input->getArgument('name');
174 13
175
        if (!Util::isValidPhinxClassName($className)) {
176 13
            throw new \InvalidArgumentException(sprintf(
177
                'The migration class name "%s" is invalid. Please use CamelCase format.',
178
                $className
179
            ));
180
        }
181
182
        if (!Util::isUniqueMigrationClassName($className, $path)) {
183 13
            throw new \InvalidArgumentException(sprintf(
184 2
                'The migration class name "%s%s" already exists',
185 2
                $namespace ? ($namespace . '\\') : '',
186 2
                $className
187
            ));
188 2
        }
189
190
        // Compute the file path
191
        $fileName = Util::mapClassNameToFileName($className);
192 13
        $filePath = $path . DIRECTORY_SEPARATOR . $fileName;
193 13
194
        if (is_file($filePath)) {
195 13
            throw new \InvalidArgumentException(sprintf(
196
                'The file "%s" already exists',
197
                $filePath
198
            ));
199
        }
200
201
        // Get the alternative template and static class options from the config, but only allow one of them.
202
        $defaultAltTemplate = $this->getConfig()->getTemplateFile();
203 13
        $defaultCreationClassName = $this->getConfig()->getTemplateClass();
204 13
        if ($defaultAltTemplate && $defaultCreationClassName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $defaultAltTemplate of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
Bug Best Practice introduced by
The expression $defaultCreationClassName of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
205 13
            throw new \InvalidArgumentException('Cannot define template:class and template:file at the same time');
206 1
        }
207
208
        // Get the alternative template and static class options from the command line, but only allow one of them.
209
        $altTemplate = $input->getOption('template');
210 12
        $creationClassName = $input->getOption('class');
211 12
        if ($altTemplate && $creationClassName) {
212 12
            throw new \InvalidArgumentException('Cannot use --template and --class at the same time');
213 1
        }
214
215
        // If no commandline options then use the defaults.
216
        if (!$altTemplate && !$creationClassName) {
217 11
            $altTemplate = $defaultAltTemplate;
218 5
            $creationClassName = $defaultCreationClassName;
219 5
        }
220 5
221
        // Verify the alternative template file's existence.
222
        if ($altTemplate && !is_file($altTemplate)) {
223 11
            throw new \InvalidArgumentException(sprintf(
224
                'The alternative template file "%s" does not exist',
225
                $altTemplate
226
            ));
227
        }
228
229
        // Verify that the template creation class (or the aliased class) exists and that it implements the required interface.
230
        $aliasedClassName = null;
231 11
        if ($creationClassName) {
232 11
            // Supplied class does not exist, is it aliased?
233
            if (!class_exists($creationClassName)) {
234 9
                $aliasedClassName = $this->getConfig()->getAlias($creationClassName);
235 3
                if ($aliasedClassName && !class_exists($aliasedClassName)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $aliasedClassName of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
236 3
                    throw new \InvalidArgumentException(sprintf(
237
                        'The class "%s" via the alias "%s" does not exist',
238
                        $aliasedClassName,
239
                        $creationClassName
240
                    ));
241
                } elseif (!$aliasedClassName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $aliasedClassName of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
242 3
                    throw new \InvalidArgumentException(sprintf(
243
                        'The class "%s" does not exist',
244
                        $creationClassName
245
                    ));
246
                }
247
            }
248 3
249
            // Does the class implement the required interface?
250
            if (!$aliasedClassName && !is_subclass_of($creationClassName, self::CREATION_INTERFACE)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $aliasedClassName of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if self::CREATION_INTERFACE can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
251 9
                throw new \InvalidArgumentException(sprintf(
252 2
                    'The class "%s" does not implement the required interface "%s"',
253 2
                    $creationClassName,
254 2
                    self::CREATION_INTERFACE
255
                ));
256 2
            } elseif ($aliasedClassName && !is_subclass_of($aliasedClassName, self::CREATION_INTERFACE)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $aliasedClassName of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if self::CREATION_INTERFACE can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
257 7
                throw new \InvalidArgumentException(sprintf(
258 1
                    'The class "%s" via the alias "%s" does not implement the required interface "%s"',
259 1
                    $aliasedClassName,
260 1
                    $creationClassName,
261 1
                    self::CREATION_INTERFACE
262
                ));
263 1
            }
264
        }
265 6
266
        // Use the aliased class.
267
        $creationClassName = $aliasedClassName ?: $creationClassName;
268 8
269
        // Determine the appropriate mechanism to get the template
270
        if ($creationClassName) {
271 8
            // Get the template from the creation class
272
            $creationClass = new $creationClassName($input, $output);
273 6
            $contents = $creationClass->getMigrationTemplate();
274 6
        } else {
275 6
            // Load the alternative template if it is defined.
276
            $contents = file_get_contents($altTemplate ?: $this->getMigrationTemplateFilename());
277 2
        }
278
279
        // inject the class names appropriate to this migration
280
        $classes = [
281
            '$namespaceDefinition' => $namespace !== null ? ('namespace ' . $namespace . ';') : '',
282 8
            '$namespace' => $namespace,
283 8
            '$useClassName' => $this->getConfig()->getMigrationBaseClassName(false),
284 8
            '$className' => $className,
285 8
            '$version' => Util::getVersionFromFileName($fileName),
286 8
            '$baseClassName' => $this->getConfig()->getMigrationBaseClassName(true),
287 8
        ];
288 8
        $contents = strtr($contents, $classes);
289 8
290
        if (file_put_contents($filePath, $contents) === false) {
291 8
            throw new \RuntimeException(sprintf(
292
                'The file "%s" could not be written to',
293
                $path
294
            ));
295
        }
296
297
        // Do we need to do the post creation call to the creation class?
298
        if (isset($creationClass)) {
299 8
            $creationClass->postMigrationCreation($filePath, $className, $this->getConfig()->getMigrationBaseClassName());
300 6
        }
301 6
302
        $output->writeln('<info>using migration base class</info> ' . $classes['$useClassName']);
303 8
304
        if (!empty($altTemplate)) {
305 8
            $output->writeln('<info>using alternative template</info> ' . $altTemplate);
306
        } elseif (!empty($creationClassName)) {
307 8
            $output->writeln('<info>using template creation class</info> ' . $creationClassName);
308 6
        } else {
309 6
            $output->writeln('<info>using default template</info>');
310 2
        }
311
312
        $output->writeln('<info>created</info> ' . str_replace(getcwd() . DIRECTORY_SEPARATOR, '', $filePath));
313 8
    }
314
}
315