Passed
Push — develop ( 6781d9...30a781 )
by Paul
02:04
created

GenerateCommand::validatePathsExist()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 3
nop 1
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace PhpUnitGen\Console;
4
5
use PhpUnitGen\Configuration\AbstractConsoleConfigFactory;
6
use PhpUnitGen\Configuration\ConfigurationInterface\ConsoleConfigInterface;
7
use PhpUnitGen\Configuration\DefaultConsoleConfigFactory;
8
use PhpUnitGen\Configuration\JsonConsoleConfigFactory;
9
use PhpUnitGen\Configuration\PhpConsoleConfigFactory;
10
use PhpUnitGen\Configuration\YamlConsoleConfigFactory;
11
use PhpUnitGen\Container\ContainerInterface\ConsoleContainerFactoryInterface;
12
use PhpUnitGen\Exception\Exception;
13
use PhpUnitGen\Exception\InvalidConfigException;
14
use PhpUnitGen\Executor\ExecutorInterface\ConsoleExecutorInterface;
15
use Respect\Validation\Validator;
16
use Symfony\Component\Console\Command\Command;
17
use Symfony\Component\Console\Input\InputArgument;
18
use Symfony\Component\Console\Input\InputInterface;
19
use Symfony\Component\Console\Input\InputOption;
20
use Symfony\Component\Console\Output\OutputInterface;
21
use Symfony\Component\Console\Style\SymfonyStyle;
22
use Symfony\Component\Stopwatch\Stopwatch;
23
24
/**
25
 * Class GenerateCommand.
26
 *
27
 * @author     Paul Thébaud <[email protected]>.
28
 * @copyright  2017-2018 Paul Thébaud <[email protected]>.
29
 * @license    https://opensource.org/licenses/MIT The MIT license.
30
 * @link       https://github.com/paul-thebaud/phpunit-generator
31
 * @since      Class available since Release 2.0.0.
32
 */
33
class GenerateCommand extends Command
34
{
35
    /**
36
     * @var string[] CONSOLE_CONFIG_FACTORIES Mapping array between file extension and configuration factories.
37
     */
38
    protected const CONSOLE_CONFIG_FACTORIES = [
39
        'yml'  => YamlConsoleConfigFactory::class,
40
        'json' => JsonConsoleConfigFactory::class,
41
        'php'  => PhpConsoleConfigFactory::class
42
    ];
43
44
    /**
45
     * @var ConsoleContainerFactoryInterface $containerFactory A container factory to create container.
46
     */
47
    protected $containerFactory;
48
49
    /**
50
     * @var ConsoleExecutorInterface $consoleExecutor A executor to execute PhpUnitGen task.
51
     */
52
    protected $consoleExecutor;
53
54
    /**
55
     * @var Stopwatch $stopwatch The stopwatch to measure duration and memory usage.
56
     */
57
    protected $stopwatch;
58
59
    /**
60
     * GenerateCommand constructor.
61
     *
62
     * @param ConsoleContainerFactoryInterface $containerFactory A container factory to create container.
63
     * @param Stopwatch                        $stopwatch        The stopwatch component for execution stats.
64
     */
65
    public function __construct(ConsoleContainerFactoryInterface $containerFactory, Stopwatch $stopwatch)
66
    {
67
        parent::__construct();
68
69
        $this->containerFactory = $containerFactory;
70
        $this->stopwatch        = $stopwatch;
71
    }
72
73
    /**
74
     * {@inheritdoc}
75
     */
76
    protected function configure()
77
    {
78
        $this->setName('generate')
79
            ->setAliases(['gen'])
80
            ->setDescription('Generate unit tests skeletons.')
81
            ->setHelp(
82
                'Use it to generate your unit tests skeletons. See documentation on ' .
83
                'https://github.com/paul-thebaud/phpunit-generator/blob/master/DOCUMENTATION.md'
84
            )
85
            ->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'The configuration file path.', 'phpunitgen.yml')
86
            ->addOption('file', 'f', InputOption::VALUE_NONE, 'If you want file parsing.')
87
            ->addOption('dir', 'd', InputOption::VALUE_NONE, 'If you want directory parsing.')
88
            ->addOption('default', 'D', InputOption::VALUE_NONE, 'If you want to use the default configuration.')
89
            ->addArgument('source', InputArgument::OPTIONAL, 'The source path (directory if "dir" option set).')
90
            ->addArgument('target', InputArgument::OPTIONAL, 'The target path (directory if "dir" option set).');
91
    }
92
93
    /**
94
     * {@inheritdoc}
95
     */
96
    protected function execute(InputInterface $input, OutputInterface $output): int
97
    {
98
        $this->stopwatch->start('command');
99
100
        $styledIO = $this->getStyledIO($input, $output);
101
        try {
102
            $config = $this->getConfiguration($input);
103
104
            $container = $this->containerFactory->invoke($config, $styledIO, $this->stopwatch);
105
106
            $this->consoleExecutor = $container->get(ConsoleExecutorInterface::class);
107
108
            $this->consoleExecutor->invoke();
109
        } catch (Exception $exception) {
110
            $styledIO->error($exception->getMessage());
111
            return -1;
112
        }
113
114
        return 0;
115
    }
116
117
    /**
118
     * Build a new instance of SymfonyStyle.
119
     *
120
     * @param InputInterface  $input  The input.
121
     * @param OutputInterface $output The output.
122
     *
123
     * @return SymfonyStyle The created symfony style i/o.
124
     */
125
    protected function getStyledIO(InputInterface $input, OutputInterface $output): SymfonyStyle
126
    {
127
        return new SymfonyStyle($input, $output);
128
    }
129
130
    /**
131
     * Build a configuration from a configuration file path.
132
     *
133
     * @param InputInterface $input An input interface to retrieve command argument.
134
     *
135
     * @return ConsoleConfigInterface The created configuration.
136
     *
137
     * @throws Exception If an error occurs during process.
138
     */
139
    protected function getConfiguration(InputInterface $input): ConsoleConfigInterface
140
    {
141
        // If the configuration to use is the default.
142
        if ($input->getOption('default')) {
143
            $this->validatePathsExist($input);
144
            // If it is a directory.
145
            if ($input->getOption('dir')) {
146
                return (new DefaultConsoleConfigFactory())
147
                    ->invokeDir($input->getArgument('source'), $input->getArgument('target'));
148
            }
149
            return (new DefaultConsoleConfigFactory())
150
                ->invokeFile($input->getArgument('source'), $input->getArgument('target'));
151
        }
152
153
        $path = $input->getOption('config');
154
155
        $factory = $this->getConfigurationFactory($path);
156
157
        if ($input->getOption('dir')) {
158
            $this->validatePathsExist($input);
159
            return $factory->invokeDir(
160
                $path,
161
                $input->getArgument('source'),
162
                $input->getArgument('target')
163
            );
164
        }
165
        if ($input->getOption('file')) {
166
            $this->validatePathsExist($input);
167
            return $factory->invokeFile(
168
                $path,
169
                $input->getArgument('source'),
170
                $input->getArgument('target')
171
            );
172
        }
173
        return $factory->invoke($path);
174
    }
175
176
    /**
177
     * Get the configuration factory depending on configuration path.
178
     *
179
     * @param string $path The configuration file path.
180
     *
181
     * @return AbstractConsoleConfigFactory The configuration factory.
182
     *
183
     * @throws InvalidConfigException If the configuration is invalid.
184
     */
185
    protected function getConfigurationFactory(string $path): AbstractConsoleConfigFactory
186
    {
187
        if (! file_exists($path)) {
188
            throw new InvalidConfigException(sprintf('Config file "%s" does not exists', $path));
189
        }
190
191
        $extension = pathinfo($path, PATHINFO_EXTENSION);
192
        if (! Validator::key($extension)->validate(static::CONSOLE_CONFIG_FACTORIES)) {
193
            throw new InvalidConfigException(
194
                sprintf('Config file "%s" must have .yml, .json or .php extension', $path)
195
            );
196
        }
197
198
        /** @var AbstractConsoleConfigFactory $factory */
199
        $factoryClass = static::CONSOLE_CONFIG_FACTORIES[$extension];
200
        return new $factoryClass();
201
    }
202
203
    /**
204
     * Throw an exception if the source or the target path is missing.
205
     *
206
     * @param InputInterface $input The input interface to check.
207
     *
208
     * @throws Exception If the source or the target path is missing.
209
     */
210
    protected function validatePathsExist(InputInterface $input): void
211
    {
212
        if (! is_string($input->getArgument('source'))) {
213
            throw new Exception('Missing the source path');
214
        }
215
        if (! is_string($input->getArgument('target'))) {
216
            throw new Exception('Missing the target path');
217
        }
218
    }
219
}
220