Passed
Pull Request — master (#287)
by Théo
01:57
created

GenerateDockerFile::execute()   B

Complexity

Conditions 5
Paths 88

Size

Total Lines 70
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 41
dl 0
loc 70
rs 8.9528
c 0
b 0
f 0
cc 5
nc 88
nop 2

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the box project.
7
 *
8
 * (c) Kevin Herrera <[email protected]>
9
 *     Théo Fidry <[email protected]>
10
 *
11
 * This source file is subject to the MIT license that is bundled
12
 * with this source code in the file LICENSE.
13
 */
14
15
namespace KevinGH\Box\Console\Command;
16
17
use Assert\Assertion;
18
use Composer\Semver\Semver;
19
use Symfony\Component\Console\Command\Command;
20
use Symfony\Component\Console\Input\InputArgument;
21
use Symfony\Component\Console\Input\InputInterface;
22
use Symfony\Component\Console\Output\OutputInterface;
23
use Symfony\Component\Console\Style\SymfonyStyle;
24
use UnexpectedValueException;
25
use function array_column;
26
use function array_filter;
27
use function array_values;
28
use function basename;
29
use function file_exists;
30
use function getcwd;
31
use function implode;
32
use function KevinGH\Box\FileSystem\dump_file;
33
use function KevinGH\Box\FileSystem\make_path_relative;
34
use function KevinGH\Box\FileSystem\remove;
35
use function realpath;
36
use function sprintf;
37
use function str_replace;
38
39
/**
40
 * @private
41
 */
42
final class GenerateDockerFile extends Command
43
{
44
    use CreateTemporaryPharFile;
45
46
    private const PHAR_ARG = 'phar';
47
    private const VERBOSITY_LEVEL = OutputInterface::VERBOSITY_VERBOSE;
48
49
    private const DOCKER_FILE_TEMPLATE = <<<'Dockerfile'
50
FROM php:__BASE_PHP_IMAGE_TOKEN__
51
52
RUN $(php -r '$extensionInstalled = array_map("strtolower", \get_loaded_extensions(false));$requiredExtensions = __PHP_EXTENSIONS_TOKEN__;$extensionsToInstall = array_diff($requiredExtensions, $extensionInstalled);if ([] !== $extensionsToInstall) {echo \sprintf("docker-php-ext-install %s", implode(" ", $extensionsToInstall));}echo "echo \"No extensions\"";')
53
54
COPY __PHAR_FILE_PATH_TOKEN__ /__PHAR_FILE_NAME_TOKEN__
55
56
ENTRYPOINT ["__PHAR_FILE_NAME_TOKEN__"]
57
58
Dockerfile;
59
60
    private const PHP_DOCKER_IMAGES = [
61
        '7.2.0' => '7.2-cli-alpine',
62
        '7.1.0' => '7.1-cli-alpine',
63
        '7.0.0' => '7-cli-alpine',
64
    ];
65
66
    /**
67
     * {@inheritdoc}
68
     */
69
    protected function configure(): void
70
    {
71
        $this->setName('generate:docker-file');
72
        $this->setDescription('Generates a Dockerfile for the given PHAR');
73
        $this->addArgument(
74
            self::PHAR_ARG,
75
            InputArgument::REQUIRED,
76
            'The PHAR file'
77
        );
78
    }
79
80
    /**
81
     * {@inheritdoc}
82
     */
83
    protected function execute(InputInterface $input, OutputInterface $output): int
84
    {
85
        $io = new SymfonyStyle($input, $output);
86
87
        $pharPath = $input->getArgument(self::PHAR_ARG);
88
89
        Assertion::file($pharPath);
90
91
        $pharPath = false !== realpath($pharPath) ? realpath($pharPath) : $pharPath;
92
93
        $io->writeln(
94
            sprintf(
95
                'Generating a Dockerfile for the PHAR "<comment>%s</comment>"...',
96
                $pharPath
97
            ),
98
            self::VERBOSITY_LEVEL
99
        );
100
101
        $tmpPharPath = $this->createTemporaryPhar($pharPath);
102
103
        $requirementsPhar = 'phar://'.$tmpPharPath.'/.box/.requirements.php';
104
105
        $dockerFileContents = self::DOCKER_FILE_TEMPLATE;
106
107
        try {
108
            if (false === file_exists($requirementsPhar)) {
109
                $io->error(
110
                    'Cannot retrieve the requirements for the PHAR. Make sure the PHAR has been built with Box and the '
111
                    .'requirement checker enabled.'
112
                );
113
            }
114
115
            $requirements = include $requirementsPhar;
116
117
            $dockerFileContents = str_replace(
118
                '__BASE_PHP_IMAGE_TOKEN__',
119
                $this->retrievePhpImageName($requirements),
120
                $dockerFileContents
121
            );
122
123
            $dockerFileContents = str_replace(
124
                '__PHP_EXTENSIONS_TOKEN__',
125
                $this->retrievePhpExtensions($requirements),
126
                $dockerFileContents
127
            );
128
129
            $dockerFileContents = str_replace(
130
                '__PHAR_FILE_PATH_TOKEN__',
131
                make_path_relative($pharPath, getcwd()),
132
                $dockerFileContents
133
            );
134
135
            $dockerFileContents = str_replace(
136
                '__PHAR_FILE_NAME_TOKEN__',
137
                basename($pharPath),
138
                $dockerFileContents
139
            );
140
141
            dump_file('Dockerfile', $dockerFileContents);
142
143
            if ($io->getVerbosity() >= self::VERBOSITY_LEVEL) {
144
                $io->success('Done');
145
            }
146
        } finally {
147
            if ($tmpPharPath !== $pharPath) {
148
                remove($tmpPharPath);
149
            }
150
        }
151
152
        return 0;
153
    }
154
155
    private function retrievePhpImageName(array $requirements): string
156
    {
157
        $conditions = array_column(
158
            array_filter(
159
                $requirements,
160
                function (array $requirement): bool {
161
                    return 'php' === $requirement['type'];
162
                }
163
            ),
164
            'condition'
165
        );
166
167
        foreach (self::PHP_DOCKER_IMAGES as $php => $image) {
168
            foreach ($conditions as $condition) {
169
                if (false === Semver::satisfies($php, $condition)) {
170
                    continue 2;
171
                }
172
            }
173
174
            return $image;
175
        }
176
177
        throw new UnexpectedValueException(
178
            sprintf(
179
                'Could not find a suitable Docker base image for the PHP constraint(s) "%s". Images available: "%s"',
180
                implode('", "', $conditions),
181
                implode('", "', array_values(self::PHP_DOCKER_IMAGES))
182
            )
183
        );
184
    }
185
186
    private function retrievePhpExtensions(array $requirements): string
187
    {
188
        $extensions = array_column(
189
            array_filter(
190
                $requirements,
191
                function (array $requirement): bool {
192
                    return 'extension' === $requirement['type'];
193
                }
194
            ),
195
            'condition'
196
        );
197
198
        if ([] === $extensions) {
199
            return '[]';
200
        }
201
202
        return sprintf(
203
            '["%s"]',
204
            implode(
205
                '", "',
206
                $extensions
207
            )
208
        );
209
    }
210
}
211