Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 59
0.00% covered (danger)
0.00%
0 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
SystemStateService
0.00% covered (danger)
0.00%
0 / 59
0.00% covered (danger)
0.00%
0 / 10
506
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 set
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 delete
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 has
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 all
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 flush
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 load
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
30
 persist
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
42
 resolvePath
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace App\Modules\Installer\Core;
4
5use App\Modules\Installer\Core\Contracts\SystemStateServiceInterface;
6
7/**
8 * Persiste l'état d'installation dans un fichier JSON sur disque.
9 * Fichier : writable/install_state.json
10 */
11class SystemStateService implements SystemStateServiceInterface
12{
13    private string $path;
14    private ?array $state = null;
15
16    public function __construct(?string $path = null)
17    {
18        $this->path = $path ?? $this->resolvePath();
19    }
20
21    // -------------------------------------------------------------------------
22    // INTERFACE
23    // -------------------------------------------------------------------------
24
25    public function get(string $key, mixed $default = null): mixed
26    {
27        $state = $this->load();
28
29        return array_key_exists($key, $state) ? $state[$key] : $default;
30    }
31
32    public function set(string $key, mixed $value): void
33    {
34        $state       = $this->load();
35        $state[$key] = $value;
36
37        $this->state = $state;
38        $this->persist($state);
39    }
40
41    public function delete(string $key): void
42    {
43        $state = $this->load();
44
45        if (!array_key_exists($key, $state)) {
46            return;
47        }
48
49        unset($state[$key]);
50
51        $this->state = $state;
52        $this->persist($state);
53    }
54
55    public function has(string $key): bool
56    {
57        return array_key_exists($key, $this->load());
58    }
59
60    public function all(): array
61    {
62        return $this->load();
63    }
64
65    public function flush(): void
66    {
67        $this->state = [];
68        $this->persist([]);
69    }
70
71    // -------------------------------------------------------------------------
72    // DISQUE
73    // -------------------------------------------------------------------------
74
75    private function load(): array
76    {
77        if ($this->state !== null) {
78            return $this->state;
79        }
80
81        if (!file_exists($this->path)) {
82            $this->persist([]);
83            $this->state = [];
84            return [];
85        }
86
87        $raw = file_get_contents($this->path);
88
89        if ($raw === false) {
90            throw new \RuntimeException(
91                "SystemStateService : impossible de lire ({$this->path})."
92            );
93        }
94
95        $decoded = json_decode($raw, true);
96
97        // Fichier corrompu → repart d'un état propre
98        if (json_last_error() !== JSON_ERROR_NONE) {
99            $this->persist([]);
100            $this->state = [];
101            return [];
102        }
103
104        $this->state = $decoded;
105
106        return $this->state;
107    }
108
109    private function persist(array $state): void
110    {
111        $dir     = dirname($this->path);
112        $content = json_encode($state, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
113
114        if (!is_dir($dir) && !mkdir($dir, 0755, true)) {
115            throw new \RuntimeException(
116                "SystemStateService : impossible de créer le dossier ({$dir})."
117            );
118        }
119
120        if (!is_writable($dir)) {
121            throw new \RuntimeException(
122                "SystemStateService : dossier non accessible en écriture ({$dir})."
123            );
124        }
125
126        // Écriture atomique : tmp + rename
127        $tmp = $this->path . '.tmp.' . getmypid();
128
129        if (file_put_contents($tmp, $content, LOCK_EX) === false) {
130            throw new \RuntimeException(
131                "SystemStateService : échec de l'écriture temporaire ({$tmp})."
132            );
133        }
134
135        if (!rename($tmp, $this->path)) {
136            @unlink($tmp);
137            throw new \RuntimeException(
138                "SystemStateService : échec du rename vers ({$this->path})."
139            );
140        }
141    }
142
143    private function resolvePath(): string
144    {
145        $base = defined('WRITEPATH')
146            ? rtrim(WRITEPATH, DIRECTORY_SEPARATOR)
147            : dirname(__DIR__, 4) . DIRECTORY_SEPARATOR . 'writable';
148
149        return $base . DIRECTORY_SEPARATOR . 'install_state.json';
150    }
151}