Completed
Push — feature-20rc1 ( 008ae2 )
by Rob
16:55
created

AwsS3Resolver::getObjectSearchFilters()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 2
eloc 6
nc 1
nop 1
1
<?php
2
3
/*
4
 * This file is part of the `liip/LiipImagineBundle` project.
5
 *
6
 * (c) https://github.com/liip/LiipImagineBundle/graphs/contributors
7
 *
8
 * For the full copyright and license information, please view the LICENSE.md
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Liip\ImagineBundle\Imagine\Cache\Resolver;
13
14
use Aws\CommandInterface;
15
use Aws\S3\Exception\S3Exception;
16
use Aws\S3\S3Client;
17
use Liip\ImagineBundle\Exception\Imagine\Cache\Resolver\NotStorableException;
18
use Liip\ImagineBundle\File\FileInterface;
19
use Liip\ImagineBundle\Log\LoggerAwareTrait;
20
use Psr\Log\LoggerInterface;
21
22
class AwsS3Resolver implements ResolverInterface
23
{
24
    use LoggerAwareTrait;
25
26
    /**
27
     * @var S3Client
28
     */
29
    protected $storage;
30
31
    /**
32
     * @var string
33
     */
34
    protected $bucket;
35
36
    /**
37
     * @var string
38
     */
39
    protected $acl;
40
41
    /**
42
     * @var array
43
     */
44
    protected $getOptions;
45
46
    /**
47
     * @var array
48
     */
49
    protected $putOptions;
50
51
    /**
52
     * @var array
53
     */
54
    protected $delOptions;
55
56
    /**
57
     * @var LoggerInterface
58
     */
59
    protected $logger;
60
61
    /**
62
     * @var string
63
     */
64
    protected $cachePrefix;
65
66
    /**
67
     * @param S3Client    $storage     The Amazon S3 storage API. It's required to know authentication information
68
     * @param string      $bucket      The bucket name to operate on
69
     * @param string|null $acl         The ACL to use when storing new objects. Default: owner read/write, public read
70
     * @param array       $getOptions  A list of options to be passed when retrieving an object url from Amazon S3
71
     * @param array       $putOptions  A list of options to be passed when saving an object to Amazon S3
72
     * @param array       $delOptions  A list of options to be passed when removing an object from Amazon S3
73
     * @param string|null $cachePrefix A cache prefix string
74
     */
75
    public function __construct(
76
        S3Client $storage,
77
        string $bucket,
78
        string $acl = null,
79
        array $getOptions = [],
80
        array $putOptions = [],
81
        array $delOptions = [],
82
        string $cachePrefix = null
83
    ) {
84
        $this->storage = $storage;
85
        $this->bucket = $bucket;
86
        $this->acl = $acl ?: 'public-read';
87
        $this->getOptions = $getOptions;
88
        $this->putOptions = $putOptions;
89
        $this->delOptions = $delOptions;
90
        $this->cachePrefix = $cachePrefix;
91
    }
92
93
    /**
94
     * {@inheritdoc}
95
     */
96
    public function isStored(string $path, string $filter): bool
97
    {
98
        return $this->objectExists($this->getObjectPath($path, $filter));
99
    }
100
101
    /**
102
     * {@inheritdoc}
103
     */
104
    public function resolve(string $path, string $filter): string
105
    {
106
        return $this->getObjectUrl($this->getObjectPath($path, $filter));
107
    }
108
109
    /**
110
     * {@inheritdoc}
111
     */
112
    public function store(FileInterface $file, string $path, string $filter): void
113
    {
114
        $this->putObjectPath($this->getObjectPath($path, $filter), $file);
115
    }
116
117
    /**
118
     * {@inheritdoc}
119
     */
120
    public function remove(array $paths, array $filters): void
121
    {
122
        if (empty($paths) && !empty($filters)) {
123
            $this->delMatchingObjectPaths($filters);
124
        } else {
125
            foreach ($filters as $f) {
126
                foreach ($paths as $p) {
127
                    $this->delObjectPath($this->getObjectPath($p, $f));
128
                }
129
            }
130
        }
131
    }
132
133
    /**
134
     * Returns the object path within the bucket.
135
     *
136
     * @param string $path   The base path of the resource
137
     * @param string $filter The name of the imagine filter in effect
138
     *
139
     * @return string The path of the object on S3
140
     */
141
    protected function getObjectPath($path, $filter)
142
    {
143
        $path = $this->cachePrefix
144
            ? sprintf('%s/%s/%s', $this->cachePrefix, $filter, $path)
145
            : sprintf('%s/%s', $filter, $path);
146
147
        return str_replace('//', '/', $path);
148
    }
149
150
    /**
151
     * Returns the URL for an object saved on Amazon S3.
152
     *
153
     * @param string $path
154
     *
155
     * @return string
156
     */
157
    protected function getObjectUrl($path)
158
    {
159
        $command = $this->storage->getCommand('GetObject', array_merge($this->getOptions, [
160
            'Bucket' => $this->bucket,
161
            'Key' => $path,
162
        ]));
163
164
        return (string) (
165
            $command instanceof CommandInterface ? \Aws\serialize($command)->getUri() : $command
166
        );
167
    }
168
169
    /**
170
     * @param array $filters
171
     *
172
     * @return string
173
     */
174
    protected function getObjectSearchFilters(array $filters)
175
    {
176
        return vsprintf('/%s(%s)/i', [
177
            $this->cachePrefix ? preg_quote(sprintf('%s/', $this->cachePrefix), '/') : '',
178
            implode('|', array_map(function (string $f): string {
179
                return preg_quote($f, '/');
180
            }, $filters)),
181
        ]);
182
    }
183
184
    /**
185
     * Checks whether an object exists.
186
     *
187
     * @param string $objectPath
188
     *
189
     * @return bool
190
     */
191
    protected function objectExists($objectPath)
192
    {
193
        return $this->storage->doesObjectExist($this->bucket, $objectPath);
194
    }
195
196
    /**
197
     * @param string        $path
198
     * @param FileInterface $file
199
     *
200
     * @throws S3Exception
201
     */
202
    private function putObjectPath(string $path, FileInterface $file): void
203
    {
204
        try {
205
            $this->storage->putObject(array_merge($this->putOptions, [
206
                'ACL' => $this->acl,
207
                'Bucket' => $this->bucket,
208
                'Key' => $path,
209
                'Body' => $file->getContents(),
210
                'ContentType' => (string) $file->getContentType(),
211
            ]));
212
        } catch (S3Exception $exception) {
213
            $this->logger->error('The object "%path%" could not be created on AWS S3 bucket "%bucket%".', [
214
                'path' => $path,
215
                'bucket' => $this->bucket,
216
                'exception' => $exception,
217
            ]);
218
219
            throw new NotStorableException(
220
                'The object "%s" could not be created on AWS S3 bucket "%s".', $path, $this->bucket, $exception
221
            );
222
        }
223
    }
224
225
    /**
226
     * @param string $object
227
     */
228
    private function delObjectPath(string $object): void
229
    {
230
        if (!$this->objectExists($object)) {
231
            return;
232
        }
233
234
        try {
235
            $this->storage->deleteObject(array_merge($this->delOptions, [
236
                'Bucket' => $this->bucket,
237
                'Key' => $object,
238
            ]));
239
        } catch (S3Exception $exception) {
240
            $this->logger->error('The object "%path%" could not be deleted from AWS S3 bucket "%bucket%".', [
241
                'path' => $object,
242
                'bucket' => $this->bucket,
243
                'exception' => $exception,
244
            ]);
245
        }
246
    }
247
248
    /**
249
     * @param array $filters
250
     */
251
    private function delMatchingObjectPaths(array $filters): void
252
    {
253
        try {
254
            $this->storage->deleteMatchingObjects($this->bucket, null, $this->getObjectSearchFilters($filters));
255
        } catch (S3Exception $exception) {
256
            $this->logger->error('The objects matching "%regex%" could not be deleted from AWS S3 bucket "%bucket%".', [
257
                'regex' => $this->getObjectSearchFilters($filters),
258
                'bucket' => $this->bucket,
259
                'exception' => $exception,
260
            ]);
261
        }
262
    }
263
}
264