CloudFront::getFlushStatus()   A
last analyzed

Complexity

Conditions 2
Paths 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.8333
c 0
b 0
f 0
cc 2
nc 3
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\MediaBundle\CDN;
15
16
use Aws\CloudFront\CloudFrontClient;
17
use Aws\CloudFront\Exception\CloudFrontException;
18
19
/**
20
 * From http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html.
21
 *
22
 * Invalidating Objects (Web Distributions Only)
23
 * If you need to remove an object from CloudFront edge-server caches before it
24
 * expires, you can do one of the following:
25
 * Invalidate the object. The next time a viewer requests the object, CloudFront
26
 * returns to the origin to fetch the latest version of the object.
27
 * Use object versioning to serve a different version of the object that has a
28
 * different name. For more information, see Updating Existing Objects Using
29
 * Versioned Object Names.
30
 * Important:
31
 * You can invalidate most types of objects that are served by a web
32
 * distribution, but you cannot invalidate media files in the Microsoft Smooth
33
 * Streaming format when you have enabled Smooth Streaming for the corresponding
34
 * cache behavior. In addition, you cannot invalidate objects that are served by
35
 * an RTMP distribution. You can invalidate a specified number of objects each
36
 * month for free. Above that limit, you pay a fee for each object that you
37
 * invalidate. For example, to invalidate a directory and all of the files in
38
 * the directory, you must invalidate the directory and each file individually.
39
 * If you need to invalidate a lot of files, it might be easier and less
40
 * expensive to create a new distribution and change your object paths to refer
41
 * to the new distribution. For more information about the charges for
42
 * invalidation, see Paying for Object Invalidation.
43
 *
44
 * @final since sonata-project/media-bundle 3.21.0
45
 *
46
 * @uses \CloudFrontClient for stablish connection with CloudFront service
47
 *
48
 * @see http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.htmlInvalidating Objects (Web Distributions Only)
49
 *
50
 * @author Javier Spagnoletti <[email protected]>
51
 */
52
class CloudFront implements CDNInterface
53
{
54
    /**
55
     * @var string
56
     */
57
    protected $path;
58
59
    /**
60
     * @var string
61
     */
62
    protected $key;
63
64
    /**
65
     * @var string
66
     */
67
    protected $secret;
68
69
    /**
70
     * @var string
71
     */
72
    protected $distributionId;
73
74
    /**
75
     * @var CloudFrontClient
76
     */
77
    protected $client;
78
79
    /**
80
     * @param string $path
81
     * @param string $key
82
     * @param string $secret
83
     * @param string $distributionId
84
     */
85
    public function __construct($path, $key, $secret, $distributionId)
86
    {
87
        $this->path = $path;
88
        $this->key = $key;
89
        $this->secret = $secret;
90
        $this->distributionId = $distributionId;
91
    }
92
93
    public function getPath($relativePath, $isFlushable = false)
94
    {
95
        return sprintf('%s/%s', rtrim($this->path, '/'), ltrim($relativePath, '/'));
96
    }
97
98
    public function flushByString($string)
99
    {
100
        return $this->flushPaths([$string]);
101
    }
102
103
    public function flush($string)
104
    {
105
        return $this->flushPaths([$string]);
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     *
111
     * @see http://docs.aws.amazon.com/aws-sdk-php/latest/class-Aws.CloudFront.CloudFrontClient.html#_createInvalidation
112
     */
113
    public function flushPaths(array $paths)
114
    {
115
        if (empty($paths)) {
116
            throw new \RuntimeException('Unable to flush : expected at least one path');
117
        }
118
        // Normalizes paths due possible typos since all the CloudFront's
119
        // objects starts with a leading slash
120
        $normalizedPaths = array_map(static function ($path) {
121
            return '/'.ltrim($path, '/');
122
        }, $paths);
123
124
        try {
125
            $result = $this->getClient()->createInvalidation([
126
                'DistributionId' => $this->distributionId,
127
                'Paths' => [
128
                    'Quantity' => \count($normalizedPaths),
129
                    'Items' => $normalizedPaths,
130
                ],
131
                'CallerReference' => $this->getCallerReference($normalizedPaths),
132
            ]);
133
134
            if (!\in_array($status = $result->get('Status'), ['Completed', 'InProgress'], true)) {
135
                throw new \RuntimeException('Unable to flush : '.$status);
136
            }
137
138
            return $result->get('Id');
139
        } catch (CloudFrontException $ex) {
140
            throw new \RuntimeException('Unable to flush : '.$ex->getMessage());
141
        }
142
    }
143
144
    /**
145
     * For testing only.
146
     */
147
    public function setClient(CloudFrontClient $client): void
148
    {
149
        $this->client = $client;
150
    }
151
152
    public function getFlushStatus($identifier)
153
    {
154
        try {
155
            $result = $this->getClient()->getInvalidation([
156
                'DistributionId' => $this->distributionId,
157
                'Id' => $identifier,
158
            ]);
159
160
            return array_search($result->get('Status'), self::getStatusList(), true);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression array_search($result->ge...getStatusList(), true); of type false|integer adds false to the return on line 160 which is incompatible with the return type declared by the interface Sonata\MediaBundle\CDN\C...terface::getFlushStatus of type string. It seems like you forgot to handle an error condition.
Loading history...
161
        } catch (CloudFrontException $ex) {
162
            throw new \RuntimeException('Unable to retrieve flush status : '.$ex->getMessage());
163
        }
164
    }
165
166
    /**
167
     * @static
168
     *
169
     * @return string[]
170
     */
171
    public static function getStatusList()
172
    {
173
        // @todo: check for a complete list of available CloudFront statuses
174
        return [
175
            self::STATUS_OK => 'Completed',
176
            self::STATUS_TO_SEND => 'STATUS_TO_SEND',
177
            self::STATUS_TO_FLUSH => 'STATUS_TO_FLUSH',
178
            self::STATUS_ERROR => 'STATUS_ERROR',
179
            self::STATUS_WAITING => 'InProgress',
180
        ];
181
    }
182
183
    /**
184
     * Generates a valid caller reference from given paths regardless its order.
185
     *
186
     * @return string a md5 representation
187
     */
188
    protected function getCallerReference(array $paths)
189
    {
190
        sort($paths);
191
192
        return md5(implode(',', $paths));
193
    }
194
195
    private function getClient(): CloudFrontClient
196
    {
197
        if (!$this->client) {
198
            $this->client = CloudFrontClient::factory([
199
                'key' => $this->key,
200
                'secret' => $this->secret,
201
            ]);
202
        }
203
204
        return $this->client;
205
    }
206
}
207