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

generateExtensionCheckStatement()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 1
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\RequirementChecker;
16
17
use function array_diff_key;
18
use function array_key_exists;
19
use function sprintf;
20
use function substr;
21
22
/**
23
 * Collect the list of requirements for running the application.
24
 *
25
 * @private
26
 */
27
final class AppRequirementsFactory
28
{
29
    private const SELF_PACKAGE = '__APPLICATION__';
30
31
    /**
32
     * @param array $composerLockDecodedContents Decoded JSON contents of the `composer.lock` file
33
     *
34
     * @return array Serialized configured requirements
35
     */
36
    public static function create(array $composerLockDecodedContents): array
37
    {
38
        return self::configureExtensionRequirements(
39
            self::configurePhpVersionRequirements([], $composerLockDecodedContents),
40
            $composerLockDecodedContents
41
        );
42
    }
43
44
    private static function configurePhpVersionRequirements(array $requirements, array $composerLockContents): array
45
    {
46
        if (isset($composerLockContents['platform']['php'])) {
47
            $requiredPhpVersion = $composerLockContents['platform']['php'];
48
49
            $requirements[] = [
50
                self::generatePhpCheckStatement((string) $requiredPhpVersion),
51
                sprintf(
52
                    'The application requires the version "%s" or greater.',
53
                    $requiredPhpVersion
54
                ),
55
                sprintf(
56
                    'The application requires the version "%s" or greater.',
57
                    $requiredPhpVersion
58
                ),
59
            ];
60
61
            return $requirements; // No need to check the packages requirements: the application platform config is the authority here
62
        }
63
64
        $packages = $composerLockContents['packages'] ?? [];
65
66
        foreach ($packages as $packageInfo) {
67
            $requiredPhpVersion = $packageInfo['require']['php'] ?? null;
68
69
            if (null === $requiredPhpVersion) {
70
                continue;
71
            }
72
73
            $requirements[] = [
74
                self::generatePhpCheckStatement((string) $requiredPhpVersion),
75
                sprintf(
76
                    'The package "%s" requires the version "%s" or greater.',
77
                    $packageInfo['name'],
78
                    $requiredPhpVersion
79
                ),
80
                sprintf(
81
                    'The package "%s" requires the version "%s" or greater.',
82
                    $packageInfo['name'],
83
                    $requiredPhpVersion
84
                ),
85
            ];
86
        }
87
88
        return $requirements;
89
    }
90
91
    private static function configureExtensionRequirements(array $requirements, array $composerLockContents): array
92
    {
93
        $extensionRequirements = self::collectExtensionRequirements($composerLockContents);
94
95
        foreach ($extensionRequirements as $extension => $packages) {
96
            foreach ($packages as $package) {
97
                if (self::SELF_PACKAGE === $package) {
98
                    $message = sprintf(
99
                        'The application requires the extension "%s". Enable it or install a polyfill.',
100
                        $extension
101
                    );
102
                    $helpMessage = sprintf(
103
                        'The application requires the extension "%s".',
104
                        $extension
105
                    );
106
                } else {
107
                    $message = sprintf(
108
                        'The package "%s" requires the extension "%s". Enable it or install a polyfill.',
109
                        $package,
110
                        $extension
111
                    );
112
                    $helpMessage = sprintf(
113
                        'The package "%s" requires the extension "%s".',
114
                        $package,
115
                        $extension
116
                    );
117
                }
118
119
                $requirements[] = [
120
                    self::generateExtensionCheckStatement($extension),
121
                    $message,
122
                    $helpMessage,
123
                ];
124
            }
125
        }
126
127
        return $requirements;
128
    }
129
130
    /**
131
     * Collects the extension required. It also accounts for the polyfills, i.e. if the polyfill `symfony/polyfill-mbstring` is provided
132
     * then the extension `ext-mbstring` will not be required.
133
     *
134
     * @param array $composerLockContents
135
     *
136
     * @return array Associative array containing the list of extensions required
137
     */
138
    private static function collectExtensionRequirements(array $composerLockContents): array
139
    {
140
        $requirements = [];
141
        $polyfills = [];
142
143
        $platform = $composerLockContents['platform'] ?? [];
144
145
        foreach ($platform as $package => $constraint) {
146
            if (preg_match('/^ext-(?<extension>.+)$/', $package, $matches)) {
147
                $extension = $matches['extension'];
148
149
                $requirements[$extension] = [self::SELF_PACKAGE];
150
            }
151
        }
152
153
        $packages = $composerLockContents['packages'] ?? [];
154
155
        foreach ($packages as $packageInfo) {
156
            $packageRequire = $packageInfo['require'] ?? [];
157
158
            if (1 === preg_match('/symfony\/polyfill-(?<extension>.+)/', $packageInfo['name'], $matches)) {
159
                $extension = $matches['extension'];
160
161
                if ('php' !== substr($extension, 0, 3)) {
162
                    $polyfills[$extension] = true;
163
                }
164
            }
165
166
            foreach ($packageRequire as $package => $constraint) {
167
                if (1 === preg_match('/^ext-(?<extension>.+)$/', $package, $matches)) {
168
                    $extension = $matches['extension'];
169
170
                    if (false === array_key_exists($extension, $requirements)) {
171
                        $requirements[$extension] = [];
172
                    }
173
174
                    $requirements[$extension][] = $packageInfo['name'];
175
                }
176
            }
177
        }
178
179
        return array_diff_key($requirements, $polyfills);
180
    }
181
182
    private static function generatePhpCheckStatement(string $requiredPhpVersion): string
183
    {
184
        return <<<PHP
185
require_once __DIR__.'/../vendor/composer/semver/src/Semver.php';
186
require_once __DIR__.'/../vendor/composer/semver/src/VersionParser.php';
187
require_once __DIR__.'/../vendor/composer/semver/src/Constraint/ConstraintInterface.php';
188
require_once __DIR__.'/../vendor/composer/semver/src/Constraint/EmptyConstraint.php';
189
require_once __DIR__.'/../vendor/composer/semver/src/Constraint/MultiConstraint.php';
190
require_once __DIR__.'/../vendor/composer/semver/src/Constraint/Constraint.php';
191
192
return \Composer\Semver\Semver::satisfies(
193
    sprintf('%d.%d.%d', \PHP_MAJOR_VERSION, \PHP_MINOR_VERSION, \PHP_RELEASE_VERSION),
194
    '$requiredPhpVersion'
195
);
196
197
PHP;
198
    }
199
200
    private static function generateExtensionCheckStatement(string $extension): string
201
    {
202
        return "return \\extension_loaded('$extension');";
203
    }
204
}
205