Failed Conditions
Push — develop ( d75317...da2a0b )
by Maxime
45:40 queued 26:16
created

src/Rocketeer/Console/Commands/AbstractCommand.php (1 issue)

1
<?php
2
3
/*
4
 * This file is part of Rocketeer
5
 *
6
 * (c) Maxime Fabre <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 */
12
13
namespace Rocketeer\Console\Commands;
14
15
use League\Container\ContainerAwareInterface;
16
use Rocketeer\Console\Style\RocketeerStyle;
17
use Rocketeer\Interfaces\IdentifierInterface;
18
use Rocketeer\Tasks\AbstractTask;
19
use Rocketeer\Tasks\Closure;
20
use Rocketeer\Tasks\Plugins\Installer;
21
use Rocketeer\Tasks\Plugins\Updater;
22
use Rocketeer\Traits\ContainerAwareTrait;
23
use Rocketeer\Traits\Properties\HasEventsTrait;
24
use Symfony\Component\Console\Command\Command;
25
use Symfony\Component\Console\Input\InputInterface;
26
use Symfony\Component\Console\Input\InputOption;
27
use Symfony\Component\Console\Output\OutputInterface;
28
29
/**
30
 * An abstract command with various helpers for all
31
 * subcommands to inherit.
32
 *
33
 * @mixin RocketeerStyle
34
 */
35
abstract class AbstractCommand extends Command implements IdentifierInterface, ContainerAwareInterface
36
{
37
    use ContainerAwareTrait;
38
    use HasEventsTrait;
39
40
    /**
41
     * @var InputInterface
42
     */
43
    protected $input;
44
45
    /**
46
     * @var RocketeerStyle
47
     */
48
    protected $output;
49
50
    /**
51
     * The console command name.
52
     *
53
     * @var string
54
     */
55
    protected $name;
56
57
    /**
58
     * The console command description.
59
     *
60
     * @var string
61
     */
62
    protected $description;
63
64
    /**
65
     * Whether the command's task should be built
66
     * into a pipeline or run straight.
67
     *
68
     * @var bool
69
     */
70
    protected $straight = false;
71
72
    /**
73
     * the task to execute on fire.
74
     *
75
     * @var \Rocketeer\Tasks\AbstractTask
76
     */
77
    protected $task;
78
79
    /**
80
     * @param \Rocketeer\Tasks\AbstractTask|null $task
81
     */
82
    public function __construct(AbstractTask $task = null)
83
    {
84
        parent::__construct($this->name);
85
86
        $this->setDescription($this->description);
87
        $this->specifyParameters();
88
89
        // If we passed a Task, bind its properties
90
        // to the command
91
        if ($task) {
92
            $this->task = $task;
93
94
            if (!$this->description && $description = $task->getDescription()) {
95
                $this->setDescription($description);
96
            }
97
        }
98
    }
99
100
    /**
101
     * @return InputInterface
102
     */
103
    public function getInput()
104
    {
105
        return $this->input;
106
    }
107
108
    /**
109
     * @return RocketeerStyle
110
     */
111
    public function getOutput()
112
    {
113
        return $this->output;
114
    }
115
116
    //////////////////////////////////////////////////////////////////////
117
    ////////////////////////////// ARGUMENTS /////////////////////////////
118
    //////////////////////////////////////////////////////////////////////
119
120
    /**
121
     * Get the console command arguments.
122
     *
123
     * @return array
124
     */
125
    protected function getArguments()
126
    {
127
        return [];
128
    }
129
130
    /**
131
     * Get the console command options.
132
     *
133
     * @return array
134
     */
135
    protected function getOptions()
136
    {
137
        // General options
138
        $global = [
139
            ['parallel', 'P', InputOption::VALUE_NONE, 'Run the tasks asynchronously instead of sequentially'],
140
            [
141
                'pretend',
142
                'p',
143
                InputOption::VALUE_NONE,
144
                'Shows which command would execute without actually doing anything',
145
            ],
146
        ];
147
148
        // Options that override the predefined configuration
149
        $overrides = [
150
            ['on', 'C', InputOption::VALUE_REQUIRED, 'The connection(s) to execute the Task in'],
151
            ['stage', 'S', InputOption::VALUE_REQUIRED, 'The stage to execute the Task in'],
152
            ['server', null, InputOption::VALUE_REQUIRED, 'The server to execute the Task in'],
153
            ['branch', 'B', InputOption::VALUE_REQUIRED, 'The branch to deploy'],
154
            ['release', null, InputOption::VALUE_REQUIRED, 'What to tag the release as'],
155
        ];
156
157
        // Additional credentials passed to Rocketeer
158
        $credentials = [
159
            ['host', null, InputOption::VALUE_REQUIRED, 'The host to use if asked'],
160
            ['root', null, InputOption::VALUE_REQUIRED, 'The root directory to use if asked'],
161
            ['username', null, InputOption::VALUE_REQUIRED, 'The username to use if asked'],
162
            ['password', null, InputOption::VALUE_REQUIRED, 'The password to use if asked'],
163
            ['key', null, InputOption::VALUE_REQUIRED, 'The key to use if asked'],
164
            ['keyphrase', null, InputOption::VALUE_REQUIRED, 'The keyphrase to use if asked'],
165
            ['repository', null, InputOption::VALUE_REQUIRED, 'The repository to use if asked'],
166
        ];
167
168
        return array_merge(
169
            $global,
170
            $overrides,
171
            $credentials
172
        );
173
    }
174
175
    /**
176
     * Specify the arguments and options on the command.
177
     */
178
    protected function specifyParameters()
179
    {
180
        // We will loop through all of the arguments and options for the command and
181
        // set them all on the base command instance. This specifies what can get
182
        // passed into these commands as "parameters" to control the execution.
183
        foreach ($this->getArguments() as $arguments) {
184
            $this->addArgument(...$arguments);
185
        }
186
187
        foreach ($this->getOptions() as $options) {
188
            $this->addOption(...$options);
189
        }
190
    }
191
192
    //////////////////////////////////////////////////////////////////////
193
    ////////////////////////////// CONTAINER /////////////////////////////
194
    //////////////////////////////////////////////////////////////////////
195
196
    /**
197
     * @param string $key
198
     *
199
     * @return mixed
200
     */
201
    public function __get($key)
202
    {
203
        $key = $this->getLocatorHandle($key);
204
        if ($key === 'command') {
205
            return $this;
206
        }
207
208
        return $this->container->get($key);
209
    }
210
211
    /**
212
     * @param string $name
213
     * @param array  $arguments
214
     *
215
     * @return mixed
216
     */
217
    public function __call($name, $arguments)
218
    {
219
        // Defer calls to output
220
        if (method_exists($this->output, $name)) {
221
            return $this->output->$name(...$arguments);
222
        }
223
    }
224
225
    /**
226
     * Get the task this command executes.
227
     *
228
     * @return AbstractTask
229
     */
230
    public function getTask()
231
    {
232
        return $this->task;
233
    }
234
235
    /**
236
     * Get a global identifier for this entity.
237
     *
238
     * @return string
239
     */
240
    public function getIdentifier()
241
    {
242
        return 'commands.'.str_replace(':', '.', $this->name);
243
    }
244
245
    //////////////////////////////////////////////////////////////////////
246
    ////////////////////////////// EXECUTION /////////////////////////////
247
    //////////////////////////////////////////////////////////////////////
248
249
    /**
250
     * Execute the console command.
251
     *
252
     * @param \Symfony\Component\Console\Input\InputInterface   $input
253
     * @param \Symfony\Component\Console\Output\OutputInterface $output
254
     *
255
     * @return mixed
256
     */
257
    protected function execute(InputInterface $input, OutputInterface $output)
258
    {
259
        $this->input = $input;
260
        $this->output = new RocketeerStyle($input, $output);
261
262
        return $this->container->call([$this, 'fire']);
263
    }
264
265
    /**
266
     * Fire a Tasks Queue.
267
     *
268
     * @param string|string[]|Closure|Closure[]|\Rocketeer\Tasks\AbstractTask|\Rocketeer\Tasks\AbstractTask[] $tasks
269
     *
270
     * @return int
271
     */
272
    protected function fireTasksQueue($tasks)
273
    {
274
        $this->prepareEnvironment();
275
276
        // Fire tasks and events arround them
277
        $status = $this->runWithBeforeAfterEvents(function () use ($tasks) {
278
            if ($this->straight) {
279
                return $this->builder->buildTask($tasks)->fire();
280
            }
281
282
            $pipeline = $this->queue->run($tasks);
283
284
            return $pipeline->succeeded();
285
        });
286
287
        // Remove command instance
288
        $this->container->remove('command');
289
290
        // Save history to logs
291
        $this->explainer->info('Saved logs to '.$this->logs->getLogsRealpath());
292
293
        return $status ? 0 : 1;
294
    }
295
296
    /**
297
     * Run the tasks.
298
     *
299
     * @return mixed
300
     */
301
    abstract public function fire();
302
303
    //////////////////////////////////////////////////////////////////////
304
    /////////////////////////////// INPUT ////////////////////////////////
305
    //////////////////////////////////////////////////////////////////////
306
307
    /**
308
     * Get the value of a command argument.
309
     *
310
     * @param string $key
311
     *
312
     * @return string|array
313
     */
314
    public function argument($key = null)
315
    {
316
        if (is_null($key)) {
317
            return $this->input->getArguments();
318
        }
319
320
        return $this->input->getArgument($key);
321
    }
322
323
    /**
324
     * Get the value of a command option.
325
     *
326
     * @param string $key
327
     *
328
     * @return string|array
329
     */
330
    public function option($key = null)
331
    {
332
        if (is_null($key)) {
333
            return $this->input->getOptions();
334
        }
335
336
        return $this->input->getOption($key);
337
    }
338
339
    //////////////////////////////////////////////////////////////////////
340
    ////////////////////////////// HELPERS ///////////////////////////////
341
    //////////////////////////////////////////////////////////////////////
342
343
    /**
344
     * Time an operation and display it afterwards.
345
     *
346
     * @param callable $callback
347
     *
348
     * @return bool
349
     */
350
    public function time(callable $callback)
351
    {
352
        $results = $this->timer->time($this, $callback);
353
        $time = $this->timer->getLatestTime($this);
354
355
        $this->explainer->line('Execution time: <comment>'.$time.'s</comment>');
356
357
        return $results;
358
    }
359
360
    /**
361
     * Prepare the environment.
362
     */
363
    protected function prepareEnvironment()
364
    {
365
        // Bind command to container
366
        $this->container->add('command', $this);
367
368
        // Set active connections from flag
369
        if ($connections = $this->command->option('on')) {
370
            $this->connections->setActiveConnections($connections);
371
        }
372
373
        // Setup plugins if not setup already
374
        $vendor = $this->paths->getRocketeerPath().DS.'vendor';
0 ignored issues
show
Documentation Bug introduced by Maxime Fabre
The method getRocketeerPath does not exist on object<Rocketeer\Services\Environment\Pathfinder>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
375
        if (!$this->files->has($vendor)) {
376
            $this->queue->execute(Installer::class);
377
        }
378
    }
379
}
380