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\InputInterface; |
16
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
17
|
|
|
use Symfony\Component\Process\Process; |
18
|
|
|
use Symfony\Component\Process\PhpExecutableFinder; |
19
|
|
|
use Symfony\Component\Filesystem\Filesystem; |
20
|
|
|
use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface; |
21
|
|
|
|
22
|
|
|
class InstallPlatformCommand extends Command |
23
|
|
|
{ |
24
|
|
|
/** @var \Doctrine\DBAL\Connection */ |
25
|
|
|
private $db; |
26
|
|
|
|
27
|
|
|
/** @var \Symfony\Component\Console\Output\OutputInterface */ |
28
|
|
|
private $output; |
29
|
|
|
|
30
|
|
|
/** @var \Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface */ |
31
|
|
|
private $cacheClearer; |
32
|
|
|
|
33
|
|
|
/** @var \Symfony\Component\Filesystem\Filesystem */ |
34
|
|
|
private $filesystem; |
35
|
|
|
|
36
|
|
|
/** @var string */ |
37
|
|
|
private $cacheDir; |
38
|
|
|
|
39
|
|
|
/** @var string */ |
40
|
|
|
private $environment; |
41
|
|
|
|
42
|
|
|
/** @var string */ |
43
|
|
|
private $searchEngine; |
44
|
|
|
|
45
|
|
|
/** @var \EzSystems\PlatformInstallerBundle\Installer\Installer[] */ |
46
|
|
|
private $installers = array(); |
47
|
|
|
|
48
|
|
|
const EXIT_DATABASE_NOT_FOUND_ERROR = 3; |
49
|
|
|
const EXIT_GENERAL_DATABASE_ERROR = 4; |
50
|
|
|
const EXIT_PARAMETERS_NOT_FOUND = 5; |
51
|
|
|
const EXIT_UNKNOWN_INSTALL_TYPE = 6; |
52
|
|
|
const EXIT_MISSING_PERMISSIONS = 7; |
53
|
|
|
|
54
|
|
|
public function __construct( |
55
|
|
|
Connection $db, |
56
|
|
|
array $installers, |
57
|
|
|
CacheClearerInterface $cacheClearer, |
58
|
|
|
Filesystem $filesystem, |
59
|
|
|
$cacheDir, |
60
|
|
|
$environment, |
61
|
|
|
$searchEngine |
62
|
|
|
) { |
63
|
|
|
$this->db = $db; |
64
|
|
|
$this->installers = $installers; |
65
|
|
|
$this->cacheClearer = $cacheClearer; |
66
|
|
|
$this->filesystem = $filesystem; |
67
|
|
|
$this->cacheDir = $cacheDir; |
68
|
|
|
$this->environment = $environment; |
69
|
|
|
$this->searchEngine = $searchEngine; |
70
|
|
|
parent::__construct(); |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
protected function configure() |
74
|
|
|
{ |
75
|
|
|
$this->setName('ezplatform:install'); |
76
|
|
|
$this->addArgument( |
77
|
|
|
'type', |
78
|
|
|
InputArgument::REQUIRED, |
79
|
|
|
'The type of install. Available options: ' . implode(', ', array_keys($this->installers)) |
80
|
|
|
); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
protected function execute(InputInterface $input, OutputInterface $output) |
84
|
|
|
{ |
85
|
|
|
$this->output = $output; |
86
|
|
|
$this->checkPermissions(); |
87
|
|
|
$this->checkParameters(); |
88
|
|
|
$this->checkDatabase(); |
89
|
|
|
|
90
|
|
|
$type = $input->getArgument('type'); |
91
|
|
|
$installer = $this->getInstaller($type); |
92
|
|
|
if ($installer === false) { |
93
|
|
|
$output->writeln( |
94
|
|
|
"Unknown install type '$type', available options in currently installed eZ Platform package: " . |
95
|
|
|
implode(', ', array_keys($this->installers)) |
96
|
|
|
); |
97
|
|
|
exit(self::EXIT_UNKNOWN_INSTALL_TYPE); |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
$installer->setOutput($output); |
101
|
|
|
|
102
|
|
|
$installer->importSchema(); |
103
|
|
|
$installer->importData(); |
104
|
|
|
$installer->importBinaries(); |
105
|
|
|
$this->cacheClear($output); |
106
|
|
|
$this->indexData($output); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
private function checkPermissions() |
110
|
|
|
{ |
111
|
|
|
if (!is_writable('web') && !is_writable('web/var')) { |
112
|
|
|
$this->output->writeln('[web/ | web/var] is not writable'); |
113
|
|
|
exit(self::EXIT_MISSING_PERMISSIONS); |
114
|
|
|
} |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
private function checkParameters() |
118
|
|
|
{ |
119
|
|
|
$parametersFile = 'app/config/parameters.yml'; |
120
|
|
|
if (!is_file($parametersFile)) { |
121
|
|
|
$this->output->writeln("Required configuration file '$parametersFile' not found"); |
122
|
|
|
exit(self::EXIT_PARAMETERS_NOT_FOUND); |
123
|
|
|
} |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* @throws \Exception if an unexpected database error occurs |
128
|
|
|
*/ |
129
|
|
|
private function configuredDatabaseExists() |
130
|
|
|
{ |
131
|
|
|
try { |
132
|
|
|
$this->db->connect(); |
133
|
|
|
} catch (ConnectionException $e) { |
134
|
|
|
// @todo 1049 is MySQL's code for "database doesn't exist", refactor |
135
|
|
|
if ($e->getPrevious()->getCode() == 1049) { |
136
|
|
|
return false; |
137
|
|
|
} |
138
|
|
|
throw $e; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
return true; |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
private function checkDatabase() |
145
|
|
|
{ |
146
|
|
|
try { |
147
|
|
|
if (!$this->configuredDatabaseExists()) { |
148
|
|
|
$this->output->writeln( |
149
|
|
|
sprintf( |
150
|
|
|
"The configured database '%s' does not exist", |
151
|
|
|
$this->db->getDatabase() |
152
|
|
|
) |
153
|
|
|
); |
154
|
|
|
exit(self::EXIT_DATABASE_NOT_FOUND_ERROR); |
155
|
|
|
} |
156
|
|
|
} catch (ConnectionException $e) { |
157
|
|
|
$this->output->writeln('An error occurred connecting to the database:'); |
158
|
|
|
$this->output->writeln($e->getMessage()); |
159
|
|
|
$this->output->writeln("Please check the database configuration in 'app/config/parameters.yml'"); |
160
|
|
|
exit(self::EXIT_GENERAL_DATABASE_ERROR); |
161
|
|
|
} |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
private function cacheClear(OutputInterface $output) |
165
|
|
|
{ |
166
|
|
|
if (!is_writable($this->cacheDir)) { |
167
|
|
|
throw new \RuntimeException( |
168
|
|
|
sprintf( |
169
|
|
|
'Unable to write in the "%s" directory, check install doc on disk permissions before you continue.', |
170
|
|
|
$this->cacheDir |
171
|
|
|
) |
172
|
|
|
); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
$output->writeln(sprintf('Clearing cache for directory <info>%s</info>', $this->cacheDir)); |
176
|
|
|
$oldCacheDir = $this->cacheDir . '_old'; |
177
|
|
|
|
178
|
|
|
if ($this->filesystem->exists($oldCacheDir)) { |
179
|
|
|
$this->filesystem->remove($oldCacheDir); |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
$this->cacheClearer->clear($this->cacheDir); |
183
|
|
|
|
184
|
|
|
$this->filesystem->rename($this->cacheDir, $oldCacheDir); |
185
|
|
|
$this->filesystem->remove($oldCacheDir); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Calls indexing commands on search engines known to need that. |
190
|
|
|
* |
191
|
|
|
* @todo This should not be needed once/if the Installer starts using API in the future. |
192
|
|
|
* So temporary measure until it is not raw SQL based for the data itself (as opposed to the schema). |
193
|
|
|
* This is done after cache clearing to make sure no cached data from before sql import is used. |
194
|
|
|
* |
195
|
|
|
* IMPORTANT: This is done using a command because config has change, so container and all services are different. |
196
|
|
|
* |
197
|
|
|
* @param OutputInterface $output |
198
|
|
|
*/ |
199
|
|
|
private function indexData(OutputInterface $output) |
200
|
|
|
{ |
201
|
|
|
if (!in_array($this->searchEngine, ['solr', 'elasticsearch'])) { |
202
|
|
|
return; |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
$output->writeln( |
206
|
|
|
sprintf('%s search engine configured, executing command ezplatform:reindex', $this->searchEngine) |
207
|
|
|
); |
208
|
|
|
$this->executeCommand($output, 'ezplatform:reindex'); |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* @param $type |
213
|
|
|
* |
214
|
|
|
* @return \EzSystems\PlatformInstallerBundle\Installer\Installer |
215
|
|
|
*/ |
216
|
|
|
private function getInstaller($type) |
217
|
|
|
{ |
218
|
|
|
if (!isset($this->installers[$type])) { |
219
|
|
|
return false; |
|
|
|
|
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
return $this->installers[$type]; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* Executes a Symfony command in separate process. |
227
|
|
|
* |
228
|
|
|
* Typically useful when configuration has changed, or you are outside of Symfony context (Composer commands). |
229
|
|
|
* |
230
|
|
|
* Based on {@see \Sensio\Bundle\DistributionBundle\Composer\ScriptHandler::executeCommand}. |
231
|
|
|
* |
232
|
|
|
* @param OutputInterface $output |
233
|
|
|
* @param string $cmd eZ Platform command to execute, like 'ezplatform:solr_create_index' |
234
|
|
|
* Escape any user provided arguments, like: 'assets:install '.escapeshellarg($webDir) |
235
|
|
|
* @param int $timeout |
236
|
|
|
*/ |
237
|
|
|
private function executeCommand(OutputInterface $output, $cmd, $timeout = 300) |
238
|
|
|
{ |
239
|
|
|
$phpFinder = new PhpExecutableFinder(); |
240
|
|
|
if (!$phpPath = $phpFinder->find(false)) { |
241
|
|
|
throw new \RuntimeException('The php executable could not be found, add it to your PATH environment variable and try again'); |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
// We don't know which php arguments where used so we gather some to be on the safe side |
245
|
|
|
$arguments = $phpFinder->findArguments(); |
246
|
|
|
if (false !== ($ini = php_ini_loaded_file())) { |
247
|
|
|
$arguments[] = '--php-ini=' . $ini; |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
// Pass memory_limit in case this was specified as php argument, if not it will most likely be same as $ini. |
251
|
|
|
if ($memoryLimit = ini_get('memory_limit')) { |
252
|
|
|
$arguments[] = '-d memory_limit=' . $memoryLimit; |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
$phpArgs = implode(' ', array_map('escapeshellarg', $arguments)); |
256
|
|
|
$php = escapeshellarg($phpPath) . ($phpArgs ? ' ' . $phpArgs : ''); |
257
|
|
|
|
258
|
|
|
// Make sure to pass along relevant global Symfony options to console command |
259
|
|
|
$console = escapeshellarg('app/console'); |
260
|
|
|
if ($output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL) { |
261
|
|
|
$console .= ' -' . str_repeat('v', $output->getVerbosity() - 1); |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
if ($output->isDecorated()) { |
265
|
|
|
$console .= ' --ansi'; |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
$console .= ' --env=' . escapeshellarg($this->environment); |
269
|
|
|
|
270
|
|
|
$process = new Process($php . ' ' . $console . ' ' . $cmd, null, null, null, $timeout); |
271
|
|
|
$process->run(function ($type, $buffer) use ($output) { $output->write($buffer, false); }); |
272
|
|
|
if (!$process->isSuccessful()) { |
273
|
|
|
throw new \RuntimeException(sprintf('An error occurred when executing the "%s" command.', escapeshellarg($cmd))); |
274
|
|
|
} |
275
|
|
|
} |
276
|
|
|
} |
277
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.