Passed
Pull Request — master (#287)
by Théo
02:12
created

DockerFileGenerator::createForRequirements()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
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;
16
17
use Assert\Assertion;
18
use Composer\Semver\Semver;
19
use UnexpectedValueException;
20
21
/**
22
 * @private
23
 */
24
final class DockerFileGenerator
25
{
26
    private const FILE_TEMPLATE = <<<'Dockerfile'
27
FROM php:__BASE_PHP_IMAGE_TOKEN__
28
29
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\"";')
30
31
COPY __PHAR_FILE_PATH_TOKEN__ /__PHAR_FILE_NAME_TOKEN__
32
33
ENTRYPOINT ["/__PHAR_FILE_NAME_TOKEN__"]
34
35
Dockerfile;
36
37
    private const PHP_DOCKER_IMAGES = [
38
        '7.2.0' => '7.2-cli-alpine',
39
        '7.1.0' => '7.1-cli-alpine',
40
        '7.0.0' => '7-cli-alpine',
41
    ];
42
43
    private $image;
44
    private $extensions;
45
    private $sourcePhar;
46
47
    /**
48
     * Creates a new instance of the generator.
49
     *
50
     * @param array  $requirements List of requirements following the format defined by the RequirementChecker component
51
     * @param string $sourcePhar   source PHAR location; This PHAR is going to be copied over to the image so the path
52
     *                             should either be absolute or relative to the location of the Dockerfile
53
     */
54
    public static function createForRequirements(array $requirements, string $sourcePhar): self
55
    {
56
        return new static(
57
            self::retrievePhpImageName($requirements),
58
            self::retrievePhpExtensions($requirements),
59
            $sourcePhar
60
        );
61
    }
62
63
    /**
64
     * @param string[] $extensions
65
     * @param string   $sourcePhar source PHAR location; This PHAR is going to be copied over to the image so the path
66
     *                             should either be absolute or relative to the location of the Dockerfile
67
     */
68
    public function __construct(string $image, array $extensions, string $sourcePhar)
69
    {
70
        Assertion::inArray($image, self::PHP_DOCKER_IMAGES);
71
        Assertion::allString($extensions);
72
73
        $this->image = $image;
74
        $this->extensions = $extensions;
75
        $this->sourcePhar = $sourcePhar;
76
    }
77
78
    /**
79
     * @return string The stub
80
     */
81
    public function generate(): string
82
    {
83
        $contents = self::FILE_TEMPLATE;
84
85
        $contents = str_replace(
86
            '__BASE_PHP_IMAGE_TOKEN__',
87
            $this->image,
88
            $contents
89
        );
90
91
        $contents = str_replace(
92
            '__PHP_EXTENSIONS_TOKEN__',
93
            [] === $this->extensions
94
                ? '[]'
95
                : sprintf(
96
                    '["%s"]',
97
                    implode(
98
                        '", "',
99
                        $this->extensions
100
                    )
101
                ),
102
            $contents
103
        );
104
105
        $contents = str_replace(
106
            '__PHAR_FILE_PATH_TOKEN__',
107
            $this->sourcePhar,
108
            $contents
109
        );
110
111
        $contents = str_replace(
112
            '__PHAR_FILE_NAME_TOKEN__',
113
            basename($this->sourcePhar),
114
            $contents
115
        );
116
117
        return $contents;
118
    }
119
120
    private static function retrievePhpImageName(array $requirements): string
121
    {
122
        $conditions = array_column(
123
            array_filter(
124
                $requirements,
125
                function (array $requirement): bool {
126
                    return 'php' === $requirement['type'];
127
                }
128
            ),
129
            'condition'
130
        );
131
132
        foreach (self::PHP_DOCKER_IMAGES as $php => $image) {
133
            foreach ($conditions as $condition) {
134
                if (false === Semver::satisfies($php, $condition)) {
135
                    continue 2;
136
                }
137
            }
138
139
            return $image;
140
        }
141
142
        throw new UnexpectedValueException(
143
            sprintf(
144
                'Could not find a suitable Docker base image for the PHP constraint(s) "%s". Images available: "%s"',
145
                implode('", "', $conditions),
146
                implode('", "', array_values(self::PHP_DOCKER_IMAGES))
147
            )
148
        );
149
    }
150
151
    /**
152
     * @return string[]
153
     */
154
    private static function retrievePhpExtensions(array $requirements): array
155
    {
156
        return array_column(
157
            array_filter(
158
                $requirements,
159
                function (array $requirement): bool {
160
                    return 'extension' === $requirement['type'];
161
                }
162
            ),
163
            'condition'
164
        );
165
    }
166
}
167