Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.33% covered (success)
93.33%
42 / 45
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
SystemRequirementsChecker
93.18% covered (success)
93.18%
41 / 44
50.00% covered (danger)
50.00%
1 / 2
10.03
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 check
93.02% covered (success)
93.02%
40 / 43
0.00% covered (danger)
0.00%
0 / 1
9.03
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Core\Installer\Service;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11/**
12 * Service evaluating host environment requirements prior to system installation.
13 *
14 * @package App\Core\Installer\Service
15 */
16final class SystemRequirementsChecker implements SystemRequirementsCheckerInterface
17{
18    /** @var list<string> Required PHP extensions. */
19    private const array REQUIRED_EXTENSIONS = [
20        'pdo_mysql',
21        'zip',
22        'mbstring',
23        'intl',
24        'curl',
25        'json',
26        'openssl',
27        'fileinfo',
28    ];
29
30    /** @var string Minimum supported PHP version. */
31    private const string MIN_PHP_VERSION = '8.2.0';
32
33    /**
34     * SystemRequirementsChecker constructor.
35     *
36     * @param string $projectRoot Application root directory.
37     */
38    public function __construct(private readonly string $projectRoot)
39    {
40    }
41
42    /**
43     * Runs complete pre-flight check suite.
44     *
45     * @return array{passed: bool, checks: array<string, array{status: bool, current: string, required: string}>}
46     */
47    public function check(): array
48    {
49        $checks = [];
50        $allPassed = true;
51
52        // 1. PHP Version Check
53        $phpVersion = PHP_VERSION;
54        $phpPassed = version_compare($phpVersion, self::MIN_PHP_VERSION, '>=');
55        $checks['php_version'] = [
56            'status' => $phpPassed,
57            'current' => $phpVersion,
58            'required' => '>=' . self::MIN_PHP_VERSION,
59        ];
60        if (!$phpPassed) {
61            $allPassed = false;
62        }
63
64        // 2. Required PHP Extensions Check
65        foreach (self::REQUIRED_EXTENSIONS as $ext) {
66            $isLoaded = extension_loaded($ext);
67            $checks['ext_' . $ext] = [
68                'status' => $isLoaded,
69                'current' => $isLoaded ? 'Loaded' : 'Missing',
70                'required' => 'Loaded',
71            ];
72            if (!$isLoaded) {
73                $allPassed = false;
74            }
75        }
76
77        // 3. Writable Directories Check
78        $writableDirs = [
79            'storage',
80            'storage/cache',
81            'storage/runtime',
82            'config',
83        ];
84
85        foreach ($writableDirs as $dir) {
86            $fullPath = $this->projectRoot . '/' . $dir;
87            $isWritable = is_dir($fullPath) ? is_writable($fullPath) : is_writable($this->projectRoot);
88            $checks['dir_' . $dir] = [
89                'status' => $isWritable,
90                'current' => $isWritable ? 'Writable' : 'Read-only / Missing',
91                'required' => 'Writable',
92            ];
93            if (!$isWritable) {
94                $allPassed = false;
95            }
96        }
97
98        return [
99            'passed' => $allPassed,
100            'php_version' => $phpVersion,
101            'extensions' => self::REQUIRED_EXTENSIONS,
102            'writable_dirs' => $writableDirs,
103            'checks' => $checks,
104        ];
105    }
106}