Passed
Pull Request — master (#407)
by Kirill
10:25 queued 04:56
created

DistributionConfig::createCloudFrontResolver()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 7
eloc 10
nc 4
nop 2
dl 0
loc 18
rs 8.8333
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\Distribution;
13
14
use Aws\Credentials\Credentials;
15
use Aws\S3\S3Client;
16
use GuzzleHttp\Psr7\Uri as GuzzleUri;
17
use Laminas\Diactoros\Uri as LaminasUri;
18
use Psr\Http\Message\UriFactoryInterface;
19
use Psr\Http\Message\UriInterface;
20
use Spiral\Distribution\Manager;
21
use Spiral\Distribution\Resolver\CloudFrontResolver;
22
use Spiral\Distribution\Resolver\S3SignedResolver;
23
use Spiral\Distribution\Resolver\StaticResolver;
24
use Spiral\Distribution\UriResolverInterface;
25
use Spiral\Config\Exception\InvalidArgumentException;
26
27
class DistributionConfig
28
{
29
    /**
30
     * @var string
31
     */
32
    public const CONFIG = 'distribution';
33
34
    /**
35
     * @var string
36
     */
37
    private $default = Manager::DEFAULT_RESOLVER;
38
39
    /**
40
     * @var array<string, UriResolverInterface>
41
     */
42
    private $resolvers = [];
43
44
    /**
45
     * @param array $config
46
     */
47
    public function __construct(array $config = [])
48
    {
49
        $this->bootDefaultDriver($config);
50
        $this->bootResolvers($config);
51
    }
52
53
    /**
54
     * @return string
55
     */
56
    public function getDefaultDriver(): string
57
    {
58
        return $this->default;
59
    }
60
61
    /**
62
     * @return iterable<string, UriResolverInterface>
63
     */
64
    public function getResolvers(): iterable
65
    {
66
        return $this->resolvers;
67
    }
68
69
    /**
70
     * @param array $config
71
     */
72
    private function bootResolvers(array $config): void
73
    {
74
        foreach ($config['resolvers'] ?? [] as $name => $child) {
75
            if (!\is_string($name)) {
76
                throw new InvalidArgumentException(
77
                    \vsprintf('Distribution driver config key must be a string, but %s given', [
78
                        \get_debug_type($child),
79
                    ])
80
                );
81
            }
82
83
            if (!\is_array($child)) {
84
                throw new InvalidArgumentException(
85
                    \vsprintf('Distribution driver config `%s` must be an array, but %s given', [
86
                        $name,
87
                        \get_debug_type($child),
88
                    ])
89
                );
90
            }
91
92
            $this->resolvers[$name] = $this->createResolver($name, $child);
93
        }
94
    }
95
96
    /**
97
     * @param array $config
98
     */
99
    private function bootDefaultDriver(array $config): void
100
    {
101
        $default = $config['default'] ?? null;
102
103
        if ($default !== null) {
104
            // Validate config
105
            if (!\is_string($default)) {
106
                throw new InvalidArgumentException(
107
                    \vsprintf('Distribution config default driver must be a string, but %s given', [
108
                        \get_debug_type($default),
109
                    ])
110
                );
111
            }
112
113
            $this->default = $default;
114
        }
115
    }
116
117
    /**
118
     * @param string $name
119
     * @param array $config
120
     * @return UriResolverInterface
121
     */
122
    private function createResolver(string $name, array $config): UriResolverInterface
123
    {
124
        if (!isset($config['type']) || !\is_string($config['type'])) {
125
            throw $this->invalidConfigKey($name, 'type', 'string');
126
        }
127
128
        $type = $config['type'];
129
130
        switch ($type) {
131
            case 'static':
132
                return $this->createStaticResolver($name, $config);
133
134
            case 's3':
135
                return $this->createS3Resolver($name, $config);
136
137
            case 'cloudfront':
138
                return $this->createCloudFrontResolver($name, $config);
139
140
            default:
141
                return $this->createCustomResolver($type, $name, $config);
142
        }
143
    }
144
145
    /**
146
     * @param string $type
147
     * @param string $name
148
     * @param array $config
149
     * @return UriResolverInterface
150
     */
151
    private function createCustomResolver(string $type, string $name, array $config): UriResolverInterface
152
    {
153
        if (!\is_subclass_of($type, UriResolverInterface::class, true)) {
154
            throw $this->invalidConfigKey($name, 'type', UriResolverInterface::class);
155
        }
156
157
        if (isset($config['options']) && !\is_array($config['options'])) {
158
            throw $this->invalidConfigKey($name, 'options', 'array');
159
        }
160
161
        try {
162
            return new $type(...\array_values($config['options'] ?? []));
163
        } catch (\Throwable $e) {
164
            $message = 'An error occurred while resolver `%s` initializing: %s';
165
            throw new InvalidArgumentException(\sprintf($message, $name, $e->getMessage()), 0, $e);
166
        }
167
    }
168
169
    /**
170
     * @param string $name
171
     * @param array $config
172
     * @return UriResolverInterface
173
     */
174
    private function createS3Resolver(string $name, array $config): UriResolverInterface
175
    {
176
        // Required config options
177
        if (!isset($config['region']) || !\is_string($config['region'])) {
178
            throw $this->invalidConfigKey($name, 'region', 'string');
179
        }
180
181
        if (!isset($config['key']) || !\is_string($config['key'])) {
182
            throw $this->invalidConfigKey($name, 'key', 'string');
183
        }
184
185
        if (!isset($config['secret']) || !\is_string($config['secret'])) {
186
            throw $this->invalidConfigKey($name, 'secret', 'string');
187
        }
188
189
        if (!isset($config['bucket']) || !\is_string($config['bucket'])) {
190
            throw $this->invalidConfigKey($name, 'bucket', 'string');
191
        }
192
193
        // Optional config options
194
        if (!\is_string($config['version'] ?? '')) {
195
            throw $this->invalidConfigKey($name, 'version', 'string or null');
196
        }
197
198
        if (!\is_string($config['token'] ?? '')) {
199
            throw $this->invalidConfigKey($name, 'token', 'string or null');
200
        }
201
202
        if (!\is_int($config['expires'] ?? 0)) {
203
            throw $this->invalidConfigKey($name, 'expires', 'positive int (unix timestamp)');
204
        }
205
206
        $client = new S3Client([
207
            'version'     => $config['version'] ?? 'latest',
208
            'region'      => $config['region'],
209
            'endpoint'    => $server['endpoint'] ?? null,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $server seems to never exist and therefore isset should always be false.
Loading history...
210
            'credentials' => new Credentials(
211
                $config['key'],
212
                $config['secret'],
213
                $config['token'] ?? null,
214
                $config['expires'] ?? null
215
            ),
216
        ]);
217
218
        return new S3SignedResolver($client, $config['bucket']);
219
    }
220
221
    /**
222
     * @param string $name
223
     * @param array $config
224
     * @return UriResolverInterface
225
     */
226
    private function createCloudFrontResolver(string $name, array $config): UriResolverInterface
227
    {
228
        if (!isset($config['key']) || !\is_string($config['key'])) {
229
            throw $this->invalidConfigKey($name, 'key', 'string');
230
        }
231
232
        if (!isset($config['private']) || !\is_string($config['private'])) {
233
            throw $this->invalidConfigKey($name, 'private', 'string value or path to key file');
234
        }
235
236
        if (!isset($config['domain']) || !\is_string($config['domain'])) {
237
            throw $this->invalidConfigKey($name, 'domain', 'string');
238
        }
239
240
        return new CloudFrontResolver(
241
            $config['key'],
242
            $config['private'],
243
            $config['domain']
244
        );
245
    }
246
247
    /**
248
     * @param string $name
249
     * @param array $config
250
     * @return UriResolverInterface
251
     */
252
    private function createStaticResolver(string $name, array $config): UriResolverInterface
253
    {
254
        if (!isset($config['uri']) || !\is_string($config['uri'])) {
255
            throw $this->invalidConfigKey($name, 'uri', 'string');
256
        }
257
258
        return new StaticResolver($this->createUri($name, $config['uri'], $config));
259
    }
260
261
    /**
262
     * @param string $name
263
     * @param string $uri
264
     * @param array $config
265
     * @return UriInterface
266
     */
267
    private function createUri(string $name, string $uri, array $config): UriInterface
268
    {
269
        if (!\is_string($config['factory'] ?? '')) {
270
            throw $this->invalidConfigKey($name, 'factory', 'string (PSR-7 uri factory implementation)');
271
        }
272
273
        switch (true) {
274
            case isset($config['factory']):
275
                /** @var UriFactoryInterface $factory */
276
                $factory = new $config['factory']();
277
278
                if (!$factory instanceof UriFactoryInterface) {
0 ignored issues
show
introduced by
$factory is always a sub-type of Psr\Http\Message\UriFactoryInterface.
Loading history...
279
                    $message = 'Distribution config driver `%s` should contain class that must be a valid PSR-7 ' .
280
                        'uri factory implementation, but `%s` given';
281
                    throw new InvalidArgumentException(\sprintf($message, $name, $config['factory']));
282
                }
283
284
                return $factory->createUri($uri);
285
286
            case \class_exists(LaminasUri::class):
287
                return new LaminasUri($uri);
288
289
            case \class_exists(GuzzleUri::class):
290
                return new GuzzleUri($uri);
291
292
            default:
293
                $message = 'Can not resolve available PSR-7 UriFactory implementation; ' .
294
                    'Please define `factory` config section in `%s` distribution driver config';
295
                throw new InvalidArgumentException(\sprintf($message, $name));
296
        }
297
    }
298
299
    /**
300
     * @param string $name
301
     * @param string $key
302
     * @param string $type
303
     * @return InvalidArgumentException
304
     */
305
    private function invalidConfigKey(string $name, string $key, string $type): InvalidArgumentException
306
    {
307
        $message = 'Distribution config of `%s` driver must contain key `%s` that must be a type of %s';
308
309
        return new InvalidArgumentException(\sprintf($message, $name, $key, $type));
310
    }
311
}
312