Passed
Push — main ( 2219e4...92dd2e )
by Thierry
02:43 queued 59s
created

StorageManager::translator()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 11
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_a;
27
use function is_array;
28
use function is_string;
29
30
class StorageManager
31
{
32
    /**
33
     * @var array<string, Closure>
34
     */
35
    protected $aAdapters = [];
36
37
    /**
38
     * @var array
39
     */
40
    private $aCurrentAdapter = [];
41
42
    /**
43
     * @var Config|null
44
     */
45
    protected Config|null $xConfig = null;
46
47
    /**
48
     * @var Closure|null
49
     */
50
    protected Closure|null $xConfigGetter = null;
51
52
    /**
53
     * The constructor
54
     *
55
     * @param Config|Closure|null $xConfig
56
     * @param Translator|null $xTranslator
57
     */
58
    public function __construct(Config|Closure|null $xConfig = null,
59
        protected Translator|null $xTranslator = null)
60
    {
61
        $this->registerDefaults();
62
63
        if($xConfig !== null)
64
        {
65
            $this->setConfig($xConfig);
66
        }
67
        if($xTranslator !== null)
68
        {
69
            $this->loadTranslations($xTranslator);
70
        }
71
    }
72
73
    /**
74
     * @param Config|Closure $xConfig
75
     *
76
     * @return self
77
     */
78
    public function setConfig(Config|Closure $xConfig): self
79
    {
80
        $this->xConfig = null;
81
        $this->xConfigGetter = null;
82
        is_a($xConfig, Config::class) ?
83
            $this->xConfig = $xConfig : $this->xConfigGetter = $xConfig;
84
85
        return $this;
86
    }
87
88
    /**
89
     * @return void
90
     */
91
    private function loadTranslations(Translator $xTranslator): void
92
    {
93
        // Translation directory
94
        $sTranslationDir = dirname(__DIR__) . '/translations';
95
        // Load the storage translations
96
        $xTranslator->loadTranslations("$sTranslationDir/en/storage.php", 'en');
97
        $xTranslator->loadTranslations("$sTranslationDir/fr/storage.php", 'fr');
98
        $xTranslator->loadTranslations("$sTranslationDir/es/storage.php", 'es');
99
    }
100
101
    /**
102
     * Get a translator with the translations loaded.
103
     *
104
     * @return Translator
105
     */
106
    public function translator(): Translator
107
    {
108
        if($this->xTranslator !== null)
109
        {
110
            return $this->xTranslator;
111
        }
112
113
        $this->xTranslator = new Translator();
114
        $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

114
        $this->loadTranslations(/** @scrutinizer ignore-type */ $this->xTranslator);
Loading history...
115
116
        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...
117
    }
118
119
    /**
120
     * @param string $sAdapter
121
     * @param Closure|string $xFactory
122
     *
123
     * @return void
124
     */
125
    public function register(string $sAdapter, Closure|string $xFactory): void
126
    {
127
        if(isset($this->aAdapters[$sAdapter]))
128
        {
129
            return;
130
        }
131
132
        if(is_string($xFactory))
133
        {
134
            // The adapter is an alias.
135
            if(!isset($this->aAdapters[$xFactory]))
136
            {
137
                Logger::error("Jaxon Storage: adapter '{$xFactory}' not configured.");
138
                throw new StorageException($this->translator()->trans('errors.storage.adapter'));
139
            }
140
            $xFactory = $this->aAdapters[$xFactory];
141
        }
142
143
        $this->aAdapters[$sAdapter] = $xFactory;
144
    }
145
146
    /**
147
     * Register the file storage adapters
148
     *
149
     * @return void
150
     */
151
    private function registerDefaults(): void
152
    {
153
        // Local file system adapter
154
        $this->register('local', fn(string $sRootDir, array $aOptions) =>
155
            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

155
            new LocalFilesystemAdapter($sRootDir, /** @scrutinizer ignore-type */ ...$aOptions));
Loading history...
156
    }
157
158
    /**
159
     * @param string $sAdapter
160
     * @param array $aAdapterOptions
161
     *
162
     * @return self
163
     */
164
    public function adapter(string $sAdapter, array $aAdapterOptions = []): self
165
    {
166
        $this->aCurrentAdapter = [
167
            'adapter' => $sAdapter,
168
            'options' => $aAdapterOptions,
169
        ];
170
        return $this;
171
    }
172
173
    /**
174
     * @param string $sRootDir
175
     * @param array $aOptions
176
     *
177
     * @return Filesystem
178
     * @throws StorageException
179
     */
180
    public function make(string $sRootDir, array $aOptions = []): Filesystem
181
    {
182
        $sAdapter = $this->aCurrentAdapter['adapter'] ?? '';
183
        if(!isset($this->aAdapters[$sAdapter]))
184
        {
185
            Logger::error("Jaxon Storage: adapter '$sAdapter' not configured.");
186
            throw new StorageException($this->translator()->trans('errors.storage.adapter'));
187
        }
188
189
        // Make the adapter.
190
        $xAdapter = $this->aAdapters[$sAdapter]($sRootDir, $this->aCurrentAdapter['options']);
191
        // Reset the current adapter.
192
        $this->aCurrentAdapter = [];
193
194
        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

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