Passed
Push — master ( 732adf...370de7 )
by
unknown
16:48
created

CommandApplication   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 148
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 22
eloc 63
c 1
b 0
f 0
dl 0
loc 148
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 21 1
A getCommandName() 0 9 2
A wantsFullBoot() 0 6 2
A resolveShortcut() 0 19 6
B run() 0 36 6
A essentialConfigurationExists() 0 4 2
A initializeContext() 0 6 1
A checkEnvironmentOrDie() 0 4 2
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Core\Console;
17
18
use Symfony\Component\Console\Application;
19
use Symfony\Component\Console\Command\Command;
20
use Symfony\Component\Console\Exception\ExceptionInterface;
21
use Symfony\Component\Console\Input\ArgvInput;
22
use Symfony\Component\Console\Output\ConsoleOutput;
23
use TYPO3\CMS\Core\Authentication\CommandLineUserAuthentication;
24
use TYPO3\CMS\Core\Configuration\ConfigurationManager;
25
use TYPO3\CMS\Core\Context\Context;
26
use TYPO3\CMS\Core\Context\DateTimeAspect;
27
use TYPO3\CMS\Core\Context\UserAspect;
28
use TYPO3\CMS\Core\Context\VisibilityAspect;
29
use TYPO3\CMS\Core\Context\WorkspaceAspect;
30
use TYPO3\CMS\Core\Core\ApplicationInterface;
31
use TYPO3\CMS\Core\Core\BootService;
32
use TYPO3\CMS\Core\Core\Bootstrap;
33
use TYPO3\CMS\Core\Core\Environment;
34
use TYPO3\CMS\Core\Information\Typo3Version;
35
use TYPO3\CMS\Core\Localization\LanguageService;
36
37
/**
38
 * Entry point for the TYPO3 Command Line for Commands
39
 * In addition to a simple Symfony Command, this also sets up a CLI user
40
 */
41
class CommandApplication implements ApplicationInterface
42
{
43
    protected Context $context;
44
45
    protected CommandRegistry $commandRegistry;
46
47
    protected ConfigurationManager $configurationManager;
48
49
    protected BootService $bootService;
50
51
    protected Application $application;
52
53
    public function __construct(
54
        Context $context,
55
        CommandRegistry $commandRegistry,
56
        ConfigurationManager $configurationMananger,
57
        BootService $bootService
58
    ) {
59
        $this->context = $context;
60
        $this->commandRegistry = $commandRegistry;
61
        $this->configurationManager = $configurationMananger;
62
        $this->bootService = $bootService;
63
64
        $this->checkEnvironmentOrDie();
65
        $this->application = new Application('TYPO3 CMS', sprintf(
66
            '%s (Application Context: <comment>%s</comment>)',
67
            (new Typo3Version())->getVersion(),
68
            Environment::getContext()
69
        ));
70
        $this->application->setAutoExit(false);
71
        $this->application->setCommandLoader($commandRegistry);
72
        // Replace default list command with TYPO3 override
73
        $this->application->add($commandRegistry->get('list'));
74
    }
75
76
    /**
77
     * Run the Symfony Console application in this TYPO3 application
78
     *
79
     * @param callable $execute
80
     */
81
    public function run(callable $execute = null)
82
    {
83
        $input = new ArgvInput();
84
        $output = new ConsoleOutput();
85
86
        $commandName = $this->getCommandName($input);
87
        if ($this->wantsFullBoot($commandName)) {
88
            // Do a full container boot if command is not a 1:1 matching low-level command
89
            $container = $this->bootService->getContainer();
90
            $commandRegistry = $container->get(CommandRegistry::class);
91
            $this->application->setCommandLoader($commandRegistry);
92
            $this->context = $container->get(Context::class);
93
94
            $realName = $this->resolveShortcut($commandName, $commandRegistry);
95
            $isLowLevelCommandShortcut = $realName !== null && !$this->wantsFullBoot($realName);
96
            // Load ext_localconf, except if a low level command shortcut was found
97
            // or if essential configuration is missing
98
            if (!$isLowLevelCommandShortcut && $this->essentialConfigurationExists()) {
99
                $this->bootService->loadExtLocalconfDatabaseAndExtTables();
100
            }
101
        }
102
103
        $this->initializeContext();
104
        // create the BE_USER object (not logged in yet)
105
        Bootstrap::initializeBackendUser(CommandLineUserAuthentication::class);
106
        $GLOBALS['LANG'] = LanguageService::createFromUserPreferences($GLOBALS['BE_USER']);
107
        // Make sure output is not buffered, so command-line output and interaction can take place
108
        ob_clean();
109
110
        $exitCode = $this->application->run($input, $output);
111
112
        if ($execute !== null) {
113
            call_user_func($execute);
114
        }
115
116
        exit($exitCode);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
117
    }
118
119
    private function resolveShortcut(string $commandName, CommandRegistry $commandRegistry): ?string
120
    {
121
        if ($commandRegistry->has($commandName)) {
122
            return $commandName;
123
        }
124
125
        $allCommands = $commandRegistry->getNames();
126
        $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $commandName))) . '[^:]*';
127
        $commands = preg_grep('{^' . $expr . '}', $allCommands);
128
129
        if ($commands === false || count($commands) === 0) {
130
            $commands = preg_grep('{^' . $expr . '}i', $allCommands);
131
        }
132
133
        if ($commands === false || count($commands) !== 1) {
134
            return null;
135
        }
136
137
        return reset($commands);
138
    }
139
140
    protected function wantsFullBoot(string $commandName): bool
141
    {
142
        if ($commandName === 'help') {
143
            return true;
144
        }
145
        return !$this->commandRegistry->has($commandName);
146
    }
147
148
    protected function getCommandName(ArgvInput $input): string
149
    {
150
        try {
151
            $input->bind($this->application->getDefinition());
152
        } catch (ExceptionInterface $e) {
153
            // Errors must be ignored, full binding/validation happens later when the console application runs.
154
        }
155
156
        return $input->getFirstArgument() ?? 'list';
157
    }
158
159
    /**
160
     * Check if LocalConfiguration.php and PackageStates.php exist
161
     *
162
     * @return bool TRUE when the essential configuration is available, otherwise FALSE
163
     */
164
    protected function essentialConfigurationExists(): bool
165
    {
166
        return file_exists($this->configurationManager->getLocalConfigurationFileLocation())
167
            && file_exists(Environment::getLegacyConfigPath() . '/PackageStates.php');
168
    }
169
170
    /**
171
     * Check the script is called from a cli environment.
172
     */
173
    protected function checkEnvironmentOrDie(): void
174
    {
175
        if (PHP_SAPI !== 'cli') {
176
            die('Not called from a command line interface (e.g. a shell or scheduler).' . LF);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
177
        }
178
    }
179
180
    /**
181
     * Initializes the Context used for accessing data and finding out the current state of the application
182
     */
183
    protected function initializeContext(): void
184
    {
185
        $this->context->setAspect('date', new DateTimeAspect(new \DateTimeImmutable('@' . $GLOBALS['EXEC_TIME'])));
186
        $this->context->setAspect('visibility', new VisibilityAspect(true, true));
187
        $this->context->setAspect('workspace', new WorkspaceAspect(0));
188
        $this->context->setAspect('backend.user', new UserAspect(null));
189
    }
190
}
191