Passed
Push — main ( 82104f...ee6fb2 )
by Thierry
05:16
created

StorageManager::config()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
c 0
b 0
f 0
nc 3
nop 0
dl 0
loc 14
rs 10
1
<?php
2
3
/**
4
 * StorageManager.php
5
 *
6
 * File storage manager.
7
 *
8
 * @package jaxon-storage
9
 * @author Thierry Feuzeu <[email protected]>
10
 * @copyright 2025 Thierry Feuzeu <[email protected]>
11
 * @license https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
12
 * @link https://github.com/jaxon-php/jaxon-core
13
 */
14
15
namespace Jaxon\Storage;
16
17
use Jaxon\Config\Config;
18
use Jaxon\Utils\Translation\Translator;
19
use Lagdo\Facades\Logger;
20
use League\Flysystem\Filesystem;
21
use League\Flysystem\Local\LocalFilesystemAdapter;
22
use Closure;
23
24
use function count;
25
use function dirname;
26
use function is_array;
27
use function is_string;
28
29
class StorageManager
30
{
31
    /**
32
     * @var array<string, Closure>
33
     */
34
    protected $aAdapters = [];
35
36
    /**
37
     * @var array
38
     */
39
    private $aCurrentAdapter = [];
40
41
    /**
42
     * @var Config|null
43
     */
44
    protected Config|null $xConfig = null;
45
46
    /**
47
     * The constructor
48
     *
49
     * @param Closure|null $xConfigGetter
50
     * @param Translator|null $xTranslator
51
     */
52
    public function __construct(private Closure|null $xConfigGetter = null,
53
        protected Translator|null $xTranslator = null)
54
    {
55
        $this->registerDefaults();
56
57
        if($xTranslator !== null)
58
        {
59
            $this->loadTranslations($xTranslator);
60
        }
61
    }
62
63
    /**
64
     * @param Closure $xConfigGetter
65
     *
66
     * @return void
67
     */
68
    public function setConfigGetter(Closure $xConfigGetter): void
69
    {
70
        $this->xConfigGetter = $xConfigGetter;
71
        $this->xConfig = null;
72
    }
73
74
    /**
75
     * @return void
76
     */
77
    private function loadTranslations(Translator $xTranslator): void
78
    {
79
        // Translation directory
80
        $sTranslationDir = dirname(__DIR__) . '/translations';
81
        // Load the storage translations
82
        $xTranslator->loadTranslations("$sTranslationDir/en/storage.php", 'en');
83
        $xTranslator->loadTranslations("$sTranslationDir/fr/storage.php", 'fr');
84
        $xTranslator->loadTranslations("$sTranslationDir/es/storage.php", 'es');
85
    }
86
87
    /**
88
     * Get a translator with the translations loaded.
89
     *
90
     * @return Translator
91
     */
92
    public function translator(): Translator
93
    {
94
        if($this->xTranslator !== null)
95
        {
96
            return $this->xTranslator;
97
        }
98
99
        $this->xTranslator = new Translator();
100
        $this->loadTranslations($this->xTranslator);
0 ignored issues
show
Bug introduced by
$this->xTranslator of type null is incompatible with the type Jaxon\Utils\Translation\Translator expected by parameter $xTranslator of Jaxon\Storage\StorageManager::loadTranslations(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

100
        $this->loadTranslations(/** @scrutinizer ignore-type */ $this->xTranslator);
Loading history...
101
102
        return $this->xTranslator;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->xTranslator returns the type null which is incompatible with the type-hinted return Jaxon\Utils\Translation\Translator.
Loading history...
103
    }
104
105
    /**
106
     * @param string $sAdapter
107
     * @param Closure|string $xFactory
108
     *
109
     * @return void
110
     */
111
    public function register(string $sAdapter, Closure|string $xFactory): void
112
    {
113
        if(isset($this->aAdapters[$sAdapter]))
114
        {
115
            return;
116
        }
117
118
        if(is_string($xFactory))
119
        {
120
            // The adapter is an alias.
121
            if(!isset($this->aAdapters[$xFactory]))
122
            {
123
                Logger::error("Jaxon Storage: adapter '{$xFactory}' not configured.");
124
                throw new Exception($this->translator()->trans('errors.storage.adapter'));
125
            }
126
            $xFactory = $this->aAdapters[$xFactory];
127
        }
128
129
        $this->aAdapters[$sAdapter] = $xFactory;
130
    }
131
132
    /**
133
     * Register the file storage adapters
134
     *
135
     * @return void
136
     */
137
    private function registerDefaults(): void
138
    {
139
        // Local file system adapter
140
        $this->register('local', fn(string $sRootDir, array $aOptions) =>
141
            new LocalFilesystemAdapter($sRootDir, ...$aOptions));
0 ignored issues
show
Bug introduced by
$aOptions is expanded, but the parameter $visibility of League\Flysystem\Local\L...mAdapter::__construct() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

141
            new LocalFilesystemAdapter($sRootDir, /** @scrutinizer ignore-type */ ...$aOptions));
Loading history...
142
    }
143
144
    /**
145
     * @param string $sAdapter
146
     * @param array $aAdapterOptions
147
     *
148
     * @return self
149
     */
150
    public function adapter(string $sAdapter, array $aAdapterOptions = []): self
151
    {
152
        $this->aCurrentAdapter = [
153
            'adapter' => $sAdapter,
154
            'options' => $aAdapterOptions,
155
        ];
156
        return $this;
157
    }
158
159
    /**
160
     * @param string $sRootDir
161
     * @param array $aOptions
162
     *
163
     * @return Filesystem
164
     * @throws Exception
165
     */
166
    public function make(string $sRootDir, array $aOptions = []): Filesystem
167
    {
168
        $sAdapter = $this->aCurrentAdapter['adapter'] ?? '';
169
        if(!isset($this->aAdapters[$sAdapter]))
170
        {
171
            Logger::error("Jaxon Storage: adapter '$sAdapter' not configured.");
172
            throw new Exception($this->translator()->trans('errors.storage.adapter'));
173
        }
174
175
        // Make the adapter.
176
        $xAdapter = $this->aAdapters[$sAdapter]($sRootDir, $this->aCurrentAdapter['options']);
177
        // Reset the current adapter.
178
        $this->aCurrentAdapter = [];
179
180
        return new Filesystem($xAdapter, ...$aOptions);
0 ignored issues
show
Bug introduced by
$aOptions is expanded, but the parameter $config of League\Flysystem\Filesystem::__construct() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

180
        return new Filesystem($xAdapter, /** @scrutinizer ignore-type */ ...$aOptions);
Loading history...
181
    }
182
183
    /**
184
     * @throws Exception
185
     * @return Config
186
     */
187
    private function config(): Config
188
    {
189
        if($this->xConfig !== null)
190
        {
191
            return $this->xConfig;
192
        }
193
194
        if($this->xConfigGetter === null)
195
        {
196
            Logger::error("Jaxon Storage: No config getter set.");
197
            throw new Exception($this->translator()->trans('errors.storage.getter'));
198
        }
199
200
        return $this->xConfig = ($this->xConfigGetter)();
201
    }
202
203
    /**
204
     * @param string $sAdapter
205
     *
206
     * @return self
207
     */
208
    private function setCurrentAdapter(string $sAdapter): self
209
    {
210
        $xConfig = $this->config();
211
212
        $aOptions = $xConfig->getOption("adapters.$sAdapter", []);
213
        if(!is_array($aOptions))
214
        {
215
            Logger::error("Jaxon Storage: incorrect values in 'adapters.$sAdapter' options.");
216
            throw new Exception($this->translator()->trans('errors.storage.options'));
217
        }
218
219
        if(count($aOptions) === 2 &&
220
            is_string($aOptions['alias'] ?? null) &&
221
            is_array($aOptions['options'] ?? null))
222
        {
223
            $this->register($sAdapter, $aOptions['alias']);
224
            $aOptions = $aOptions['options'];
225
        }
226
227
        return $this->adapter($sAdapter, $aOptions);
228
    }
229
230
    /**
231
     * @param string $sOptionName
232
     *
233
     * @return Filesystem
234
     * @throws Exception
235
     */
236
    public function get(string $sOptionName): Filesystem
237
    {
238
        $xConfig = $this->config();
239
240
        $sAdapter = $xConfig->getOption("stores.$sOptionName.adapter");
241
        $sRootDir = $xConfig->getOption("stores.$sOptionName.dir");
242
        $aOptions = $xConfig->getOption("stores.$sOptionName.options", []);
243
        if(!is_string($sAdapter) || !is_string($sRootDir) || !is_array($aOptions))
244
        {
245
            Logger::error("Jaxon Storage: incorrect values in 'stores.$sOptionName' options.");
246
            throw new Exception($this->translator()->trans('errors.storage.options'));
247
        }
248
249
        return $this->setCurrentAdapter($sAdapter)->make($sRootDir, $aOptions);
250
    }
251
}
252