Completed
Push — master ( 50b2d8...27d9a4 )
by David
14s queued 10s
created

AddEventCommand   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 307
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 36
dl 0
loc 307
rs 8.8
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A getEventName() 0 3 1
F executeEvent() 0 266 30
B getAvailableVersionParts() 0 28 5
1
<?php
2
3
namespace TheAentMachine\AentPhp\Command;
4
5
use Symfony\Component\Console\Question\ChoiceQuestion;
6
use Symfony\Component\Console\Question\Question;
7
use TheAentMachine\AentPhp\EventEnum;
8
use TheAentMachine\CommonEvents;
9
use TheAentMachine\EventCommand;
10
use TheAentMachine\Pheromone;
11
use TheAentMachine\Registry\RegistryClient;
12
use TheAentMachine\Service\Service;
13
14
class AddEventCommand extends EventCommand
15
{
16
    protected function getEventName(): string
17
    {
18
        return EventEnum::ADD;
19
    }
20
21
    protected function executeEvent(?string $payload): void
22
    {
23
        $helper = $this->getHelper('question');
24
25
        $commentEvents = new CommonEvents();
26
27
        $commentEvents->canDispatchServiceOrFail($helper, $this->input, $this->output);
28
29
        $service = new Service();
30
31
        /************************ Service name **********************/
32
        $question = new Question('Please enter the name of the service [app]: ', 'app');
33
        $question->setValidator(function (string $value) {
34
            $value = trim($value);
35
            if (!\preg_match('/^[a-zA-Z0-9_.-]+$/', $value)) {
36
                throw new \InvalidArgumentException('Invalid service name "'.$value.'". Service names can contain alphanumeric characters, and "_", ".", "-".');
37
            }
38
39
            return $value;
40
        });
41
42
        $serviceName = $helper->ask($this->input, $this->output, $question);
43
        $this->output->writeln("<info>You are about to create a '$serviceName' PHP container</info>");
44
        $service->setServiceName($serviceName);
45
46
47
        /************************ PHP Version **********************/
48
        [
49
            'phpVersions' => $phpVersions,
50
            'variants' => $variants,
51
            'nodeVersions' => $nodeVersions,
52
        ] = $this->getAvailableVersionParts();
53
54
        $question = new ChoiceQuestion(
55
            'Select your PHP version :',
56
            $phpVersions,
57
            0
58
        );
59
        $phpVersion = $helper->ask($this->input, $this->output, $question);
60
        $this->output->writeln("<info>You are about to install PHP $phpVersion</info>");
61
        $this->output->writeln('');
62
        $this->output->writeln('');
63
64
65
        $question = new ChoiceQuestion(
66
            'Select your variant :',
67
            $variants,
68
            0
69
        );
70
        $variant = $helper->ask($this->input, $this->output, $question);
71
        $this->output->writeln("<info>You selected the $variant variant</info>");
72
        $this->output->writeln('');
73
        $this->output->writeln('');
74
75
        $question = new ChoiceQuestion(
76
            'Do you want to install NodeJS :',
77
            array_merge(['No'], $nodeVersions),
78
            0
79
        );
80
        $node = $helper->ask($this->input, $this->output, $question);
81
        if ($node !== 'No') {
82
            $this->output->writeln("<info>The image will also contain $node</info>");
83
        } else {
84
            $this->output->writeln("<info>The image will not contain NodeJS</info>");
85
        }
86
        $this->output->writeln('');
87
        $this->output->writeln('');
88
89
        if ($node === 'No') {
90
            $node = '';
91
        } else {
92
            $node = '-'.$node;
93
        }
94
95
        $service->setImage("thecodingmachine/php:$phpVersion-v1-$variant$node");
96
97
98
99
        /************************ Root application path **********************/
100
        do {
101
            $this->output->writeln('Now, we need to find the root of your web application. This is typically the directory that contains your composer.json file.');
102
            $question = new Question('What is your PHP application root directory? (relative to the project root directory): ', '');
103
            $appDirectory = $helper->ask($this->input, $this->output, $question);
104
105
            $appDirectory = trim($appDirectory, '/') ?: '.';
106
            $rootDir = Pheromone::getContainerProjectDirectory();
107
108
            $fullDir = $rootDir.'/'.$appDirectory;
109
            if (!is_dir($fullDir)) {
110
                $this->output->writeln('<error>Could not find directory '.Pheromone::getHostProjectDirectory().'/'.$appDirectory.'</error>');
111
                $appDirectory = null;
112
            }
113
        } while ($appDirectory === null);
114
        $this->output->writeln('<info>Your root PHP application directory is '.Pheromone::getHostProjectDirectory().'/'.$appDirectory.'</info>');
115
        $this->output->writeln('');
116
        $this->output->writeln('');
117
118
        $service->addBindVolume('/var/www/html', $appDirectory);
119
120
        /************************ Web application path **********************/
121
        $webDirectory = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $webDirectory is dead and can be removed.
Loading history...
122
        if ($variant === 'apache') {
123
            $question = new ChoiceQuestion(
124
                'Do you have a public web folder that is not the root of your application? [Yes] ',
125
                array('Yes', 'No'),
126
                0
127
            );
128
            $answer = $helper->ask($this->input, $this->output, $question);
129
            if ($answer === 'Yes') {
130
                do {
131
                    $question = new Question('What is your PHP application web directory? (relative to the PHP project directory): ', '');
132
                    $webDirectory = $helper->ask($this->input, $this->output, $question);
133
134
                    $webDirectory = trim($webDirectory, '/') ?: '.';
135
                    $rootDir = Pheromone::getContainerProjectDirectory();
136
137
                    $fullDir = $rootDir.'/'.$appDirectory.'/'.$webDirectory;
138
                    if (!is_dir($fullDir)) {
139
                        $this->output->writeln('<error>Could not find directory '.Pheromone::getHostProjectDirectory().'/'.$appDirectory.'/'.$webDirectory.'</error>');
140
                        $webDirectory = null;
141
                    }
142
                } while ($webDirectory === null);
143
144
                $service->addImageEnvVariable('APACHE_DOCUMENT_ROOT', $webDirectory);
145
                $this->output->writeln('<info>Your web directory is '.Pheromone::getHostProjectDirectory().'/'.$appDirectory.'/'.$webDirectory.'</info>');
146
                $this->output->writeln('');
147
                $this->output->writeln('');
148
            }
149
        }
150
151
        /************************ Upload path **********************/
152
        $this->output->writeln('Now, we need to know if there are directories you want to store <info>out of the container</info>.');
153
        $this->output->writeln('When a container is removed, anything in it is lost. If your application is letting users upload files, or if it generates files, it might be important to <comment>store those files out of the container</comment>.');
154
        $this->output->writeln('If you want to mount such a directory out of the container, please specify the directory path below. Path must be relative to the PHP application root directory.');
155
        $this->output->writeln('');
156
157
        $uploadDirs = [];
158
        do {
159
            $question = new Question('Please input directory (for instance for file uploads) that you want to mount out of the container? (keep empty to ignore) ', '');
160
            $uploadDirectory = $helper->ask($this->input, $this->output, $question);
161
162
            $uploadDirectory = trim($uploadDirectory, '/');
163
            $rootDir = Pheromone::getContainerProjectDirectory();
164
165
            if ($uploadDirectory !== '') {
166
                $fullDir = $rootDir.'/'.$appDirectory.'/'.$uploadDirectory;
167
                if (!is_dir($fullDir)) {
168
                    $this->output->writeln('<error>Could not find directory '.Pheromone::getHostProjectDirectory().'/'.$appDirectory.'/'.$uploadDirectory.'</error>');
169
                    $uploadDirectory = null;
170
                } else {
171
                    $uploadDirs[] = $uploadDirectory;
172
                    $this->output->writeln('<info>Directory '.Pheromone::getHostProjectDirectory().'/'.$appDirectory.'/'.$uploadDirectory.' will be stored out of the container</info>');
173
174
                    $question = new Question('What name should we use for this volume? ', '');
175
                    $question->setValidator(function (string $value) {
176
                        $value = trim($value);
177
                        if (!\preg_match('/^[a-zA-Z0-9_.-]+$/', $value)) {
178
                            throw new \InvalidArgumentException('Invalid volume name "'.$value.'". Volume names can contain alphanumeric characters, and "_", ".", "-".');
179
                        }
180
181
                        return $value;
182
                    });
183
                    $volumeName = $helper->ask($this->input, $this->output, $question);
184
185
                    $service->addNamedVolume($volumeName, $appDirectory.'/'.$uploadDirectory);
186
                }
187
            }
188
        } while ($uploadDirectory !== '');
189
        $this->output->writeln('');
190
        $this->output->writeln('');
191
192
        $availableExtensions = ['amqp', 'ast', 'bcmath', 'bz2', 'calendar', 'dba', 'enchant', 'ev', 'event', 'exif',
193
            'gd', 'gettext', 'gmp', 'igbinary', 'imap', 'intl', 'ldap', 'mcrypt', 'memcached', 'mongodb', 'pcntl',
194
            'pdo_dblib', 'pdo_pgsql', 'pgsql', 'pspell', 'shmop', 'snmp', 'sockets', 'sysvmsg', 'sysvsem', 'sysvshm',
195
            'tidy', 'wddx', 'weakref', 'xdebug', 'xmlrpc', 'xsl', 'yaml'];
196
197
        $this->output->writeln('By default, the following extensions are enabled:');
198
        $this->output->writeln('<info>apcu mysqli opcache pdo pdo_mysql redis zip soap mbstring ftp mysqlnd</info>');
199
        $this->output->writeln('You can select more extensions below:');
200
        $this->output->writeln('<info>'.\implode(' ', $availableExtensions).'</info>');
201
202
        /************************ Extensions **********************/
203
        $extensions = [];
204
        do {
205
            $question = new Question('Please enter the name of an additional extension you want to install (keep empty to skip): ', '');
206
            $question->setAutocompleterValues($availableExtensions);
207
            $question->setValidator(function (string $value) use ($availableExtensions) {
208
                if (trim($value) !== '' && !\in_array($value, $availableExtensions)) {
209
                    throw new \InvalidArgumentException('Unknown extension '.$value);
210
                }
211
212
                return trim($value);
213
            });
214
215
            $extension = $helper->ask($this->input, $this->output, $question);
216
217
            if ($extension !== '') {
218
                $service->addImageEnvVariable('PHP_EXTENSION_'.\strtoupper($extension), '1');
219
                $extensions[] = $extension;
220
            }
221
        } while ($extension !== '');
222
        $this->output->writeln('<info>Enabled extensions: apcu mysqli opcache pdo pdo_mysql redis zip soap mbstring ftp mysqlnd '.\implode(' ', $extensions).'</info>');
223
        $this->output->writeln('');
224
        $this->output->writeln('');
225
226
227
        /************************ php.ini settings **********************/
228
        $this->output->writeln("Now, let's customize some settings of <info>php.ini</info>.");
229
        $question = new Question('Please specify the PHP <info>memory limit</info> (keep empty to stay with the default 128M): ', '');
230
        $question->setValidator(function (string $value) {
231
            if (trim($value) !== '' && !\preg_match('/^[0-9]+([MGK])?$/i', $value)) {
232
                throw new \InvalidArgumentException('Invalid value: '.$value);
233
            }
234
235
            return trim($value);
236
        });
237
        $memoryLimit = $helper->ask($this->input, $this->output, $question);
238
        if ($memoryLimit !== '') {
239
            $this->output->writeln("<info>Memory limit: $memoryLimit</info>");
240
            $service->addImageEnvVariable('PHP_INI_MEMORY_LIMIT', $memoryLimit);
241
        }
242
243
        $question = new Question('Please specify the <info>maximum file size for uploaded files</info> (keep empty to stay with the default 2M): ', '');
244
        $question->setValidator(function (string $value) {
245
            if (trim($value) !== '' && !\preg_match('/^[0-9]+([MGK])?$/i', $value)) {
246
                throw new \InvalidArgumentException('Invalid value: '.$value);
247
            }
248
249
            return $value;
250
        });
251
        $uploadMaxFileSize = $helper->ask($this->input, $this->output, $question);
252
        if ($uploadMaxFileSize !== '') {
253
            $this->output->writeln("<info>Upload maximum file size: $uploadMaxFileSize</info>");
254
            $service->addImageEnvVariable('PHP_INI_UPLOAD_MAX_FILESIZE', $uploadMaxFileSize);
255
            $service->addImageEnvVariable('PHP_INI_POST_MAX_SIZE', $uploadMaxFileSize);
256
        }
257
258
        $this->output->writeln('');
259
        $this->output->writeln('');
260
        $this->output->writeln('Does your service depends on another service to start? For instance a "mysql" instance?');
261
        $depends = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $depends is dead and can be removed.
Loading history...
262
        do {
263
            $question = new Question('Please input a service name your application depends on (keep empty to skip) : ', '');
264
265
            $depend = $helper->ask($this->input, $this->output, $question);
266
267
            if ($depend !== '') {
268
                $service->addDependsOn($depend);
269
                $this->output->writeln('<info>Added dependency: '.$depend.'</info>');
270
            }
271
        } while ($depend !== '');
272
        $this->output->writeln('');
273
        $this->output->writeln('');
274
275
276
        // TODO: propose to run composer install on startup?
277
278
        if ($variant === 'apache') {
279
            $service->addInternalPort(80);
280
        }
281
282
        $commentEvents->dispatchService($service, $helper, $this->input, $this->output);
283
284
        // Now, let's configure the reverse proxy
285
        if ($variant === 'apache') {
286
            $commentEvents->dispatchNewVirtualHost($helper, $this->input, $this->output, $serviceName);
287
        }
288
    }
289
290
    /**
291
     * @return array[] An array with 3 keys: phpVersions, variants and nodeVersions
292
     */
293
    private function getAvailableVersionParts() : array
294
    {
295
        $registryClient = new RegistryClient();
296
        $tags = $registryClient->getImageTagsOnDockerHub('thecodingmachine/php');
297
298
        $phpVersions = [];
299
        $variants = [];
300
        $nodeVersions = [];
301
302
        foreach ($tags as $tag) {
303
            $parts = \explode('-', $tag);
304
            if (count($parts) < 3) {
305
                continue;
306
            }
307
            if ($parts[1] !== 'v1') {
308
                continue;
309
            }
310
            $phpVersions[$parts[0]] = true;
311
            $variants[$parts[2]] = true;
312
            if (isset($parts[3])) {
313
                $nodeVersions[$parts[3]] = true;
314
            }
315
        }
316
317
        return [
318
            'phpVersions' => \array_keys($phpVersions),
319
            'variants' => \array_keys($variants),
320
            'nodeVersions' => \array_keys($nodeVersions),
321
        ];
322
    }
323
}
324