Passed
Push — master ( bf7dfe...58f0d2 )
by Pol
06:58 queued 33s
created

Runner::registerDynamicCommands()   C

Complexity

Conditions 13
Paths 97

Size

Total Lines 68
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 182

Importance

Changes 0
Metric Value
cc 13
eloc 35
nc 97
nop 1
dl 0
loc 68
ccs 0
cts 35
cp 0
crap 182
rs 6.6166
c 0
b 0
f 0

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
declare(strict_types = 1);
4
5
namespace PhpTaskman\Core;
6
7
use Composer\Autoload\ClassLoader;
8
use Consolidation\AnnotatedCommand\AnnotatedCommand;
9
use League\Container\Inflector\Inflector;
10
use PhpTaskman\Core\Plugin\Task\YamlTask;
11
use PhpTaskman\Core\Robo\Plugin\Commands\YamlCommands;
12
use League\Container\ContainerAwareTrait;
13
use Robo\Application;
14
use Robo\Collection\CollectionBuilder;
15
use Robo\Common\ConfigAwareTrait;
16
use Robo\Contract\BuilderAwareInterface;
17
use Symfony\Component\Console\Input\ArgvInput;
18
use Symfony\Component\Console\Input\InputArgument;
19
use Symfony\Component\Console\Input\InputInterface;
20
use Symfony\Component\Console\Output\ConsoleOutput;
21
use Symfony\Component\Console\Output\OutputInterface;
22
23
/**
24
 * Class Runner.
25
 */
26
final class Runner
27
{
28
    use ConfigAwareTrait;
29
    use ContainerAwareTrait;
30
31
    /**
32
     * @var Application
33
     */
34
    private $application;
35
36
    /**
37
     * @var \Composer\Autoload\ClassLoader
38
     */
39
    private $classLoader;
40
41
    /**
42
     * @var InputInterface
43
     */
44
    private $input;
45
46
    /**
47
     * @var OutputInterface
48
     */
49
    private $output;
50
51
    /**
52
     * @var \Robo\Runner
53
     */
54
    private $runner;
55
56
    /**
57
     * @var string
58
     */
59
    private $workingDir;
60
61
    /**
62
     * Runner constructor.
63
     *
64
     * @param InputInterface $input
65
     * @param OutputInterface $output
66
     * @param ClassLoader $classLoader
67
     */
68
    public function __construct(
69
        InputInterface $input = null,
70
        OutputInterface $output = null,
71
        ClassLoader $classLoader = null
72
    ) {
73
        $this->input = $input ?? new ArgvInput();
74
        $this->output = $output ?? new ConsoleOutput();
75
        $this->classLoader = $classLoader ?? new ClassLoader();
76
77
        $this->workingDir = $this->getWorkingDir($this->input);
78
        \chdir($this->workingDir);
79
80
        $this->config = Taskman::createConfiguration(
81
            [],
82
            $this->workingDir
83
        );
84
        $this->application = Taskman::createDefaultApplication(
85
            null,
86
            null,
87
            $this->workingDir
88
        );
89
        $this->container = Taskman::createContainer(
90
            $this->input,
91
            $this->output,
92
            $this->application,
93
            $this->config,
94
            $this->classLoader
95
        );
96
97
        $this->runner = Taskman::createDefaultRunner($this->container);
98
    }
99
100
    /**
101
     * @param mixed $args
102
     *
103
     * @throws \ReflectionException
104
     *
105
     * @return int
106
     */
107
    public function run($args)
0 ignored issues
show
Unused Code introduced by
The parameter $args is not used and could be removed. ( Ignorable by Annotation )

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

107
    public function run(/** @scrutinizer ignore-unused */ $args)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
108
    {
109
        // Register command classes.
110
        $this->runner->registerCommandClasses($this->application, [YamlCommands::class]);
111
112
        // Register commands defined in task.yml file.
113
        $this->registerDynamicCommands($this->application);
114
115
        // Register tasks
116
        $this->registerDynamicTasks($this->application);
117
118
        // Run command.
119
        return $this->runner->run($this->input, $this->output, $this->application);
120
    }
121
122
    /**
123
     * @param bool $hasDefault
124
     * @param mixed $defaultValue
125
     *
126
     * @return int
127
     */
128
    protected function getCommandArgumentMode(bool $hasDefault, $defaultValue)
129
    {
130
        if (!$hasDefault) {
131
            return InputArgument::REQUIRED;
132
        }
133
        if (\is_array($defaultValue)) {
134
            return InputArgument::IS_ARRAY;
135
        }
136
137
        return InputArgument::OPTIONAL;
138
    }
139
140
    /**
141
     * @param \Consolidation\AnnotatedCommand\AnnotatedCommand $command
142
     * @param array $commandDefinition
143
     */
144
    private function addOptions(AnnotatedCommand $command, array $commandDefinition)
145
    {
146
        // This command doesn't define any option.
147
        if (empty($commandDefinition['options'])) {
148
            return;
149
        }
150
151
        $defaults = \array_fill_keys(['shortcut', 'mode', 'description', 'default'], null);
152
        foreach ($commandDefinition['options'] as $optionName => $optionDefinition) {
153
            $optionDefinition += $defaults;
154
            $command->addOption(
155
                '--' . $optionName,
156
                $optionDefinition['shortcut'],
157
                $optionDefinition['mode'],
158
                $optionDefinition['description'],
159
                $optionDefinition['default']
160
            );
161
        }
162
    }
163
164
    /**
165
     * @param string $command
166
     *
167
     * @throws \InvalidArgumentException
168
     *
169
     * @return array
170
     */
171
    private function getTasks($command)
172
    {
173
        $commands = $this->getConfig()->get('commands', []);
174
175
        if (!isset($commands[$command])) {
176
            throw new \InvalidArgumentException("Custom command '${command}' not defined.");
177
        }
178
179
        return !empty($commands[$command]['tasks']) ? $commands[$command]['tasks'] : $commands[$command];
180
    }
181
182
    /**
183
     * @param \Symfony\Component\Console\Input\InputInterface $input
184
     *
185
     * @return mixed
186
     */
187
    private function getWorkingDir(InputInterface $input)
188
    {
189
        return $input->getParameterOption('--working-dir', \getcwd());
190
    }
191
192
    /**
193
     * @param \Robo\Application $application
194
     */
195
    private function registerDynamicCommands(Application $application)
196
    {
197
        $customCommands = $this->getConfig()->get('commands', []);
198
        foreach ($customCommands as $name => $commandDefinition) {
199
            /** @var \Consolidation\AnnotatedCommand\AnnotatedCommandFactory $commandFactory */
200
            $commandFileName = YamlCommands::class . 'Commands';
201
            $commandClass = $this->container->get($commandFileName);
202
            $commandFactory = $this->container->get('commandFactory');
203
            $commandInfo = $commandFactory->createCommandInfo($commandClass, 'runTasks');
204
205
            $commandDefinition += ['options' => []];
206
            foreach ($commandDefinition['options'] as &$option) {
207
                if (isset($option['mode'])) {
208
                    continue;
209
                }
210
211
                $option['mode'] = $this->getCommandArgumentMode(
212
                    isset($option['default']),
213
                    $option['default'] ?? null
214
                );
215
            }
216
217
            $command = $commandFactory->createCommand($commandInfo, $commandClass)->setName($name);
218
219
            // Override default description.
220
            if (isset($commandDefinition['description'])) {
221
                $command->setDescription($commandDefinition['description']);
222
            }
223
            // Override default help.
224
            if (isset($commandDefinition['help'])) {
225
                $command->setHelp($commandDefinition['help']);
226
            }
227
228
            // Dynamic commands may define their own options.
229
            $this->addOptions($command, $commandDefinition);
230
231
            // Append also options of subsequent tasks.
232
            foreach ($this->getTasks($name) as $taskEntry) {
233
                if (!\is_array($taskEntry)) {
234
                    continue;
235
                }
236
237
                if (!isset($taskEntry['task'])) {
238
                    continue;
239
                }
240
241
                if ('run' !== $taskEntry['task']) {
242
                    continue;
243
                }
244
245
                if (empty($taskEntry['command'])) {
246
                    continue;
247
                }
248
249
                // This is a 'run' task.
250
                if (!empty($customCommands[$taskEntry['command']])) {
251
                    // Add the options of another custom command.
252
                    $this->addOptions($command, $customCommands[$taskEntry['command']]);
253
                } else {
254
                    // Add the options of an already registered command.
255
                    if ($this->application->has($taskEntry['command'])) {
256
                        $subCommand = $this->application->get($taskEntry['command']);
257
                        $command->addOptions($subCommand->getDefinition()->getOptions());
258
                    }
259
                }
260
            }
261
262
            $application->add($command);
263
        }
264
    }
265
266
    /**
267
     * @param \Robo\Application $application
268
     *
269
     * @throws \ReflectionException
270
     */
271
    private function registerDynamicTasks(Application $application)
0 ignored issues
show
Unused Code introduced by
The parameter $application is not used and could be removed. ( Ignorable by Annotation )

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

271
    private function registerDynamicTasks(/** @scrutinizer ignore-unused */ Application $application)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
272
    {
273
        $classes = Taskman::discoverTasksClasses('Plugin');
274
275
        /** @var \ReflectionClass[] $tasks */
276
        $tasks = [];
277
        foreach ($classes as $className) {
278
            $class = new \ReflectionClass($className);
279
            if (!$class->isInstantiable()) {
280
                continue;
281
            }
282
            $tasks[] = $class;
283
        }
284
285
        $builder = CollectionBuilder::create($this->container, '');
286
287
        $inflector = $this->container->inflector(BuilderAwareInterface::class);
288
        if ($inflector instanceof Inflector) {
0 ignored issues
show
introduced by
$inflector is always a sub-type of League\Container\Inflector\Inflector.
Loading history...
289
            $inflector->invokeMethod('setBuilder', [$builder]);
290
        }
291
292
        // Register custom Task classes.
293
        foreach ($tasks as $taskReflectionClass) {
294
            $this->container->add(
295
                'task.' . $taskReflectionClass->getConstant('NAME'),
296
                $taskReflectionClass->getName()
297
            );
298
        }
299
300
        // Register custom YAML tasks.
301
        $customTasks = $this->getConfig()->get('tasks', []);
302
303
        foreach ($customTasks as $name => $tasks) {
304
            $this->container->add(
305
                'task.' . $name,
306
                YamlTask::class
307
            )->withArgument($tasks);
308
        }
309
    }
310
}
311