Passed
Pull Request — master (#407)
by Kirill
09:17 queued 03: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\ResolverInterface;
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, ResolverInterface>
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, ResolverInterface>
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 ResolverInterface
121
     */
122
    private function createResolver(string $name, array $config): ResolverInterface
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 ResolverInterface
150
     */
151
    private function createCustomResolver(string $type, string $name, array $config): ResolverInterface
152
    {
153
        if (!\is_subclass_of($type, ResolverInterface::class, true)) {
154
            throw $this->invalidConfigKey($name, 'type', ResolverInterface::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 ResolverInterface
173
     */
174
    private function createS3Resolver(string $name, array $config): ResolverInterface
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
            'credentials' => new Credentials(
210
                $config['key'],
211
                $config['secret'],
212
                $config['token'] ?? null,
213
                $config['expires'] ?? null
214
            ),
215
        ]);
216
217
        return new S3SignedResolver($client, $config['bucket']);
218
    }
219
220
    /**
221
     * @param string $name
222
     * @param array $config
223
     * @return ResolverInterface
224
     */
225
    private function createCloudFrontResolver(string $name, array $config): ResolverInterface
226
    {
227
        if (!isset($config['key']) || !\is_string($config['key'])) {
228
            throw $this->invalidConfigKey($name, 'key', 'string');
229
        }
230
231
        if (!isset($config['private']) || !\is_string($config['private'])) {
232
            throw $this->invalidConfigKey($name, 'private', 'string value or path to key file');
233
        }
234
235
        if (!isset($config['domain']) || !\is_string($config['domain'])) {
236
            throw $this->invalidConfigKey($name, 'domain', 'string');
237
        }
238
239
        return new CloudFrontResolver(
240
            $config['key'],
241
            $config['private'],
242
            $config['domain']
243
        );
244
    }
245
246
    /**
247
     * @param string $name
248
     * @param array $config
249
     * @return ResolverInterface
250
     */
251
    private function createStaticResolver(string $name, array $config): ResolverInterface
252
    {
253
        if (!isset($config['uri']) || !\is_string($config['uri'])) {
254
            throw $this->invalidConfigKey($name, 'uri', 'string');
255
        }
256
257
        return new StaticResolver($this->createUri($name, $config['uri'], $config));
258
    }
259
260
    /**
261
     * @param string $name
262
     * @param string $uri
263
     * @param array $config
264
     * @return UriInterface
265
     */
266
    private function createUri(string $name, string $uri, array $config): UriInterface
267
    {
268
        if (!\is_string($config['factory'] ?? '')) {
269
            throw $this->invalidConfigKey($name, 'factory', 'string (PSR-7 uri factory implementation)');
270
        }
271
272
        switch (true) {
273
            case isset($config['factory']):
274
                /** @var UriFactoryInterface $factory */
275
                $factory = new $config['factory']();
276
277
                if (!$factory instanceof UriFactoryInterface) {
0 ignored issues
show
introduced by
$factory is always a sub-type of Psr\Http\Message\UriFactoryInterface.
Loading history...
278
                    $message = 'Distribution config driver `%s` should contain class that must be a valid PSR-7 ' .
279
                        'uri factory implementation, but `%s` given';
280
                    throw new InvalidArgumentException(\sprintf($message, $name, $config['factory']));
281
                }
282
283
                return $factory->createUri($uri);
284
285
            case \class_exists(LaminasUri::class):
286
                return new LaminasUri($uri);
287
288
            case \class_exists(GuzzleUri::class):
289
                return new GuzzleUri($uri);
290
291
            default:
292
                $message = 'Can not resolve available PSR-7 UriFactory implementation; ' .
293
                    'Please define `factory` config section in `%s` distribution driver config';
294
                throw new InvalidArgumentException(\sprintf($message, $name));
295
        }
296
    }
297
298
    /**
299
     * @param string $name
300
     * @param string $key
301
     * @param string $type
302
     * @return InvalidArgumentException
303
     */
304
    private function invalidConfigKey(string $name, string $key, string $type): InvalidArgumentException
305
    {
306
        $message = 'Distribution config of `%s` driver must contain key `%s` that must be a type of %s';
307
308
        return new InvalidArgumentException(\sprintf($message, $name, $key, $type));
309
    }
310
}
311