Passed
Pull Request — master (#407)
by Kirill
05:39
created

StorageConfig::getDefaultBucket()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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