Passed
Pull Request — master (#407)
by Kirill
09:14 queued 02:20
created

StorageConfig::bootStorages()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 47
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 7
eloc 27
nc 7
nop 1
dl 0
loc 47
rs 8.5546
c 1
b 0
f 1
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\Bootloader\Storage;
13
14
use AsyncAws\S3\S3Client as S3AsyncClient;
15
use Aws\Credentials\Credentials;
16
use Aws\S3\S3Client;
17
use League\Flysystem\AsyncAwsS3\AsyncAwsS3Adapter;
18
use League\Flysystem\AwsS3V3\AwsS3V3Adapter;
19
use League\Flysystem\FilesystemAdapter;
20
use League\Flysystem\Local\LocalFilesystemAdapter;
21
use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
22
use Spiral\Config\Exception\InvalidArgumentException;
23
use Spiral\Storage\Manager;
24
use Spiral\Storage\Visibility;
25
26
class StorageConfig
27
{
28
    /**
29
     * @var string
30
     */
31
    public const CONFIG = 'storage';
32
33
    /**
34
     * @var string
35
     */
36
    private $default;
37
38
    /**
39
     * @var array<string, FilesystemAdapter>
40
     */
41
    private $adapters = [];
42
43
    /**
44
     * @var array<string, string>
45
     */
46
    private $distributions = [];
47
48
    /**
49
     * @param array $config
50
     */
51
    public function __construct(array $config = [])
52
    {
53
        $config = $this->normalize($config);
54
55
        $this->default = $config['default'];
56
        $this->bootStorages($config);
57
    }
58
59
    /**
60
     * @return string
61
     */
62
    public function getDefaultBucket(): string
63
    {
64
        return $this->default;
65
    }
66
67
    /**
68
     * @return array<string, FilesystemAdapter>
69
     */
70
    public function getAdapters(): array
71
    {
72
        return $this->adapters;
73
    }
74
75
    /**
76
     * @return array<string, string>
77
     */
78
    public function getDistributions(): array
79
    {
80
        return $this->distributions;
81
    }
82
83
    /**
84
     * @param array $config
85
     * @return array
86
     */
87
    private function normalize(array $config): array
88
    {
89
        $defaults = [
90
            'default' => Manager::DEFAULT_STORAGE,
91
            'servers' => [],
92
            'buckets' => [],
93
        ];
94
95
        return \array_merge($defaults, $config);
96
    }
97
98
    /**
99
     * @param array $config
100
     */
101
    private function bootStorages(array $config): void
102
    {
103
        foreach ($config['buckets'] as $name => $bucket) {
104
            if (!\is_string($name)) {
105
                throw new InvalidArgumentException(
106
                    \vsprintf('Storage bucket config key must be a string, but %s defined', [
107
                        \get_debug_type($name),
108
                    ])
109
                );
110
            }
111
112
            $serverName = $bucket['server'] ?? null;
113
            if (!\is_string($serverName)) {
114
                throw new InvalidArgumentException(
115
                    \vsprintf('Storage bucket `%s.server` config key required and must be a string, but %s defined', [
116
                        $name,
117
                        \get_debug_type($serverName),
118
                    ])
119
                );
120
            }
121
122
            $server = $config['servers'][$serverName] ?? null;
123
            if (!\is_array($server)) {
124
                throw new InvalidArgumentException(
125
                    \vsprintf('Storage bucket `%s` relates to non-existing server `%s`', [
126
                        $name,
127
                        $serverName,
128
                    ])
129
                );
130
            }
131
132
            $adapter = $server['adapter'] ?? null;
133
            if (!\is_string($adapter)) {
134
                throw new InvalidArgumentException(
135
                    \vsprintf('Storage server `%s.adapter` config key required and must be a string, but %s defined', [
136
                        $serverName,
137
                        \get_debug_type($adapter),
138
                    ])
139
                );
140
            }
141
142
            $adapter = $this->createAdapter($serverName, $bucket, $server);
143
144
            $this->adapters[$name] = $adapter;
145
146
            if (isset($bucket['cdn'])) {
147
                $this->distributions[$name] = $bucket['cdn'];
148
            }
149
        }
150
    }
151
152
    /**
153
     * @param string $serverName
154
     * @param array $bucket
155
     * @param array $server
156
     * @return FilesystemAdapter
157
     */
158
    private function createAdapter(string $serverName, array $bucket, array $server): FilesystemAdapter
159
    {
160
        switch ($server['adapter']) {
161
            case 'local':
162
                return $this->createLocalAdapter($serverName, $bucket, $server);
163
164
            case 's3':
165
                return $this->createS3Adapter($bucket, $server, false);
166
167
            case 's3-async':
168
                return $this->createS3Adapter($bucket, $server, true);
169
170
            default:
171
                return $this->createCustomAdapter($serverName, $server);
172
        }
173
    }
174
175
    /**
176
     * @param array $bucket
177
     * @param array $server
178
     * @param bool $async
179
     * @return FilesystemAdapter
180
     */
181
    private function createS3Adapter(array $bucket, array $server, bool $async): FilesystemAdapter
182
    {
183
        $config = [
184
            'version'     => $server['version'] ?? 'latest',
185
            'region'      => $server['region'],
186
            'credentials' => new Credentials(
187
                $server['key'],
188
                $server['secret'],
189
                $server['token'] ?? null,
190
                $server['expires'] ?? null
191
            ),
192
            $server['endpoint'] ?? null,
193
        ];
194
195
        $bucket = $bucket['bucket'] ?? $server['bucket'];
196
197
        if ($async) {
198
            if (!\class_exists(AsyncAwsS3Adapter::class)) {
199
                throw new InvalidArgumentException(
200
                    'Can not create async S3 client, please install "league/flysystem-async-aws-s3"'
201
                );
202
            }
203
204
            return new AsyncAwsS3Adapter(
205
                new S3AsyncClient($config),
206
                $bucket
207
            );
208
        }
209
210
        if (!\class_exists(AwsS3V3Adapter::class)) {
211
            throw new InvalidArgumentException(
212
                'Can not create S3 client, please install "league/flysystem-aws-s3-v3"'
213
            );
214
        }
215
216
        return new AwsS3V3Adapter(
217
            new S3Client($config),
218
            $bucket
219
        );
220
    }
221
222
    /**
223
     * @param string $serverName
224
     * @param array $bucket
225
     * @param array $server
226
     * @return FilesystemAdapter
227
     */
228
    private function createLocalAdapter(string $serverName, array $bucket, array $server): FilesystemAdapter
229
    {
230
        if (!\is_string($server['directory'] ?? null)) {
231
            throw new InvalidArgumentException(
232
                \vsprintf('Storage server `%s.directory` config key required and must be a string, but %s defined', [
233
                    $serverName,
234
                    \get_debug_type($server['directory'] ?? null),
235
                ])
236
            );
237
        }
238
239
        $visibility = new PortableVisibilityConverter(
240
            $server['visibility']['public']['file'] ?? 0644,
241
            $server['visibility']['private']['file'] ?? 0600,
242
            $server['visibility']['public']['dir'] ?? 0755,
243
            $server['visibility']['private']['dir'] ?? 0700,
244
            $bucket['visibility'] ?? $server['visibility']['default'] ?? Visibility::VISIBILITY_PRIVATE
245
        );
246
247
        $directory = \implode('/', [
248
            \rtrim($server['directory'], '/'),
249
            \trim($bucket['prefix'] ?? '', '/'),
250
        ]);
251
252
        return new LocalFilesystemAdapter(\rtrim($directory, '/'), $visibility);
253
    }
254
255
    /**
256
     * @param string $serverName
257
     * @param array $server
258
     * @return FilesystemAdapter
259
     */
260
    private function createCustomAdapter(string $serverName, array $server): FilesystemAdapter
261
    {
262
        $adapter = $server['adapter'];
263
        $isFilesystemAdapter = \is_subclass_of($adapter, FilesystemAdapter::class, true);
264
265
        if (!$isFilesystemAdapter) {
266
            throw new InvalidArgumentException(
267
                \vsprintf('Storage server `%s` must be a class string of %s, but `%s` defined', [
268
                    $serverName,
269
                    FilesystemAdapter::class,
270
                    $adapter,
271
                ])
272
            );
273
        }
274
275
        try {
276
            return new $adapter(...\array_values($server['options'] ?? []));
277
        } catch (\Throwable $e) {
278
            $message = 'An error occurred while server `%s` initializing: %s';
279
            throw new InvalidArgumentException(\sprintf($message, $serverName, $e->getMessage()), 0, $e);
280
        }
281
    }
282
}
283