xref: /web-bugs/src/Utils/Versions/Generator.php (revision 3f831426)
1<?php
2
3namespace App\Utils\Versions;
4
5use App\Utils\Cache;
6
7/**
8 * Service for retrieving a list of valid PHP versions when reporting bugs. PHP
9 * versions have format MAJOR.MINOR.MICRO{TYPE} where TYPE is one of alpha,
10 * beta, RC, or dev.
11 *
12 * Stable releases are pulled from the https://php.net. The RC and dev versions
13 * are pulled from the https://qa.php.net.
14 *
15 * To add a new PHP version add it to:
16 * https://github.com/php/web-qa/blob/master/include/release-qa.php
17 *
18 * The versions are weighted by the following criteria:
19 * - major+minor version desc (7>5.4>5.3>master)
20 * - Between minor versions ordering is done by the micro version if available.
21 *   First the QA releases: alpha/beta/rc, then stable, then nightly versions
22 *   (Git, snaps). Snaps are more or less Windows snapshot builds.
23 *
24 * The result is cached for 1 hour into a temporary file.
25 */
26class Generator
27{
28    /**
29     * PHP API pages client.
30     *
31     * @var Client
32     */
33    private $client;
34
35    /**
36     * Cache service for storing fetched versions.
37     *
38     * @var Cache
39     */
40    private $cache;
41
42    /**
43     * Time after cache file is considered expired in seconds.
44     */
45    private const TTL = 3600;
46
47    /**
48     * Additional versions appended to the list of generated versions.
49     */
50    private const APPENDICES = [
51        'Next Major Version',
52        'Next Minor Version',
53        'Irrelevant',
54    ];
55
56    /**
57     * Class constructor.
58     */
59    public function __construct(Client $client, Cache $cache)
60    {
61        $this->client = $client;
62        $this->cache = $cache;
63    }
64
65    /**
66     * Get a list of valid PHP versions. Versions are cached for efficiency.
67     */
68    public function getVersions(): array
69    {
70        if (!$this->cache->has('versions')) {
71            $this->cache->set('versions', $this->generateVersions(), self::TTL);
72        }
73
74        return $this->cache->get('versions');
75    }
76
77    /**
78     * Return fetched and processed versions.
79     */
80    private function generateVersions(): array
81    {
82        $versions = array_merge($this->getDevVersions(), $this->getStableVersions());
83        rsort($versions);
84
85        // Get minor branches (PHP 7.2, PHP 7.3, etc)
86        $branches = [];
87        foreach ($versions as $version) {
88            $parts = $this->parseVersion($version);
89            $branch = $parts['major'].'.'.$parts['minor'];
90            $branches[$branch] = $branch;
91        }
92
93        $sorted = [];
94
95        // Add versions grouped by branches
96        foreach ($branches as $branch) {
97            foreach ($versions as $version) {
98                $parts = $this->parseVersion($version);
99                if ($parts['major'].'.'.$parts['minor'] === $branch) {
100                    $sorted[] = $version;
101                }
102            }
103
104            // Append Git and snaps for each branch
105            foreach ($this->getAffixes() as $item) {
106                $sorted[] = $branch.$item;
107            }
108        }
109
110        // Append master branch to the versions list
111        foreach ($this->getAffixes() as $item) {
112            $sorted[] = 'master-'.$item;
113        }
114
115        // Append human readable versions
116        $sorted = array_merge($sorted, self::APPENDICES);
117
118        return $sorted;
119    }
120
121    /**
122     * Get version affixes such as Git or snapshots.
123     */
124    protected function getAffixes(): array
125    {
126        $date = date('Y-m-d');
127
128        return [
129            'Git-'.$date.' (Git)',
130            'Git-'.$date.' (snap)',
131        ];
132    }
133
134    /**
135     * Get alpha, beta and RC versions.
136     */
137    private function getDevVersions(): array
138    {
139        $versions = [];
140
141        foreach ($this->client->fetchDevVersions() as $version) {
142            $parts = $this->parseVersion($version);
143            if ('dev' !== $parts['type']) {
144                $versions[] = $version;
145            }
146        }
147
148        return $versions;
149    }
150
151    /**
152     * Get stable versions.
153     */
154    private function getStableVersions(): array
155    {
156        $versions = [];
157
158        foreach ($this->client->fetchStableVersions() as $releases) {
159            foreach ($releases as $release) {
160                $versions[] = $release['version'];
161            }
162        }
163
164        return $versions;
165    }
166
167    /**
168     * Parse the versions data string and convert it to array.
169     */
170    private function parseVersion(string $version): array
171    {
172        $matches = [];
173        preg_match('#(?P<major>\d+)\.(?P<minor>\d+).(?P<micro>\d+)[-]?(?P<type>RC|alpha|beta|dev)?(?P<number>[\d]?).*#ui', $version, $matches);
174        $parts = [
175            'major'    => $matches['major'],
176            'minor'    => $matches['minor'],
177            'micro'    => $matches['micro'],
178            'type'     => strtolower($matches['type'] ? $matches['type'] : 'stable'),
179            'number'   => $matches['number'],
180            'original' => $version,
181        ];
182
183        return $parts;
184    }
185}
186