Completed
Push — master ( ba30b6...904d28 )
by Christian
13:26
created

Application::renderException()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
dl 0
loc 22
rs 8.9197
c 2
b 0
f 1
cc 4
eloc 11
nc 4
nop 2
1
<?php
2
3
/**
4
 * This file is part of tenside/core.
5
 *
6
 * (c) Christian Schiffler <[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
 * This project is provided in good faith and hope to be usable by anyone.
12
 *
13
 * @package    tenside/core
14
 * @author     Christian Schiffler <[email protected]>
15
 * @copyright  2015 Christian Schiffler <[email protected]>
16
 * @license    https://github.com/tenside/core/blob/master/LICENSE MIT
17
 * @link       https://github.com/tenside/core
18
 * @filesource
19
 */
20
21
namespace Tenside\Console;
22
23
use Composer\Command as ComposerCommand;
24
use Composer\Command\ScriptAliasCommand;
25
use Composer\IO\IOInterface;
26
use Psr\Log\LoggerInterface;
27
use Symfony\Component\Console\Application as SymfonyApplication;
28
use Composer\IO\ConsoleIO;
29
use Symfony\Component\Console\Command\Command;
30
use Symfony\Component\Console\Input\InputInterface;
31
use Symfony\Component\Console\Input\InputOption;
32
use Symfony\Component\Console\Output\BufferedOutput;
33
use Symfony\Component\Console\Output\OutputInterface;
34
use Symfony\Component\Console\Output\ConsoleOutput;
35
use Symfony\Component\Console\Formatter\OutputFormatter;
36
use Composer\Factory as ComposerFactory;
37
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
38
use Symfony\Component\HttpKernel\Bundle\Bundle;
39
use Symfony\Component\HttpKernel\KernelInterface;
40
use Tenside\Composer\ComposerJson;
41
use Tenside\Tenside;
42
use Tenside\Util\RuntimeHelper;
43
44
/**
45
 * The console application that handles the commands.
46
 */
47
class Application extends SymfonyApplication
48
{
49
    /**
50
     * The kernel in use.
51
     *
52
     * @var KernelInterface
53
     */
54
    private $kernel;
55
56
    /**
57
     * Flag if commands have been registered.
58
     *
59
     * @var bool
60
     */
61
    private $commandsRegistered = false;
62
63
    /**
64
     * The io interface in use.
65
     *
66
     * @var IOInterface
67
     */
68
    private $inputOutput;
69
70
    /**
71
     * Out logo, will get concatenated with the composer logo.
72
     *
73
     * @var string
74
     */
75
    private static $logo = '
76
 _____               _     _
77
/__   \___ _ __  ___(_) __| | ___
78
  / /\/ _ \ \'_ \/ __| |/ _` |/ _ \
79
 / / |  __/ | | \__ \ | (_| |  __/
80
 \/   \___|_| |_|___/_|\__,_|\___|
81
';
82
83
    /**
84
     * Constructor.
85
     *
86
     * @param KernelInterface $kernel A KernelInterface instance.
87
     */
88
    public function __construct(KernelInterface $kernel)
89
    {
90
        if (function_exists('ini_set') && extension_loaded('xdebug')) {
91
            ini_set('xdebug.show_exception_trace', false);
92
            ini_set('xdebug.scream', false);
93
        }
94
95
        $this->ensurePath();
96
97
        if (function_exists('date_default_timezone_set') && function_exists('date_default_timezone_get')) {
98
            date_default_timezone_set(date_default_timezone_get());
99
        }
100
101
        $this->kernel = $kernel;
102
103
        parent::__construct(Tenside::DISTRIBUTION, Tenside::VERSION);
104
105
        $definition = $this->getDefinition();
106
        $definition->addOption(new InputOption('--shell', null, InputOption::VALUE_NONE, 'Launch the shell.'));
107
        $definition->addOption(
108
            new InputOption(
109
                '--process-isolation',
110
                null,
111
                InputOption::VALUE_NONE,
112
                'Launch commands from shell as a separate process.'
113
            )
114
        );
115
116
        if (!\Phar::running()) {
117
            $definition->addOption(
118
                new InputOption(
119
                    '--env',
120
                    '-e',
121
                    InputOption::VALUE_REQUIRED,
122
                    'The Environment name.',
123
                    $kernel->getEnvironment()
124
                )
125
            );
126
127
            $definition->addOption(
128
                new InputOption(
129
                    '--no-debug',
130
                    null,
131
                    InputOption::VALUE_NONE,
132
                    'Switches off debug mode.'
133
                )
134
            );
135
        }
136
    }
137
138
    /**
139
     * Gets the Kernel associated with this Console.
140
     *
141
     * @return KernelInterface A KernelInterface instance
142
     */
143
    public function getKernel()
144
    {
145
        return $this->kernel;
146
    }
147
148
    /**
149
     * {@inheritDoc}
150
     */
151
    public function doRun(InputInterface $input, OutputInterface $output)
152
    {
153
        $this->kernel->boot();
154
155
        if (!$this->commandsRegistered) {
156
            $this->registerCommands($output);
157
158
            $this->commandsRegistered = true;
159
        }
160
161
        $container = $this->kernel->getContainer();
162
163
        foreach ($this->all() as $command) {
164
            if ($command instanceof ContainerAwareInterface) {
165
                $command->setContainer($container);
166
            }
167
        }
168
169
        $this->setDispatcher($container->get('event_dispatcher'));
170
171
        RuntimeHelper::setupHome($container->get('tenside.home')->homeDir());
172
173
        $this->inputOutput = new ConsoleIO($input, $output, $this->getHelperSet());
174
175
        if (version_compare(PHP_VERSION, '5.4', '<')) {
176
            $output->writeln(
177
                '<warning>Tenside only officially supports PHP 5.4 and above, ' .
178
                'you will most likely encounter problems running it with PHP ' . PHP_VERSION .
179
                ', upgrading is strongly recommended.</warning>'
180
            );
181
        }
182
183
        $this->isUpdateNeeded($input, $output);
184
185
        return parent::doRun($input, $output);
186
    }
187
188
    /**
189
     * {@inheritDoc}
190
     */
191
    protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
192
    {
193
        // FIXME: we should check if the command needs the composer instance.
0 ignored issues
show
Coding Style introduced by
Comment refers to a FIXME task "we should check if the command needs the composer instance"
Loading history...
194
        if ($command instanceof \Composer\Command\Command) {
195
            $command->setComposer(ComposerFactory::create($this->inputOutput));
196
            $command->setIO(new ConsoleIO($input, $output, $this->getHelperSet()));
197
        }
198
199
        return parent::doRunCommand($command, $input, $output);
200
    }
201
202
    /**
203
     * Register all commands from the container and bundles in the application.
204
     *
205
     * @param OutputInterface $output The output handler to use.
206
     *
207
     * @return void
208
     */
209
    protected function registerCommands(OutputInterface $output)
210
    {
211
        $container = $this->kernel->getContainer();
212
213
        foreach ($this->kernel->getBundles() as $bundle) {
214
            if ($bundle instanceof Bundle) {
215
                $bundle->registerCommands($this);
216
            }
217
        }
218
219
        if ($container->hasParameter('console.command.ids')) {
220
            foreach ($container->getParameter('console.command.ids') as $id) {
221
                $this->add($container->get($id));
222
            }
223
        }
224
225
        $this->addComposerCommands();
226
227
        /** @var ComposerJson $file */
228
        $file = $container->get('tenside.composer_json');
229
230
        // Add non-standard scripts as own commands - keep this last to ensure we do not override internal commands.
231
        if ($file->has('scripts')) {
232
            foreach (array_keys($file->get('scripts')) as $script) {
233
                if (!defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($script)))) {
234
                    if ($this->has($script)) {
235
                        $output->writeln(
236
                            sprintf(
237
                                '<warning>' .
238
                                'A script named %s would override a native function and has been skipped' .
239
                                '</warning>',
240
                                $script
241
                            )
242
                        );
243
                        continue;
244
                    }
245
                    $this->add(new ScriptAliasCommand($script));
246
                }
247
            }
248
        }
249
    }
250
251
    /**
252
     * Add the composer base commands.
253
     *
254
     * @return void
255
     */
256
    protected function addComposerCommands()
257
    {
258
        $this->add(new ComposerCommand\AboutCommand());
259
        $this->add(new ComposerCommand\ConfigCommand());
260
        $this->add(new ComposerCommand\DependsCommand());
261
        $this->add(new ComposerCommand\InitCommand());
262
        $this->add(new ComposerCommand\InstallCommand());
263
        $this->add(new ComposerCommand\CreateProjectCommand());
264
        $this->add(new ComposerCommand\UpdateCommand());
265
        $this->add(new ComposerCommand\SearchCommand());
266
        $this->add(new ComposerCommand\ValidateCommand());
267
        $this->add(new ComposerCommand\ShowCommand());
268
        $this->add(new ComposerCommand\SuggestsCommand());
269
        $this->add(new ComposerCommand\RequireCommand());
270
        $this->add(new ComposerCommand\DumpAutoloadCommand());
271
        $this->add(new ComposerCommand\StatusCommand());
272
        $this->add(new ComposerCommand\ArchiveCommand());
273
        $this->add(new ComposerCommand\DiagnoseCommand());
274
        $this->add(new ComposerCommand\RunScriptCommand());
275
        $this->add(new ComposerCommand\LicensesCommand());
276
        $this->add(new ComposerCommand\GlobalCommand());
277
        $this->add(new ComposerCommand\ClearCacheCommand());
278
        $this->add(new ComposerCommand\RemoveCommand());
279
        $this->add(new ComposerCommand\HomeCommand());
280
    }
281
282
    /**
283
     * {@inheritDoc}
284
     */
285
    public function run(InputInterface $input = null, OutputInterface $output = null)
286
    {
287
        if (null === $output) {
288
            $styles    = ComposerFactory::createAdditionalStyles();
289
            $formatter = new OutputFormatter(null, $styles);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
290
            $output    = new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, null, $formatter);
291
        }
292
293
        return parent::run($input, $output);
294
    }
295
296
    /**
297
     * {@inheritDoc}
298
     */
299
    public function renderException($exception, $output)
300
    {
301
        // Preserve plain echoing to console...
302
        parent::renderException($exception, $output);
303
304
        // ... but pass to logger as well.
305
        if ($container = $this->kernel->getContainer()) {
306
            /** @var LoggerInterface $logger */
307
            $logger = $container->get('logger');
308
309
            // We want stack traces, therefore be very verbose.
310
            $buffer = new BufferedOutput(BufferedOutput::VERBOSITY_VERBOSE);
311
            parent::renderException($exception, $buffer);
312
            $logger->error('--------------------------------------------------------');
313
            foreach (explode("\n", str_replace("\n\n", "\n", $buffer->fetch())) as $line) {
314
                if ('' !== $line) {
315
                    $logger->error($line);
316
                }
317
            }
318
            $logger->error('--------------------------------------------------------');
319
        }
320
    }
321
322
    /**
323
     * Return the help string.
324
     *
325
     * @return string
326
     */
327
    public function getHelp()
328
    {
329
        return self::$logo . parent::getHelp();
330
    }
331
332
    /**
333
     * {@inheritDoc}
334
     */
335
    public function getLongVersion()
336
    {
337
        return parent::getLongVersion() . ' ' . Tenside::RELEASE_DATE;
338
    }
339
340
    /**
341
     * Check if updating is needed.
342
     *
343
     * @param InputInterface  $input  The input interface.
344
     * @param OutputInterface $output The output interface.
345
     *
346
     * @return bool
347
     *
348
     * @SuppressWarnings(PHPMD.Superglobals)
349
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
350
     */
351
    protected function isUpdateNeeded(InputInterface $input, OutputInterface $output)
352
    {
353
        if ('@warning_time@' !== Tenside::WARNING_TIME) {
354
            $commandName = '';
355
            if ($name = $this->getCommandName($input)) {
356
                try {
357
                    $commandName = $this->find($name)->getName();
358
                } catch (\InvalidArgumentException $e) {
359
                    // Swallow the exception.
360
                }
361
            }
362
            if ($commandName !== 'self-update' && $commandName !== 'selfupdate') {
363
                if (time() > Tenside::WARNING_TIME) {
364
                    $output->writeln(
365
                        sprintf(
366
                            '<warning>Warning: This development build is over 30 days old. ' .
367
                            'It is recommended to update it by running "%s self-update" to get the latest version.' .
368
                            '</warning>',
369
                            $_SERVER['PHP_SELF']
370
                        )
371
                    );
372
373
                    return true;
374
                }
375
            }
376
        }
377
378
        return false;
379
    }
380
381
    /**
382
     * Ensure we have a PATH environment variable.
383
     *
384
     * @return void
385
     */
386
    protected function ensurePath()
387
    {
388
        // "git" binary not found when no PATH environment is present.
389
        // https://github.com/contao-community-alliance/composer-client/issues/54
390
        if (!getenv('PATH')) {
391
            if (defined('PHP_WINDOWS_VERSION_BUILD')) {
392
                putenv('PATH=%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem');
393
            } else {
394
                putenv('PATH=/opt/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin');
395
            }
396
        }
397
    }
398
}
399