Completed
Push — master ( 94018a...6a6ebc )
by AD
13s
created

Create::execute()   F

Complexity

Conditions 33
Paths 2130

Size

Total Lines 163
Code Lines 95

Duplication

Lines 8
Ratio 4.91 %

Code Coverage

Tests 76
CRAP Score 51.0358

Importance

Changes 0
Metric Value
dl 8
loc 163
ccs 76
cts 102
cp 0.7451
rs 2
c 0
b 0
f 0
cc 33
eloc 95
nc 2130
nop 2
crap 51.0358

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\Migration\CreationInterface;
33
use Phinx\Util\Util;
34
use Symfony\Component\Console\Input\InputArgument;
35
use Symfony\Component\Console\Input\InputInterface;
36
use Symfony\Component\Console\Input\InputOption;
37
use Symfony\Component\Console\Output\OutputInterface;
38
use Symfony\Component\Console\Question\ChoiceQuestion;
39
use Symfony\Component\Console\Question\ConfirmationQuestion;
40
41
class Create extends AbstractCommand
42
{
43
    /**
44
     * The name of the interface that any external template creation class is required to implement.
45
     */
46
    const CREATION_INTERFACE = 'Phinx\Migration\CreationInterface';
47
48
    /**
49
     * {@inheritdoc}
50
     */
51 35
    protected function configure()
52
    {
53 35
        parent::configure();
54
55 35
        $this->setName('create')
56 35
            ->setDescription('Create a new migration')
57 35
            ->addArgument('name', InputArgument::REQUIRED, 'What is the name of the migration (in CamelCase)?')
58 35
            ->setHelp(sprintf(
59 35
                '%sCreates a new database migration%s',
60 35
                PHP_EOL,
61
                PHP_EOL
62 35
            ));
63
64
        // An alternative template.
65 35
        $this->addOption('template', 't', InputOption::VALUE_REQUIRED, 'Use an alternative template');
66
67
        // A classname to be used to gain access to the template content as well as the ability to
68
        // have a callback once the migration file has been created.
69 35
        $this->addOption('class', 'l', InputOption::VALUE_REQUIRED, 'Use a class implementing "' . self::CREATION_INTERFACE . '" to generate the template');
70
71
        // Allow the migration path to be chosen non-interactively.
72 35
        $this->addOption('path', null, InputOption::VALUE_REQUIRED, 'Specify the path in which to create this migration');
73 35
    }
74
75
    /**
76
     * Get the confirmation question asking if the user wants to create the
77
     * migrations directory.
78
     *
79
     * @return ConfirmationQuestion
80
     */
81
    protected function getCreateMigrationDirectoryQuestion()
82
    {
83
        return new ConfirmationQuestion('Create migrations directory? [y]/n ', true);
84
    }
85
86
    /**
87
     * Get the question that allows the user to select which migration path to use.
88
     *
89
     * @param string[] $paths
90
     * @return ChoiceQuestion
91
     */
92
    protected function getSelectMigrationPathQuestion(array $paths)
93
    {
94
        return new ChoiceQuestion('Which migrations path would you like to use?', $paths, 0);
95
    }
96
97
    /**
98
     * Returns the migration path to create the migration in.
99
     *
100
     * @param InputInterface $input
101
     * @param OutputInterface $output
102
     * @return mixed
103
     * @throws \Exception
104
     */
105 13 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...
106
    {
107
        // First, try the non-interactive option:
108 13
        $path = $input->getOption('path');
109
110 13
        if (!empty($path)) {
111
            return $path;
112
        }
113
114 13
        $paths = $this->getConfig()->getMigrationPaths();
115
116
        // No paths? That's a problem.
117 13
        if (empty($paths)) {
118
            throw new \Exception('No migration paths set in your Phinx configuration file.');
119
        }
120
121 13
        $paths = Util::globAll($paths);
122
123 13
        if (empty($paths)) {
124
            throw new \Exception(
125
                'You probably used curly braces to define migration path in your Phinx configuration file, ' .
126
                'but no directories have been matched using this pattern. ' .
127
                'You need to create a migration directory manually.'
128
            );
129
        }
130
131
        // Only one path set, so select that:
132 13
        if (1 === count($paths)) {
133 13
            return array_shift($paths);
134
        }
135
136
        // Ask the user which of their defined paths they'd like to use:
137
        $helper = $this->getHelper('question');
138
        $question = $this->getSelectMigrationPathQuestion($paths);
139
140
        return $helper->ask($input, $output, $question);
141
    }
142
143
    /**
144
     * Create the new migration.
145
     *
146
     * @param InputInterface $input
147
     * @param OutputInterface $output
148
     * @throws \RuntimeException
149
     * @throws \InvalidArgumentException
150
     * @return void
151
     */
152 13
    protected function execute(InputInterface $input, OutputInterface $output)
153
    {
154 13
        $this->bootstrap($input, $output);
155
156
        // get the migration path from the config
157 13
        $path = $this->getMigrationPath($input, $output);
158
159 13 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...
160
            $helper   = $this->getHelper('question');
161
            $question = $this->getCreateMigrationDirectoryQuestion();
162
163
            if ($helper->ask($input, $output, $question)) {
164
                mkdir($path, 0755, true);
165
            }
166
        }
167
168 13
        $this->verifyMigrationDirectory($path);
169
170 13
        $config = $this->getConfig();
171 13
        $namespace = $config instanceof NamespaceAwareInterface ? $config->getMigrationNamespaceByPath($path) : null;
172
173 13
        $path = realpath($path);
174 13
        $className = $input->getArgument('name');
175
176 13
        if (!Util::isValidPhinxClassName($className)) {
177
            throw new \InvalidArgumentException(sprintf(
178
                'The migration class name "%s" is invalid. Please use CamelCase format.',
179
                $className
180
            ));
181
        }
182
183 13
        if (!Util::isUniqueMigrationClassName($className, $path)) {
184 2
            throw new \InvalidArgumentException(sprintf(
185 2
                'The migration class name "%s%s" already exists',
186 2
                $namespace ? ($namespace . '\\') : '',
187
                $className
188 2
            ));
189
        }
190
191
        // Compute the file path
192 13
        $fileName = Util::mapClassNameToFileName($className);
193 13
        $filePath = $path . DIRECTORY_SEPARATOR . $fileName;
194
195 13
        if (is_file($filePath)) {
196
            throw new \InvalidArgumentException(sprintf(
197
                'The file "%s" already exists',
198
                $filePath
199
            ));
200
        }
201
202
        // Get the alternative template and static class options from the config, but only allow one of them.
203 13
        $defaultAltTemplate = $this->getConfig()->getTemplateFile();
204 13
        $defaultCreationClassName = $this->getConfig()->getTemplateClass();
205 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...
206 1
            throw new \InvalidArgumentException('Cannot define template:class and template:file at the same time');
207
        }
208
209
        // Get the alternative template and static class options from the command line, but only allow one of them.
210 12
        $altTemplate = $input->getOption('template');
211 12
        $creationClassName = $input->getOption('class');
212 12
        if ($altTemplate && $creationClassName) {
213 1
            throw new \InvalidArgumentException('Cannot use --template and --class at the same time');
214
        }
215
216
        // If no commandline options then use the defaults.
217 11
        if (!$altTemplate && !$creationClassName) {
218 5
            $altTemplate = $defaultAltTemplate;
219 5
            $creationClassName = $defaultCreationClassName;
220 5
        }
221
222
        // Verify the alternative template file's existence.
223 11
        if ($altTemplate && !is_file($altTemplate)) {
224
            throw new \InvalidArgumentException(sprintf(
225
                'The alternative template file "%s" does not exist',
226
                $altTemplate
227
            ));
228
        }
229
230
        // Verify that the template creation class (or the aliased class) exists and that it implements the required interface.
231 11
        $aliasedClassName  = null;
232 11
        if ($creationClassName) {
233
            // Supplied class does not exist, is it aliased?
234 9
            if (!class_exists($creationClassName)) {
235 3
                $aliasedClassName = $this->getConfig()->getAlias($creationClassName);
236 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...
237
                    throw new \InvalidArgumentException(sprintf(
238
                        'The class "%s" via the alias "%s" does not exist',
239
                        $aliasedClassName,
240
                        $creationClassName
241
                    ));
242 3
                } 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...
243
                    throw new \InvalidArgumentException(sprintf(
244
                        'The class "%s" does not exist',
245
                        $creationClassName
246
                    ));
247
                }
248 3
            }
249
250
            // Does the class implement the required interface?
251 9
            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...
252 2
                throw new \InvalidArgumentException(sprintf(
253 2
                    'The class "%s" does not implement the required interface "%s"',
254 2
                    $creationClassName,
255
                    self::CREATION_INTERFACE
256 2
                ));
257 7
            } 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...
258 1
                throw new \InvalidArgumentException(sprintf(
259 1
                    'The class "%s" via the alias "%s" does not implement the required interface "%s"',
260 1
                    $aliasedClassName,
261 1
                    $creationClassName,
262
                    self::CREATION_INTERFACE
263 1
                ));
264
            }
265 6
        }
266
267
        // Use the aliased class.
268 8
        $creationClassName = $aliasedClassName ?: $creationClassName;
269
270
        // Determine the appropriate mechanism to get the template
271 8
        if ($creationClassName) {
272
            // Get the template from the creation class
273 6
            $creationClass = new $creationClassName($input, $output);
274 6
            $contents = $creationClass->getMigrationTemplate();
275 6
        } else {
276
            // Load the alternative template if it is defined.
277 2
            $contents = file_get_contents($altTemplate ?: $this->getMigrationTemplateFilename());
278
        }
279
280
        // inject the class names appropriate to this migration
281
        $classes = [
282 8
            '$namespaceDefinition' => $namespace !== null ? ('namespace ' . $namespace . ';') : '',
283 8
            '$namespace'           => $namespace,
284 8
            '$useClassName'        => $this->getConfig()->getMigrationBaseClassName(false),
285 8
            '$className'           => $className,
286 8
            '$version'             => Util::getVersionFromFileName($fileName),
287 8
            '$baseClassName'       => $this->getConfig()->getMigrationBaseClassName(true),
288 8
        ];
289 8
        $contents = strtr($contents, $classes);
290
291 8
        if (file_put_contents($filePath, $contents) === false) {
292
            throw new \RuntimeException(sprintf(
293
                'The file "%s" could not be written to',
294
                $path
295
            ));
296
        }
297
298
        // Do we need to do the post creation call to the creation class?
299 8
        if (isset($creationClass)) {
300 6
            $creationClass->postMigrationCreation($filePath, $className, $this->getConfig()->getMigrationBaseClassName());
301 6
        }
302
303 8
        $output->writeln('<info>using migration base class</info> ' . $classes['$useClassName']);
304
305 8
        if (!empty($altTemplate)) {
306
            $output->writeln('<info>using alternative template</info> ' . $altTemplate);
307 8
        } elseif (!empty($creationClassName)) {
308 6
            $output->writeln('<info>using template creation class</info> ' . $creationClassName);
309 6
        } else {
310 2
            $output->writeln('<info>using default template</info>');
311
        }
312
313 8
        $output->writeln('<info>created</info> ' . str_replace(getcwd() . DIRECTORY_SEPARATOR, '', $filePath));
314 8
    }
315
}
316