Completed
Push — master ( 58f0d2...7a3e19 )
by Pol
09:02
created

Runner::getTasks()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 3
nop 1
dl 0
loc 9
ccs 0
cts 5
cp 0
crap 12
rs 10
c 0
b 0
f 0
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
        \chdir($this->input->getParameterOption('--working-dir', \getcwd()));
78
79
        $this->config = Taskman::createConfiguration(
80
            [],
81
            $this->workingDir
82
        );
83
        $this->application = Taskman::createDefaultApplication(
84
            null,
85
            null,
86
            $this->workingDir
87
        );
88
        $this->container = Taskman::createContainer(
89
            $this->input,
90
            $this->output,
91
            $this->application,
92
            $this->config,
93
            $this->classLoader
94
        );
95
96
        $this->runner = Taskman::createDefaultRunner($this->container);
97
    }
98
99
    /**
100
     * @param mixed $args
101
     *
102
     * @throws \ReflectionException
103
     *
104
     * @return int
105
     */
106
    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

106
    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...
107
    {
108
        // Register command classes.
109
        $this->runner->registerCommandClasses($this->application, [YamlCommands::class]);
110
111
        // Register commands defined in task.yml file.
112
        $this->registerDynamicCommands($this->application);
113
114
        // Register tasks
115
        $this->registerDynamicTasks($this->application);
116
117
        // Run command.
118
        return $this->runner->run($this->input, $this->output, $this->application);
119
    }
120
121
    /**
122
     * @param bool $hasDefault
123
     * @param mixed $defaultValue
124
     *
125
     * @return int
126
     */
127
    protected function getCommandArgumentMode(bool $hasDefault, $defaultValue)
128
    {
129
        if (!$hasDefault) {
130
            return InputArgument::REQUIRED;
131
        }
132
        if (\is_array($defaultValue)) {
133
            return InputArgument::IS_ARRAY;
134
        }
135
136
        return InputArgument::OPTIONAL;
137
    }
138
139
    /**
140
     * @param \Consolidation\AnnotatedCommand\AnnotatedCommand $command
141
     * @param array $commandDefinition
142
     */
143
    private function addOptions(AnnotatedCommand $command, array $commandDefinition)
144
    {
145
        // This command doesn't define any option.
146
        if (empty($commandDefinition['options'])) {
147
            return;
148
        }
149
150
        $defaults = \array_fill_keys(['shortcut', 'mode', 'description', 'default'], null);
151
        foreach ($commandDefinition['options'] as $optionName => $optionDefinition) {
152
            $optionDefinition += $defaults;
153
            $command->addOption(
154
                '--' . $optionName,
155
                $optionDefinition['shortcut'],
156
                $optionDefinition['mode'],
157
                $optionDefinition['description'],
158
                $optionDefinition['default']
159
            );
160
        }
161
    }
162
163
    /**
164
     * @param \Symfony\Component\Console\Input\InputInterface $input
165
     *
166
     * @return mixed
167
     */
168
    private function getWorkingDir(InputInterface $input)
0 ignored issues
show
Unused Code introduced by
The method getWorkingDir() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
Unused Code introduced by
The parameter $input 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

168
    private function getWorkingDir(/** @scrutinizer ignore-unused */ InputInterface $input)

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...
169
    {
170
    }
171
172
    /**
173
     * @param \Robo\Application $application
174
     */
175
    private function registerDynamicCommands(Application $application)
176
    {
177
        $commandDefinitions = $this->getConfig()->get('commands', []);
178
179
        foreach ($commandDefinitions as $name => $commandDefinition) {
180
            /** @var \PhpTaskman\Core\Robo\Plugin\Commands\YamlCommands $commandClass */
181
            $commandClass = $this->container->get(YamlCommands::class . 'Commands');
182
183
            /** @var \Consolidation\AnnotatedCommand\AnnotatedCommandFactory $commandFactory */
184
            $commandFactory = $this->container->get('commandFactory');
185
186
            $commandInfo = $commandFactory->createCommandInfo($commandClass, 'runTasks');
187
188
            $commandDefinition += ['options' => []];
189
            foreach ($commandDefinition['options'] as &$option) {
190
                if (isset($option['mode'])) {
191
                    continue;
192
                }
193
194
                $option['mode'] = $this->getCommandArgumentMode(
195
                    isset($option['default']),
196
                    $option['default'] ?? null
197
                );
198
            }
199
200
            $command = $commandFactory->createCommand($commandInfo, $commandClass)->setName($name);
201
202
            // Override default description.
203
            if (isset($commandDefinition['description'])) {
204
                $command->setDescription($commandDefinition['description']);
205
            }
206
            // Override default help.
207
            if (isset($commandDefinition['help'])) {
208
                $command->setHelp($commandDefinition['help']);
209
            }
210
211
            // Dynamic commands may define their own options.
212
            $this->addOptions($command, $commandDefinition);
213
214
            $tasks = $commandDefinition['tasks'] ?? $commandDefinition;
215
216
            // Append also options of subsequent tasks.
217
            foreach ($tasks as $taskEntry) {
218
                if (!\is_array($taskEntry)) {
219
                    continue;
220
                }
221
222
                if (!isset($taskEntry['task'])) {
223
                    continue;
224
                }
225
226
                if ('run' !== $taskEntry['task']) {
227
                    continue;
228
                }
229
230
                if (empty($taskEntry['command'])) {
231
                    continue;
232
                }
233
234
                // This is a 'run' task.
235
                if (!empty($customCommands[$taskEntry['command']])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $customCommands seems to never exist and therefore empty should always be true.
Loading history...
236
                    // Add the options of another custom command.
237
                    $this->addOptions($command, $customCommands[$taskEntry['command']]);
238
                } else {
239
                    // Add the options of an already registered command.
240
                    if ($this->application->has($taskEntry['command'])) {
241
                        $subCommand = $this->application->get($taskEntry['command']);
242
                        $command->addOptions($subCommand->getDefinition()->getOptions());
243
                    }
244
                }
245
            }
246
247
            $application->add($command);
248
        }
249
    }
250
251
    /**
252
     * @param \Robo\Application $application
253
     *
254
     * @throws \ReflectionException
255
     */
256
    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

256
    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...
257
    {
258
        $classes = Taskman::discoverTasksClasses('Plugin');
259
260
        /** @var \ReflectionClass[] $tasks */
261
        $tasks = [];
262
        foreach ($classes as $className) {
263
            $class = new \ReflectionClass($className);
264
            if (!$class->isInstantiable()) {
265
                continue;
266
            }
267
            $tasks[] = $class;
268
        }
269
270
        $builder = CollectionBuilder::create($this->container, '');
271
272
        $inflector = $this->container->inflector(BuilderAwareInterface::class);
273
        if ($inflector instanceof Inflector) {
0 ignored issues
show
introduced by
$inflector is always a sub-type of League\Container\Inflector\Inflector.
Loading history...
274
            $inflector->invokeMethod('setBuilder', [$builder]);
275
        }
276
277
        // Register custom Task classes.
278
        foreach ($tasks as $taskReflectionClass) {
279
            $this->container->add(
280
                'task.' . $taskReflectionClass->getConstant('NAME'),
281
                $taskReflectionClass->getName()
282
            );
283
        }
284
285
        // Register custom YAML tasks.
286
        $customTasks = $this->getConfig()->get('tasks', []);
287
288
        foreach ($customTasks as $name => $tasks) {
289
            $this->container->add(
290
                'task.' . $name,
291
                YamlTask::class
292
            )->withArgument($tasks);
293
        }
294
    }
295
}
296