Config::getInstance()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 7
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gacela\Framework\Config;
6
7
use Gacela\Framework\Bootstrap\SetupGacelaInterface;
8
use Gacela\Framework\Event\Dispatcher\EventDispatcherInterface;
9
use Gacela\Framework\Exception\ConfigException;
10
use Override;
11
use RuntimeException;
12
13
use function array_key_exists;
14
15
final class Config implements ConfigInterface
16
{
17
    private static ?self $instance = null;
18
19
    private static ?EventDispatcherInterface $eventDispatcher = null;
20
21
    private ?ConfigFactory $configFactory = null;
22
23
    private ?string $appRootDir = null;
24
25
    /** @var array<string,mixed> */
26
    private array $config = [];
27
28
    private ?string $cacheDir = null;
29
30
    private function __construct(
31
        private readonly SetupGacelaInterface $setup,
32
    ) {
33
    }
34
35
    public static function createWithSetup(SetupGacelaInterface $setup): self
36
    {
37
        self::$instance = new self($setup);
38
39
        return self::$instance;
40
    }
41
42
    public static function getInstance(): self
43
    {
44
        if (!self::$instance instanceof self) {
45
            throw new RuntimeException('You have to call createWithSetup() first. Have you forgot to bootstrap Gacela?');
46
        }
47
48
        return self::$instance;
49
    }
50
51
    /**
52
     * @internal
53
     */
54
    public static function resetInstance(): void
55
    {
56
        self::$instance = null;
57
        self::$eventDispatcher = null;
58
    }
59
60
    public static function getEventDispatcher(): EventDispatcherInterface
61
    {
62
        if (!self::$eventDispatcher instanceof EventDispatcherInterface) {
63
            self::$eventDispatcher = self::getInstance()
64
                ->getSetupGacela()
65
                ->getEventDispatcher();
66
        }
67
68
        return self::$eventDispatcher;
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::eventDispatcher could return the type null which is incompatible with the type-hinted return Gacela\Framework\Event\D...ventDispatcherInterface. Consider adding an additional type-check to rule them out.
Loading history...
69
    }
70
71
    /**
72
     * @throws ConfigException
73
     */
74
    #[Override]
75
    public function get(string $key, mixed $default = self::DEFAULT_CONFIG_VALUE): mixed
76
    {
77
        if ($this->config === []) {
78
            $this->init();
79
        }
80
81
        if ($default !== self::DEFAULT_CONFIG_VALUE && !$this->hasKey($key)) {
82
            return $default;
83
        }
84
85
        if (!$this->hasKey($key)) {
86
            throw ConfigException::keyNotFound($key, self::class);
87
        }
88
89
        return $this->config[$key];
90
    }
91
92
    /**
93
     * Force loading all config values in memory.
94
     *
95
     * @throws ConfigException
96
     */
97
    public function init(): void
98
    {
99
        $this->configFactory = null;
100
101
        /** @psalm-suppress DuplicateArrayKey */
102
        $this->config = [
103
            ...$this->loadAllConfigValues(),
104
            ...$this->setup->getConfigKeyValues(),
105
        ];
106
    }
107
108
    public function setAppRootDir(string $dir): self
109
    {
110
        $this->appRootDir = rtrim($dir, DIRECTORY_SEPARATOR);
111
112
        if ($this->appRootDir === '' || $this->appRootDir === '0') {
113
            $this->appRootDir = getcwd() ?: ''; // @codeCoverageIgnore
114
        }
115
116
        return $this;
117
    }
118
119
    public function getAppRootDir(): string
120
    {
121
        return $this->appRootDir ?? getcwd() ?: '';
122
    }
123
124
    public function getCacheDir(): string
125
    {
126
        if ($this->cacheDir !== null) {
127
            return $this->cacheDir;
128
        }
129
130
        $this->cacheDir = getenv('GACELA_CACHE_DIR') ?: $this->getDefaultCacheDir();
131
132
        return rtrim($this->cacheDir, DIRECTORY_SEPARATOR);
133
    }
134
135
    /**
136
     * @internal
137
     */
138
    public function getFactory(): ConfigFactory
139
    {
140
        if (!$this->configFactory instanceof ConfigFactory) {
141
            $this->configFactory = new ConfigFactory(
142
                $this->getAppRootDir(),
143
                $this->setup,
144
            );
145
        }
146
147
        return $this->configFactory;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->configFactory could return the type null which is incompatible with the type-hinted return Gacela\Framework\Config\ConfigFactory. Consider adding an additional type-check to rule them out.
Loading history...
148
    }
149
150
    #[Override]
151
    public function getSetupGacela(): SetupGacelaInterface
152
    {
153
        return $this->setup;
154
    }
155
156
    #[Override]
157
    public function hasKey(string $key): bool
158
    {
159
        return array_key_exists($key, $this->config);
160
    }
161
162
    private function getDefaultCacheDir(): string
163
    {
164
        $cacheDir = $this->setup->getFileCacheDirectory();
165
        if ($cacheDir === '') {
166
            return sys_get_temp_dir();
167
        }
168
169
        $appRoot = $this->getAppRootDir();
170
171
        if (preg_match('#^[A-Za-z]:[\\/]#', $cacheDir) === 1) {
172
            return $cacheDir;
173
        }
174
175
        if ($cacheDir[0] === DIRECTORY_SEPARATOR) {
176
            if (str_starts_with($cacheDir, $appRoot . DIRECTORY_SEPARATOR)) {
177
                return $cacheDir;
178
            }
179
180
            return $appRoot . $cacheDir;
181
        }
182
183
        return $appRoot
184
            . DIRECTORY_SEPARATOR
185
            . ltrim($cacheDir, DIRECTORY_SEPARATOR);
186
    }
187
188
    /**
189
     * @return array<string,mixed>
190
     */
191
    private function loadAllConfigValues(): array
192
    {
193
        return $this->getFactory()
194
            ->createConfigLoader()
195
            ->loadAll();
196
    }
197
}
198