Completed
Pull Request — master (#1382)
by
unknown
13:18 queued 07:25
created

AbstractCommand   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 354
Duplicated Lines 9.04 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 53.72%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 40
c 2
b 0
f 0
lcom 1
cbo 11
dl 32
loc 354
ccs 65
cts 121
cp 0.5372
rs 8.2608

16 Methods

Rating   Name   Duplication   Size   Complexity  
A configure() 0 5 1
B bootstrap() 0 32 5
A setConfig() 0 6 1
A getConfig() 0 4 1
A setAdapter() 0 6 1
A getAdapter() 0 4 1
A setManager() 0 6 1
A getManager() 0 4 1
B locateConfigFile() 0 33 6
C loadConfig() 0 42 8
A loadManager() 0 11 2
A verifyMigrationDirectory() 16 16 3
A verifySeedDirectory() 16 16 3
A getMigrationTemplateFilename() 0 4 1
A getSeedTemplateFilename() 0 4 1
A loadEventSubscribers() 0 20 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like AbstractCommand often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractCommand, and based on these observations, apply Extract Interface, too.

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\Config;
32
use Phinx\Config\ConfigInterface;
33
use Phinx\Db\Adapter\AdapterInterface;
34
use Phinx\Migration\Manager;
35
use Phinx\Util\Util;
36
use Symfony\Component\Config\FileLocator;
37
use Symfony\Component\Console\Command\Command;
38
use Symfony\Component\Console\Input\InputInterface;
39
use Symfony\Component\Console\Input\InputOption;
40
use Symfony\Component\Console\Output\OutputInterface;
41
use Symfony\Component\EventDispatcher\EventDispatcher;
42
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
43
use Symfony\Component\Finder\Finder;
44
use Symfony\Component\Finder\SplFileInfo;
45
46
/**
47
 * Abstract command, contains bootstrapping info
48
 *
49
 * @author Rob Morgan <[email protected]>
50
 */
51
abstract class AbstractCommand extends Command
52
{
53
    /**
54
     * The location of the default migration template.
55
     */
56
    const DEFAULT_MIGRATION_TEMPLATE = '/../../Migration/Migration.template.php.dist';
57
58
    /**
59
     * The location of the default seed template.
60
     */
61
    const DEFAULT_SEED_TEMPLATE = '/../../Seed/Seed.template.php.dist';
62
63
    /**
64
     * @var \Phinx\Config\ConfigInterface
65
     */
66
    protected $config;
67
68
    /**
69
     * @var \Phinx\Db\Adapter\AdapterInterface
70
     */
71
    protected $adapter;
72
73
    /**
74
     * @var \Phinx\Migration\Manager
75
     */
76
    protected $manager;
77 54
78
    /**
79 54
     * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
80 54
     */
81 54
    protected $dispatcher;
82
83
    /**
84
     * {@inheritdoc}
85
     */
86
    protected function configure()
87
    {
88
        $this->addOption('--configuration', '-c', InputOption::VALUE_REQUIRED, 'The configuration file to load');
89
        $this->addOption('--parser', '-p', InputOption::VALUE_REQUIRED, 'Parser used to read the config file. Defaults to YAML');
90 32
    }
91
92 32
    /**
93
     * Bootstrap Phinx.
94
     *
95
     * @param \Symfony\Component\Console\Input\InputInterface $input
96 32
     * @param \Symfony\Component\Console\Output\OutputInterface $output
97
     * @return void
98
     */
99 32
    public function bootstrap(InputInterface $input, OutputInterface $output)
100
    {
101 32
        $this->dispatcher = new EventDispatcher();
102
103 32
        if (!$this->getConfig()) {
104 32
            $this->loadConfig($input, $output);
105 32
        }
106
107
        $this->loadManager($input, $output);
108 32
        $this->loadEventSubscribers();
109
110 6
        // report the paths
111
        $paths = $this->getConfig()->getMigrationPaths();
112 6
113 6
        $output->writeln('<info>using migration paths</info> ');
114 6
115 32
        foreach (Util::globAll($paths) as $path) {
116
            $output->writeln('<info> - ' . realpath($path) . '</info>');
117
        }
118 32
119
        try {
120
            $paths = $this->getConfig()->getSeedPaths();
121
122
            $output->writeln('<info>using seed paths</info> ');
123
124
            foreach (Util::globAll($paths) as $path) {
125
                $output->writeln('<info> - ' . realpath($path) . '</info>');
126 32
            }
127
        } catch (\UnexpectedValueException $e) {
128 32
            // do nothing as seeds are optional
129 32
        }
130
    }
131
132
    /**
133
     * Sets the config.
134
     *
135
     * @param  \Phinx\Config\ConfigInterface $config
136
     * @return \Phinx\Console\Command\AbstractCommand
137 32
     */
138
    public function setConfig(ConfigInterface $config)
139 32
    {
140
        $this->config = $config;
141
142
        return $this;
143
    }
144
145
    /**
146
     * Gets the config.
147
     *
148
     * @return \Phinx\Config\ConfigInterface
149
     */
150
    public function getConfig()
151
    {
152
        return $this->config;
153
    }
154
155
    /**
156
     * Sets the database adapter.
157
     *
158
     * @param \Phinx\Db\Adapter\AdapterInterface $adapter
159
     * @return \Phinx\Console\Command\AbstractCommand
160
     */
161
    public function setAdapter(AdapterInterface $adapter)
162
    {
163
        $this->adapter = $adapter;
164
165
        return $this;
166
    }
167
168
    /**
169
     * Gets the database adapter.
170 32
     *
171
     * @return \Phinx\Db\Adapter\AdapterInterface
172 32
     */
173 32
    public function getAdapter()
174
    {
175
        return $this->adapter;
176
    }
177
178
    /**
179
     * Sets the migration manager.
180
     *
181 32
     * @param \Phinx\Migration\Manager $manager
182
     * @return \Phinx\Console\Command\AbstractCommand
183 32
     */
184
    public function setManager(Manager $manager)
185
    {
186
        $this->manager = $manager;
187
188
        return $this;
189
    }
190
191
    /**
192 10
     * Gets the migration manager.
193
     *
194 10
     * @return \Phinx\Migration\Manager|null
195
     */
196 10
    public function getManager()
197
    {
198 10
        return $this->manager;
199 4
    }
200 4
201
    /**
202 10
     * Returns config file path
203
     *
204
     * @param \Symfony\Component\Console\Input\InputInterface $input
205
     * @return string
206 10
     */
207
    protected function locateConfigFile(InputInterface $input)
208 10
    {
209
        $configFile = $input->getOption('configuration');
210 10
211
        $useDefault = false;
212 6
213
        if ($configFile === null || $configFile === false) {
214
            $useDefault = true;
215 4
        }
216 4
217
        $cwd = getcwd();
218 4
219 3
        // locate the phinx config file (default: phinx.yml)
220 3
        // TODO - In future walk the tree in reverse (max 10 levels)
221
        $locator = new FileLocator([
222 3
            $cwd . DIRECTORY_SEPARATOR
223 1
        ]);
224
225
        if (!$useDefault) {
226
            // Locate() throws an exception if the file does not exist
227
            return $locator->locate($configFile, $cwd, $first = true);
228
        }
229
230
        $possibleConfigFiles = ['phinx.php', 'phinx.json', 'phinx.yml'];
231
        foreach ($possibleConfigFiles as $configFile) {
232
            try {
233
                return $locator->locate($configFile, $cwd, $first = true);
234
            } catch (\InvalidArgumentException $exception) {
235
                $lastException = $exception;
236
            }
237
        }
238
        throw $lastException;
239
    }
240
241
    /**
242
     * Parse the config file and load it into the config object
243
     *
244
     * @param \Symfony\Component\Console\Input\InputInterface $input
245
     * @param \Symfony\Component\Console\Output\OutputInterface $output
246
     * @throws \InvalidArgumentException
247
     * @return void
248
     */
249
    protected function loadConfig(InputInterface $input, OutputInterface $output)
250
    {
251
        $configFilePath = $this->locateConfigFile($input);
252
        $output->writeln('<info>using config file</info> .' . str_replace(getcwd(), '', realpath($configFilePath)));
253
254
        $parser = $input->getOption('parser');
255
256
        // If no parser is specified try to determine the correct one from the file extension.  Defaults to YAML
257
        if ($parser === null) {
258
            $extension = pathinfo($configFilePath, PATHINFO_EXTENSION);
259
260
            switch (strtolower($extension)) {
261
                case 'json':
262
                    $parser = 'json';
263
                    break;
264
                case 'php':
265
                    $parser = 'php';
266
                    break;
267
                case 'yml':
268
                default:
269
                    $parser = 'yaml';
270
            }
271
        }
272
273
        switch (strtolower($parser)) {
274
            case 'json':
275
                $config = Config::fromJson($configFilePath);
0 ignored issues
show
Bug introduced by
It seems like $configFilePath defined by $this->locateConfigFile($input) on line 251 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...
276
                break;
277
            case 'php':
278
                $config = Config::fromPhp($configFilePath);
0 ignored issues
show
Bug introduced by
It seems like $configFilePath defined by $this->locateConfigFile($input) on line 251 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...
279
                break;
280
            case 'yaml':
281
                $config = Config::fromYaml($configFilePath);
0 ignored issues
show
Bug introduced by
It seems like $configFilePath defined by $this->locateConfigFile($input) on line 251 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...
282
                break;
283 32
            default:
284
                throw new \InvalidArgumentException(sprintf('\'%s\' is not a valid parser.', $parser));
285 32
        }
286
287
        $output->writeln('<info>using config parser</info> ' . $parser);
288
289 32
        $this->setConfig($config);
290 32
    }
291 32
292
    /**
293 32
     * Load the migrations manager and inject the config
294
     *
295
     * @param \Symfony\Component\Console\Input\InputInterface $input
296
     * @param \Symfony\Component\Console\Output\OutputInterface $output
297
     */
298
    protected function loadManager(InputInterface $input, OutputInterface $output)
299
    {
300
        if ($this->getManager() === null) {
301
            $manager = new Manager($this->getConfig(), $input, $output);
302 13
            $this->setManager($manager);
303
        } else {
304 13
            $manager = $this->getManager();
305
            $manager->setInput($input);
306
            $manager->setOutput($output);
307
        }
308
    }
309
310
    /**
311 13
     * Verify that the migration directory exists and is writable.
312
     *
313
     * @param string $path
314
     * @throws \InvalidArgumentException
315
     * @return void
316
     */
317 13 View Code Duplication
    protected function verifyMigrationDirectory($path)
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...
318
    {
319
        if (!is_dir($path)) {
320
            throw new \InvalidArgumentException(sprintf(
321
                'Migration directory "%s" does not exist',
322
                $path
323
            ));
324
        }
325
326 2
        if (!is_writable($path)) {
327
            throw new \InvalidArgumentException(sprintf(
328 2
                'Migration directory "%s" is not writable',
329
                $path
330
            ));
331
        }
332
    }
333
334
    /**
335 2
     * Verify that the seed directory exists and is writable.
336
     *
337
     * @param string $path
338
     * @throws \InvalidArgumentException
339
     * @return void
340
     */
341 2 View Code Duplication
    protected function verifySeedDirectory($path)
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...
342
    {
343
        if (!is_dir($path)) {
344
            throw new \InvalidArgumentException(sprintf(
345
                'Seed directory "%s" does not exist',
346
                $path
347
            ));
348 2
        }
349
350 2
        if (!is_writable($path)) {
351
            throw new \InvalidArgumentException(sprintf(
352
                'Seed directory "%s" is not writable',
353
                $path
354
            ));
355
        }
356
    }
357
358 1
    /**
359
     * Returns the migration template filename.
360 1
     *
361
     * @return string
362
     */
363
    protected function getMigrationTemplateFilename()
364
    {
365
        return __DIR__ . self::DEFAULT_MIGRATION_TEMPLATE;
366
    }
367
368
    /**
369
     * Returns the seed template filename.
370
     *
371
     * @return string
372
     */
373
    protected function getSeedTemplateFilename()
374
    {
375
        return __DIR__ . self::DEFAULT_SEED_TEMPLATE;
376
    }
377
378
    /**
379
     * Load all event subscribers
380
     *
381
     * @throws \ReflectionException
382
     * @return void
383
     */
384
    protected function loadEventSubscribers()
385
    {
386
        $paths = $this->getConfig()->getSubscriberPaths();
387
        if (!$paths) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $paths of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
388
            return;
389
        }
390
        $finder = new Finder();
391
        $listeners = $finder->in($paths)->name('*.php')->files();
392
393
        /** @var SplFileInfo $listener */
394
        foreach ($listeners as $listener) {
395
            require_once $listener->getRealPath();
396
397
            $refClass = new \ReflectionClass($listener->getBasename('.php'));
398
            if (!$refClass->implementsInterface('\Symfony\Component\EventDispatcher\EventSubscriberInterface')) {
399
                continue;
400
            }
401
            $this->dispatcher->addSubscriber($refClass->newInstance());
402
        }
403
    }
404
}
405