Passed
Push — master ( 5c4bac...e50301 )
by butschster
05:42 queued 19s
created

StorageConfig::createS3Adapter()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 38
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 7.6393

Importance

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