Passed
Pull Request — master (#407)
by Kirill
06:59
created

StorageConfig::normalizeBuckets()   B

Complexity

Conditions 10
Paths 9

Size

Total Lines 56
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 31
c 1
b 0
f 0
nc 9
nop 1
dl 0
loc 56
rs 7.6666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file is part of Spiral Framework package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Spiral\Storage\Config;
13
14
use League\Flysystem\FilesystemAdapter;
15
use Spiral\Storage\Config\DTO\FileSystemInfo\FileSystemInfoInterface;
16
use Spiral\Storage\Exception\ConfigException;
17
use Spiral\Storage\Config\DTO\BucketInfo;
18
use Spiral\Storage\Config\DTO\BucketInfoInterface;
19
use Spiral\Storage\Config\DTO\FileSystemInfo;
20
use Spiral\Storage\Exception\StorageException;
21
22
/**
23
 * @psalm-type ConfigServerSection = array {
24
 *      adapter:  class-string<FilesystemAdapter>,
25
 *      options?: array<string, mixed>
26
 * }
27
 *
28
 * @psalm-type ConfigBucketSection = array {
29
 *      server:   string,
30
 *      options?: array<string, mixed>
31
 * }
32
 *
33
 * @psalm-type ConfigArray = array {
34
 *      servers: array<string, ConfigServerSection>,
35
 *      buckets: array<string, ConfigBucketSection>,
36
 *      temp:    string
37
 * }
38
 *
39
 * @psalm-type ConfigInputArray = array {
40
 *      servers?: array<string, ConfigServerSection>|null,
41
 *      buckets?: array<string, ConfigBucketSection>|null,
42
 *      temp?:    string|null
43
 * }
44
 *
45
 * @see FilesystemAdapter
46
 */
47
final class StorageConfig implements ConfigInterface
48
{
49
    /**
50
     * @var string
51
     */
52
    public const CONFIG = 'storage';
53
54
    /**
55
     * @var string
56
     */
57
    private const SERVERS_KEY = 'servers';
58
59
    /**
60
     * @var string
61
     */
62
    private const STORAGES_KEY = 'buckets';
63
64
    /**
65
     * @var string
66
     */
67
    private const TMP_DIR_KEY = 'temp';
68
69
    /**
70
     * @var ConfigArray
0 ignored issues
show
Bug introduced by
The type Spiral\Storage\Config\ConfigArray was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
71
     */
72
    protected $config = [
73
        self::SERVERS_KEY  => [],
74
        self::STORAGES_KEY => [],
75
        self::TMP_DIR_KEY  => '',
76
    ];
77
78
    /**
79
     * Internal list allows to keep once built filesystems info
80
     *
81
     * @var FileSystemInfoInterface[]
82
     */
83
    protected $fileSystemsInfoList = [];
84
85
    /**
86
     * Internal list allows to keep once built buckets info
87
     *
88
     * @var BucketInfoInterface[]
89
     */
90
    protected $bucketsInfoList = [];
91
92
    /**
93
     * @param ConfigInputArray $config
0 ignored issues
show
Bug introduced by
The type Spiral\Storage\Config\ConfigInputArray was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
94
     * @throws ConfigException
95
     */
96
    public function __construct(array $config = [])
97
    {
98
        $this->config = $this->normalize($config);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->normalize($config) of type array is incompatible with the declared type Spiral\Storage\Config\ConfigArray of property $config.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
99
    }
100
101
    /**
102
     * @param ConfigInputArray $config
103
     * @return ConfigArray
104
     * @throws ConfigException
105
     */
106
    private function normalize(array $config): array
107
    {
108
        $config = $this->normalizeServers($config);
109
        $config = $this->normalizeBuckets($config);
110
        $config = $this->normalizeTemporaryDirectory($config);
111
112
        return $config;
113
    }
114
115
    /**
116
     * @param ConfigInputArray $config
117
     * @return ConfigArray
118
     * @throws ConfigException
119
     * @psalm-suppress DocblockTypeContradiction
120
     * @psalm-suppress MoreSpecificReturnType
121
     * @psalm-suppress LessSpecificReturnStatement
122
     */
123
    private function normalizeServers(array $config): array
124
    {
125
        if (! isset($config[self::SERVERS_KEY])) {
126
            $config[self::SERVERS_KEY] = [];
127
128
            return $config;
129
        }
130
131
        $servers = $config[self::SERVERS_KEY];
132
133
        if (! \is_array($servers)) {
134
            $message = 'Storage servers list must be an array, but `%s` defined';
135
            throw new ConfigException(\sprintf($message, $this->valueToString($servers)), 0x01);
136
        }
137
138
        foreach ($servers as $name => $server) {
139
            if (! \is_string($name)) {
140
                $message = 'Storage server name must be a string, but %s defined';
141
                $message = \sprintf($message, $this->valueToString($name));
142
143
                throw new ConfigException($message, 0x02);
144
            }
145
146
            if (! \is_array($server)) {
147
                $message = 'Storage server `%s` config must be an array, but %s defined';
148
                $message = \sprintf($message, $name, $this->valueToString($server));
149
150
                throw new ConfigException($message, 0x03);
151
            }
152
153
            $adapter = $server['adapter'] ?? null;
154
155
            if (! \is_string($adapter) || ! \is_subclass_of($adapter, FilesystemAdapter::class, true)) {
156
                $message = 'Storage server `%s` adapter must be a class ' .
157
                    'string that implements %s interface, but %s defined'
158
                ;
159
160
                $message = \sprintf($message, $name, FilesystemAdapter::class, $this->valueToString($adapter));
161
162
                throw new ConfigException($message, 0x04);
163
            }
164
165
            if (isset($server['options']) && ! \is_array($server['options'])) {
166
                $message = 'Storage server `%s` options must be an array, but %s defined';
167
                $message = \sprintf($message, $name, $this->valueToString($server['options']));
168
169
                throw new ConfigException($message, 0x05);
170
            }
171
        }
172
173
        return $config;
174
    }
175
176
    /**
177
     * @param ConfigInputArray $config
178
     * @return ConfigArray
179
     * @throws ConfigException
180
     * @psalm-suppress DocblockTypeContradiction
181
     * @psalm-suppress MoreSpecificReturnType
182
     * @psalm-suppress LessSpecificReturnStatement
183
     */
184
    private function normalizeBuckets(array $config): array
185
    {
186
        if (! isset($config[self::STORAGES_KEY])) {
187
            $config[self::STORAGES_KEY] = [];
188
189
            return $config;
190
        }
191
192
        $buckets = $config[self::STORAGES_KEY];
193
194
        if (! \is_array($buckets)) {
195
            $message = 'Storage buckets list must be an array, but `%s` defined';
196
            throw new ConfigException(\sprintf($message, $this->valueToString($buckets)), 0x06);
197
        }
198
199
        foreach ($buckets as $name => $bucket) {
200
            if (! \is_string($name)) {
201
                $message = 'Storage bucket name must be a string, but %s defined';
202
                $message = \sprintf($message, $this->valueToString($name));
203
204
                throw new ConfigException($message, 0x07);
205
            }
206
207
            if (! \is_array($bucket)) {
208
                $message = 'Storage bucket `%s` config must be an array, but %s defined';
209
                $message = \sprintf($message, $name, $this->valueToString($bucket));
210
211
                throw new ConfigException($message, 0x08);
212
            }
213
214
            $server = $bucket['server'] ?? null;
215
216
            if (! \is_string($server)) {
217
                $message = 'Storage server of bucket `%s` must be a string, but %s defined';
218
                $message = \sprintf($message, $name, $this->valueToString($server));
219
220
                throw new ConfigException($message, 0x09);
221
            }
222
223
            if (! isset($config[self::SERVERS_KEY][$server])) {
224
                $variants = \implode(', ', \array_keys($config[self::SERVERS_KEY] ?? []));
225
                $message = 'Storage server `%s` of bucket `%s` has not been defined, one of [%s] required';
226
                $message = \sprintf($message, $server, $name, $variants);
227
228
                throw new ConfigException($message, 0x0A);
229
            }
230
231
            if (isset($bucket['options']) && ! \is_array($bucket['options'])) {
232
                $message = 'Storage bucket `%s` options must be an array, but %s defined';
233
                $message = \sprintf($message, $name, $this->valueToString($bucket['options']));
234
235
                throw new ConfigException($message, 0x0B);
236
            }
237
        }
238
239
        return $config;
240
    }
241
242
    /**
243
     * @param ConfigInputArray $config
244
     * @return ConfigArray
245
     * @throws ConfigException
246
     * @psalm-suppress DocblockTypeContradiction
247
     * @psalm-suppress MoreSpecificReturnType
248
     * @psalm-suppress LessSpecificReturnStatement
249
     */
250
    private function normalizeTemporaryDirectory(array $config): array
251
    {
252
        if (! isset($config[self::TMP_DIR_KEY]) ) {
253
            $config[self::TMP_DIR_KEY] = \sys_get_temp_dir();
254
255
            return $config;
256
        }
257
258
        $directory = $config[self::TMP_DIR_KEY];
259
260
        if (! \is_string($directory)) {
261
            $message = 'Storage temporary directory must be a string, but `%s` defined';
262
            throw new ConfigException(\sprintf($message, $this->valueToString($directory)), 0x0C);
263
        }
264
265
        if (! \is_dir($directory)) {
266
            $message = 'Storage temporary directory `%s` must be a valid directory';
267
            throw new ConfigException(\sprintf($message, $directory), 0x0D);
268
        }
269
270
        if (! \is_writable($directory)) {
271
            $message = 'Storage temporary directory `%s` must be writable';
272
            throw new ConfigException(\sprintf($message, $directory), 0x0E);
273
        }
274
275
        return $config;
276
    }
277
278
    /**
279
     * @param mixed $value
280
     * @return string
281
     */
282
    private function valueToString($value): string
283
    {
284
        $suffix = \is_scalar($value) ? "($value)" : (string)$value;
285
286
        return \get_debug_type($value) . $suffix;
287
    }
288
289
    /**
290
     * {@inheritDoc}
291
     */
292
    public function getServersKeys(): array
293
    {
294
        return \array_keys($this->config[self::SERVERS_KEY]);
295
    }
296
297
    /**
298
     * {@inheritDoc}
299
     */
300
    public function hasServer(string $key): bool
301
    {
302
        return isset($this->config[self::SERVERS_KEY][$key]);
303
    }
304
305
    /**
306
     * {@inheritDoc}
307
     */
308
    public function getBucketsKeys(): array
309
    {
310
        return \array_keys($this->config[self::STORAGES_KEY]);
311
    }
312
313
    /**
314
     * {@inheritDoc}
315
     */
316
    public function hasBucket(string $key): bool
317
    {
318
        return isset($this->config[self::STORAGES_KEY][$key]);
319
    }
320
321
    /**
322
     * {@inheritDoc}
323
     */
324
    public function getTmpDir(): string
325
    {
326
        return $this->config[self::TMP_DIR_KEY];
327
    }
328
329
    /**
330
     * {@inheritDoc}
331
     */
332
    public function buildFileSystemInfo(string $fs, ?bool $force = false): FileSystemInfoInterface
333
    {
334
        if (!$this->hasBucket($fs)) {
335
            throw new ConfigException(
336
                \sprintf('Bucket `%s` was not found', $fs)
337
            );
338
        }
339
340
        if (!$force && array_key_exists($fs, $this->fileSystemsInfoList)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $force of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
341
            return $this->fileSystemsInfoList[$fs];
342
        }
343
344
        $bucketInfo = $this->buildBucketInfo($fs);
345
346
        if (!$this->hasServer($bucketInfo->getServer())) {
347
            throw new ConfigException(
348
                \sprintf(
349
                    'Server `%s` info for filesystem `%s` was not detected',
350
                    $bucketInfo->getServer(),
351
                    $fs
352
                )
353
            );
354
        }
355
356
        $serverInfo = $this->config[self::SERVERS_KEY][$bucketInfo->getServer()];
357
358
        switch ($this->extractServerAdapter($serverInfo)) {
359
            case \League\Flysystem\Local\LocalFilesystemAdapter::class:
360
                $fsInfoDTO = new FileSystemInfo\LocalInfo($fs, $serverInfo);
361
                break;
362
            case \League\Flysystem\AwsS3V3\AwsS3V3Adapter::class:
363
            case \League\Flysystem\AsyncAwsS3\AsyncAwsS3Adapter::class:
364
                $serverInfo[FileSystemInfo\Aws\AwsS3Info::OPTIONS_KEY] = array_merge(
365
                    [
366
                        FileSystemInfo\Aws\AwsS3Info::BUCKET_KEY => $bucketInfo->getOption(
367
                            BucketInfoInterface::BUCKET_KEY
368
                        )
369
                    ],
370
                    $serverInfo[FileSystemInfo\Aws\AwsS3Info::OPTIONS_KEY]
371
                );
372
373
                $fsInfoDTO = new FileSystemInfo\Aws\AwsS3Info($fs, $serverInfo);
374
                break;
375
            default:
376
                throw new ConfigException(
377
                    \sprintf('Adapter can\'t be identified for filesystem `%s`', $fs)
378
                );
379
        }
380
381
        $this->fileSystemsInfoList[$fs] = $fsInfoDTO;
382
        $bucketInfo->setFileSystemInfo($fsInfoDTO);
383
384
        return $this->fileSystemsInfoList[$fs];
385
    }
386
387
    /**
388
     * {@inheritDoc}
389
     */
390
    public function buildBucketInfo(string $bucketLabel, ?bool $force = false): BucketInfoInterface
391
    {
392
        if (!$this->hasBucket($bucketLabel)) {
393
            throw new StorageException(
394
                \sprintf('Bucket `%s` was not found', $bucketLabel)
395
            );
396
        }
397
398
        if (!$force && array_key_exists($bucketLabel, $this->bucketsInfoList)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $force of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
399
            return $this->bucketsInfoList[$bucketLabel];
400
        }
401
402
        $bucketInfo = $this->config[self::STORAGES_KEY][$bucketLabel];
403
404
        $this->bucketsInfoList[$bucketLabel] = new BucketInfo(
405
            $bucketLabel,
406
            $bucketInfo[BucketInfoInterface::SERVER_KEY],
407
            $bucketInfo
408
        );
409
410
        return $this->bucketsInfoList[$bucketLabel];
411
    }
412
413
    /**
414
     * Extract server adapter class from server description
415
     *
416
     * @param array $serverInfo
417
     *
418
     * @return string|null
419
     */
420
    private function extractServerAdapter(array $serverInfo): ?string
421
    {
422
        return $serverInfo[FileSystemInfoInterface::ADAPTER_KEY] ?? null;
423
    }
424
}
425