|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* This file is part of the eZ Publish Kernel package. |
|
5
|
|
|
* |
|
6
|
|
|
* @copyright Copyright (C) eZ Systems AS. All rights reserved. |
|
7
|
|
|
* @license For full copyright and license information view LICENSE file distributed with this source code. |
|
8
|
|
|
*/ |
|
9
|
|
|
namespace EzSystems\PlatformInstallerBundle\Command; |
|
10
|
|
|
|
|
11
|
|
|
use Doctrine\DBAL\Connection; |
|
12
|
|
|
use Doctrine\DBAL\Exception\ConnectionException; |
|
13
|
|
|
use Symfony\Component\Console\Command\Command; |
|
14
|
|
|
use Symfony\Component\Console\Input\InputArgument; |
|
15
|
|
|
use Symfony\Component\Console\Input\InputOption; |
|
16
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
|
17
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
|
18
|
|
|
use Symfony\Component\Process\Process; |
|
19
|
|
|
use Symfony\Component\Process\PhpExecutableFinder; |
|
20
|
|
|
use Symfony\Component\Filesystem\Filesystem; |
|
21
|
|
|
use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface; |
|
22
|
|
|
|
|
23
|
|
|
class InstallPlatformCommand extends Command |
|
24
|
|
|
{ |
|
25
|
|
|
/** @var \Doctrine\DBAL\Connection */ |
|
26
|
|
|
private $db; |
|
27
|
|
|
|
|
28
|
|
|
/** @var \Symfony\Component\Console\Output\OutputInterface */ |
|
29
|
|
|
private $output; |
|
30
|
|
|
|
|
31
|
|
|
/** @var \Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface */ |
|
32
|
|
|
private $cacheClearer; |
|
33
|
|
|
|
|
34
|
|
|
/** @var \Symfony\Component\Filesystem\Filesystem */ |
|
35
|
|
|
private $filesystem; |
|
36
|
|
|
|
|
37
|
|
|
/** @var string */ |
|
38
|
|
|
private $cacheDir; |
|
39
|
|
|
|
|
40
|
|
|
/** @var string */ |
|
41
|
|
|
private $environment; |
|
42
|
|
|
|
|
43
|
|
|
/** @var string */ |
|
44
|
|
|
private $searchEngine; |
|
45
|
|
|
|
|
46
|
|
|
/** @var \EzSystems\PlatformInstallerBundle\Installer\Installer[] */ |
|
47
|
|
|
private $installers = array(); |
|
48
|
|
|
|
|
49
|
|
|
const EXIT_DATABASE_NOT_FOUND_ERROR = 3; |
|
50
|
|
|
const EXIT_GENERAL_DATABASE_ERROR = 4; |
|
51
|
|
|
const EXIT_PARAMETERS_NOT_FOUND = 5; |
|
52
|
|
|
const EXIT_UNKNOWN_INSTALL_TYPE = 6; |
|
53
|
|
|
const EXIT_MISSING_PERMISSIONS = 7; |
|
54
|
|
|
|
|
55
|
|
|
public function __construct( |
|
56
|
|
|
Connection $db, |
|
57
|
|
|
array $installers, |
|
58
|
|
|
CacheClearerInterface $cacheClearer, |
|
59
|
|
|
Filesystem $filesystem, |
|
60
|
|
|
$cacheDir, |
|
61
|
|
|
$environment, |
|
62
|
|
|
$searchEngine |
|
63
|
|
|
) { |
|
64
|
|
|
$this->db = $db; |
|
65
|
|
|
$this->installers = $installers; |
|
66
|
|
|
$this->cacheClearer = $cacheClearer; |
|
67
|
|
|
$this->filesystem = $filesystem; |
|
68
|
|
|
$this->cacheDir = $cacheDir; |
|
69
|
|
|
$this->environment = $environment; |
|
70
|
|
|
$this->searchEngine = $searchEngine; |
|
71
|
|
|
parent::__construct(); |
|
72
|
|
|
} |
|
73
|
|
|
|
|
74
|
|
|
protected function configure() |
|
75
|
|
|
{ |
|
76
|
|
|
$this->setName('ezplatform:install'); |
|
77
|
|
|
$this->addArgument( |
|
78
|
|
|
'type', |
|
79
|
|
|
InputArgument::REQUIRED, |
|
80
|
|
|
'The type of install. Available options: ' . implode(', ', array_keys($this->installers)) |
|
81
|
|
|
); |
|
82
|
|
|
// @todo This is not the right approach, base configure step, just like base schema, should be inline here |
|
83
|
|
|
// @todo and not on installer duplicating things in every installer |
|
84
|
|
|
// @todo OR? Problem with that argument is that if installers (db) is allowed to generate config we violate 12-factor |
|
85
|
|
|
// @note Only Studio demo inserts config, and it might unneded. |
|
86
|
|
|
$this->addOption( |
|
87
|
|
|
'skip-config', |
|
88
|
|
|
null, |
|
89
|
|
|
InputOption::VALUE_NONE, |
|
90
|
|
|
'To skip dumping configuration, for use when running command first with `skip-data`' |
|
91
|
|
|
); |
|
92
|
|
|
$this->addOption( |
|
93
|
|
|
'skip-data', |
|
94
|
|
|
null, |
|
95
|
|
|
InputOption::VALUE_NONE, |
|
96
|
|
|
'To skip inserting data, for use when running command in second iteration with `skip-config`' |
|
97
|
|
|
); |
|
98
|
|
|
} |
|
99
|
|
|
|
|
100
|
|
|
protected function execute(InputInterface $input, OutputInterface $output) |
|
101
|
|
|
{ |
|
102
|
|
|
$this->output = $output; |
|
103
|
|
|
$this->checkPermissions(); |
|
104
|
|
|
$this->checkParameters(); |
|
105
|
|
|
$this->checkDatabase(); |
|
106
|
|
|
|
|
107
|
|
|
$type = $input->getArgument('type'); |
|
108
|
|
|
$installer = $this->getInstaller($type); |
|
109
|
|
|
if ($installer === false) { |
|
110
|
|
|
$output->writeln("Unknown install type '$type'"); |
|
111
|
|
|
exit(self::EXIT_UNKNOWN_INSTALL_TYPE); |
|
112
|
|
|
} |
|
113
|
|
|
|
|
114
|
|
|
$installer->setOutput($output); |
|
115
|
|
|
|
|
116
|
|
|
$skipConfigure = $input->getOption('skip-config'); |
|
117
|
|
|
$skipData = $input->getOption('skip-data'); |
|
118
|
|
|
|
|
119
|
|
|
if (!$skipConfigure) { |
|
120
|
|
|
$installer->createConfiguration(); |
|
121
|
|
|
} |
|
122
|
|
|
|
|
123
|
|
|
if (!$skipData) { |
|
124
|
|
|
$installer->importSchema(); |
|
125
|
|
|
$installer->importData(); |
|
126
|
|
|
$installer->importBinaries(); |
|
127
|
|
|
} |
|
128
|
|
|
|
|
129
|
|
|
$this->cacheClear($output); |
|
130
|
|
|
|
|
131
|
|
|
if (!$skipData) { |
|
132
|
|
|
$this->indexData($output); |
|
133
|
|
|
} |
|
134
|
|
|
} |
|
135
|
|
|
|
|
136
|
|
|
private function checkPermissions() |
|
137
|
|
|
{ |
|
138
|
|
|
if (!is_writable('app/config')) { |
|
139
|
|
|
$this->output->writeln('app/config is not writable'); |
|
140
|
|
|
exit(self::EXIT_MISSING_PERMISSIONS); |
|
141
|
|
|
} |
|
142
|
|
|
} |
|
143
|
|
|
|
|
144
|
|
|
private function checkParameters() |
|
145
|
|
|
{ |
|
146
|
|
|
$parametersFile = 'app/config/parameters.yml'; |
|
147
|
|
|
if (!is_file($parametersFile)) { |
|
148
|
|
|
$this->output->writeln("Required configuration file $parametersFile not found"); |
|
149
|
|
|
exit(self::EXIT_PARAMETERS_NOT_FOUND); |
|
150
|
|
|
} |
|
151
|
|
|
} |
|
152
|
|
|
|
|
153
|
|
|
/** |
|
154
|
|
|
* @throws \Exception if an unexpected database error occurs |
|
155
|
|
|
*/ |
|
156
|
|
|
private function configuredDatabaseExists() |
|
157
|
|
|
{ |
|
158
|
|
|
try { |
|
159
|
|
|
$this->db->connect(); |
|
160
|
|
|
} catch (ConnectionException $e) { |
|
|
|
|
|
|
161
|
|
|
// @todo 1049 is MySQL's code for "database doesn't exist", refactor |
|
162
|
|
|
if ($e->getPrevious()->getCode() == 1049) { |
|
163
|
|
|
return false; |
|
164
|
|
|
} |
|
165
|
|
|
throw $e; |
|
166
|
|
|
} |
|
167
|
|
|
|
|
168
|
|
|
return true; |
|
169
|
|
|
} |
|
170
|
|
|
|
|
171
|
|
|
private function checkDatabase() |
|
172
|
|
|
{ |
|
173
|
|
|
try { |
|
174
|
|
|
if (!$this->configuredDatabaseExists()) { |
|
175
|
|
|
$this->output->writeln( |
|
176
|
|
|
sprintf( |
|
177
|
|
|
"The configured database '%s' does not exist", |
|
178
|
|
|
$this->db->getDatabase() |
|
179
|
|
|
) |
|
180
|
|
|
); |
|
181
|
|
|
exit(self::EXIT_DATABASE_NOT_FOUND_ERROR); |
|
182
|
|
|
} |
|
183
|
|
|
} catch (ConnectionException $e) { |
|
|
|
|
|
|
184
|
|
|
$this->output->writeln('An error occured connecting to the database:'); |
|
185
|
|
|
$this->output->writeln($e->getMessage()); |
|
186
|
|
|
$this->output->writeln('Please check the database configuration in parameters.yml'); |
|
187
|
|
|
exit(self::EXIT_GENERAL_DATABASE_ERROR); |
|
188
|
|
|
} |
|
189
|
|
|
} |
|
190
|
|
|
|
|
191
|
|
|
private function cacheClear(OutputInterface $output) |
|
192
|
|
|
{ |
|
193
|
|
|
if (!is_writable($this->cacheDir)) { |
|
194
|
|
|
throw new \RuntimeException(sprintf('Unable to write in the "%s" directory', $this->cacheDir)); |
|
195
|
|
|
} |
|
196
|
|
|
|
|
197
|
|
|
$output->writeln(sprintf('Clearing cache for directory <info>%s</info>', $this->cacheDir)); |
|
198
|
|
|
$oldCacheDir = $this->cacheDir . '_old'; |
|
199
|
|
|
|
|
200
|
|
|
if ($this->filesystem->exists($oldCacheDir)) { |
|
201
|
|
|
$this->filesystem->remove($oldCacheDir); |
|
202
|
|
|
} |
|
203
|
|
|
|
|
204
|
|
|
$this->cacheClearer->clear($this->cacheDir); |
|
205
|
|
|
|
|
206
|
|
|
$this->filesystem->rename($this->cacheDir, $oldCacheDir); |
|
207
|
|
|
$this->filesystem->remove($oldCacheDir); |
|
208
|
|
|
} |
|
209
|
|
|
|
|
210
|
|
|
/** |
|
211
|
|
|
* Calls indexing commands on search engines known to need that. |
|
212
|
|
|
* |
|
213
|
|
|
* @todo This should not be needed once/if the Installer starts using API in the future. |
|
214
|
|
|
* So temporary measure until it is not raw SQL based for the data itself (as opposed to the schema). |
|
215
|
|
|
* This is done after cache clearing to make sure no cached data from before sql import is used. |
|
216
|
|
|
* |
|
217
|
|
|
* IMPORTANT: This is done using a command because config has change, so container and all services are different. |
|
218
|
|
|
* |
|
219
|
|
|
* @param OutputInterface $output |
|
220
|
|
|
*/ |
|
221
|
|
|
private function indexData(OutputInterface $output) |
|
222
|
|
|
{ |
|
223
|
|
|
if ($this->searchEngine === 'solr') { |
|
224
|
|
|
$output->writeln('Solr search engine configured, executing command ezplatform:solr_create_index'); |
|
225
|
|
|
$this->executeCommand($output, 'ezplatform:solr_create_index'); |
|
226
|
|
|
} |
|
227
|
|
|
|
|
228
|
|
|
if ($this->searchEngine === 'elasticsearch') { |
|
229
|
|
|
$output->writeln('Elasticsearch search engine configured, executing command ezplatform:elasticsearch_create_index'); |
|
230
|
|
|
$this->executeCommand($output, 'ezplatform:elasticsearch_create_index'); |
|
231
|
|
|
} |
|
232
|
|
|
} |
|
233
|
|
|
|
|
234
|
|
|
/** |
|
235
|
|
|
* @param $type |
|
236
|
|
|
* |
|
237
|
|
|
* @return \EzSystems\PlatformInstallerBundle\Installer\Installer |
|
238
|
|
|
*/ |
|
239
|
|
|
private function getInstaller($type) |
|
240
|
|
|
{ |
|
241
|
|
|
if (!isset($this->installers[$type])) { |
|
242
|
|
|
return false; |
|
|
|
|
|
|
243
|
|
|
} |
|
244
|
|
|
|
|
245
|
|
|
return $this->installers[$type]; |
|
246
|
|
|
} |
|
247
|
|
|
|
|
248
|
|
|
/** |
|
249
|
|
|
* Executes a Symfony command in separate process. |
|
250
|
|
|
* |
|
251
|
|
|
* Typically usefull when configuration has changed, our you are outside of Symfony context (Composer commands). |
|
252
|
|
|
* |
|
253
|
|
|
* Based on {@see \Sensio\Bundle\DistributionBundle\Composer\ScriptHandler::executeCommand}. |
|
254
|
|
|
* |
|
255
|
|
|
* @param OutputInterface $output |
|
256
|
|
|
* @param string $cmd eZ Platform command to execute, like 'ezplatform:solr_create_index' |
|
257
|
|
|
* Escape any user provided arguments, like: 'assets:install '.escapeshellarg($webDir) |
|
258
|
|
|
* @param int $timeout |
|
259
|
|
|
*/ |
|
260
|
|
|
private function executeCommand(OutputInterface $output, $cmd, $timeout = 300) |
|
261
|
|
|
{ |
|
262
|
|
|
$phpFinder = new PhpExecutableFinder(); |
|
263
|
|
|
if (!$phpPath = $phpFinder->find(false)) { |
|
264
|
|
|
throw new \RuntimeException('The php executable could not be found, add it to your PATH environment variable and try again'); |
|
265
|
|
|
} |
|
266
|
|
|
|
|
267
|
|
|
// We don't know which php arguments where used so we gather some to be on the safe side |
|
268
|
|
|
$arguments = $phpFinder->findArguments(); |
|
269
|
|
|
if (false !== ($ini = php_ini_loaded_file())) { |
|
270
|
|
|
$arguments[] = '--php-ini=' . $ini; |
|
271
|
|
|
} |
|
272
|
|
|
|
|
273
|
|
|
// Pass memory_limit in case this was specified as php argument, if not it will most likely be same as $ini. |
|
274
|
|
|
if ($memoryLimit = ini_get('memory_limit')) { |
|
275
|
|
|
$arguments[] = '-d memory_limit=' . $memoryLimit; |
|
276
|
|
|
} |
|
277
|
|
|
|
|
278
|
|
|
$phpArgs = implode(' ', array_map('escapeshellarg', $arguments)); |
|
279
|
|
|
$php = escapeshellarg($phpPath) . ($phpArgs ? ' ' . $phpArgs : ''); |
|
280
|
|
|
|
|
281
|
|
|
// Make sure to pass along relevant global Symfony options to console command |
|
282
|
|
|
$console = escapeshellarg('app/console'); |
|
283
|
|
|
if ($output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL) { |
|
284
|
|
|
$console .= ' -' . str_repeat('v', $output->getVerbosity() - 1); |
|
285
|
|
|
} |
|
286
|
|
|
|
|
287
|
|
|
if ($output->isDecorated()) { |
|
288
|
|
|
$console .= ' --ansi'; |
|
289
|
|
|
} |
|
290
|
|
|
|
|
291
|
|
|
$console .= ' --env=' . escapeshellarg($this->environment); |
|
292
|
|
|
|
|
293
|
|
|
$process = new Process($php . ' ' . $console . ' ' . $cmd, null, null, null, $timeout); |
|
294
|
|
|
$process->run(function ($type, $buffer) use ($output) { $output->write($buffer, false); }); |
|
295
|
|
|
if (!$process->isSuccessful()) { |
|
296
|
|
|
throw new \RuntimeException(sprintf('An error occurred when executing the "%s" command.', escapeshellarg($cmd))); |
|
297
|
|
|
} |
|
298
|
|
|
} |
|
299
|
|
|
} |
|
300
|
|
|
|
Scrutinizer analyzes your
composer.json/composer.lockfile if available to determine the classes, and functions that are defined by your dependencies.It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.