Issues (224)

src/DockerFileGenerator.php (2 issues)

Labels
Severity
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 Composer\Semver\Semver;
18
use UnexpectedValueException;
19
use Webmozart\Assert\Assert;
20
use function array_column;
21
use function array_filter;
22
use function array_unique;
23
use function basename;
24
use function count;
25
use function implode;
26
use function sprintf;
27
use function strtr;
28
use const PHP_EOL;
29
30
/**
31
 * @private
32
 */
33
final class DockerFileGenerator
34
{
35
    private const FILE_TEMPLATE = <<<'Dockerfile'
36
        FROM php:__BASE_PHP_IMAGE_TOKEN__
37
38
        COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
39
        __REQUIRED_EXTENSIONS__
40
        COPY __PHAR_FILE_PATH_TOKEN__ /__PHAR_FILE_NAME_TOKEN__
41
42
        ENTRYPOINT ["/__PHAR_FILE_NAME_TOKEN__"]
43
44
        Dockerfile;
45
46
    private const PHP_DOCKER_IMAGES = [
47
        '8.3.0' => '8.3-cli-alpine',
48
        '8.2.0' => '8.2-cli-alpine',
49
        '8.1.0' => '8.1-cli-alpine',
50
        '8.0.0' => '8.0-cli-alpine',
51
        '7.4.0' => '7.4-cli-alpine',
52
        '7.3.0' => '7.3-cli-alpine',
53
        '7.2.0' => '7.2-cli-alpine',
54
        '7.1.0' => '7.1-cli-alpine',
55
        '7.0.0' => '7-cli-alpine',
56
        '*' => 'to-define-manually',
57
    ];
58
59
    private readonly string $image;
60
61
    /**
62
     * @var string[]
63
     */
64
    private readonly array $extensions;
65
66
    /**
67
     * Creates a new instance of the generator.
68
     *
69
     * @param array  $requirements List of requirements following the format defined by the RequirementChecker component
70
     * @param string $sourcePhar   source PHAR location; This PHAR is going to be copied over to the image so the path
71
     *                             should either be absolute or relative to the location of the Dockerfile
72
     */
73
    public static function createForRequirements(array $requirements, string $sourcePhar): self
74
    {
75
        return new self(
76
            self::retrievePhpImageName($requirements),
77
            self::retrievePhpExtensions($requirements),
78
            $sourcePhar,
79
        );
80
    }
81
82
    /**
83
     * @param string[] $extensions
84
     * @param string   $sourcePhar source PHAR location; This PHAR is going to be copied over to the image so the path
85
     *                             should either be absolute or relative to the location of the Dockerfile
86
     */
87
    public function __construct(
88
        string $image,
89
        array $extensions,
90
        private readonly string $sourcePhar,
91
    ) {
92
        Assert::inArray($image, self::PHP_DOCKER_IMAGES);
93
        Assert::allString($extensions);
94
95
        $this->image = $image;
0 ignored issues
show
The property image is declared read-only in KevinGH\Box\DockerFileGenerator.
Loading history...
96
        $this->extensions = $extensions;
0 ignored issues
show
The property extensions is declared read-only in KevinGH\Box\DockerFileGenerator.
Loading history...
97
    }
98
99
    public function generateStub(): string
100
    {
101
        $requiredExtensions = 0 === count($this->extensions)
102
            ? ''
103
            : sprintf(
104
                'RUN install-php-extensions %s%s',
105
                implode(' ', $this->extensions),
106
                PHP_EOL,
107
            );
108
109
        return strtr(
110
            self::FILE_TEMPLATE,
111
            [
112
                '__BASE_PHP_IMAGE_TOKEN__' => $this->image,
113
                '__PHAR_FILE_PATH_TOKEN__' => $this->sourcePhar,
114
                '__PHAR_FILE_NAME_TOKEN__' => basename($this->sourcePhar),
115
                '__REQUIRED_EXTENSIONS__' => $requiredExtensions,
116
            ],
117
        );
118
    }
119
120
    private static function retrievePhpImageName(array $requirements): string
121
    {
122
        $conditions = array_column(
123
            array_filter(
124
                $requirements,
125
                static fn (array $requirement): bool => 'php' === $requirement['type'],
126
            ),
127
            'condition',
128
        );
129
130
        foreach (self::PHP_DOCKER_IMAGES as $php => $image) {
131
            foreach ($conditions as $condition) {
132
                if ('*' === $php) {
133
                    return $image;
134
                }
135
136
                if (false === Semver::satisfies($php, $condition)) {
137
                    continue 2;
138
                }
139
            }
140
141
            return $image;
142
        }
143
144
        throw new UnexpectedValueException(
145
            sprintf(
146
                'Could not find a suitable Docker base image for the PHP constraint(s) "%s". Images available: "%s".',
147
                implode('", "', $conditions),
148
                implode('", "', self::PHP_DOCKER_IMAGES),
149
            ),
150
        );
151
    }
152
153
    /**
154
     * @return string[]
155
     */
156
    private static function retrievePhpExtensions(array $requirements): array
157
    {
158
        return array_unique(
159
            array_column(
160
                array_filter(
161
                    $requirements,
162
                    static fn (array $requirement): bool => 'extension' === $requirement['type'],
163
                ),
164
                'condition',
165
            ),
166
        );
167
    }
168
}
169