Completed
Pull Request — master (#30)
by
unknown
03:51
created

AwsS3Provider::getCloudFront()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 0
1
<?php
2
3
namespace Rehmatworks\laravelcdn\Providers;
4
5
use Aws\S3\BatchDelete;
6
use Aws\S3\Exception\S3Exception;
7
use Aws\S3\S3Client;
8
use Illuminate\Support\Collection;
9
use Rehmatworks\laravelcdn\Contracts\CdnHelperInterface;
10
use Rehmatworks\laravelcdn\Providers\Contracts\ProviderInterface;
11
use Rehmatworks\laravelcdn\Validators\Contracts\ProviderValidatorInterface;
12
use Symfony\Component\Console\Output\ConsoleOutput;
13
14
/**
15
 * Class AwsS3Provider
16
 * Amazon (AWS) S3.
17
 *
18
 *
19
 * @category Driver
20
 *
21
 * @property string  $provider_url
22
 * @property string  $threshold
23
 * @property string  $version
24
 * @property string  $region
25
 * @property string  $credential_key
26
 * @property string  $credential_secret
27
 * @property string  $buckets
28
 * @property string  $acl
29
 * @property string  $cloudfront
30
 * @property string  $cloudfront_url
31
 * @property string $http
32
 *
33
 * @author   Mahmoud Zalt <[email protected]>
34
 */
35
class AwsS3Provider extends Provider implements ProviderInterface
36
{
37
    /**
38
     * All the configurations needed by this class with the
39
     * optional configurations default values.
40
     *
41
     * @var array
42
     */
43
    protected $default = [
44
        'url' => null,
45
        'threshold' => 10,
46
        'providers' => [
47
            'aws' => [
48
                's3' => [
49
                    'version' => null,
50
                    'region' => null,
51
                    'endpoint' => null,
52
                    'buckets' => null,
53
                    'upload_folder' => '',
54
                    'http' => null,
55
                    'acl' => 'public-read',
56
                    'cloudfront' => [
57
                        'use' => false,
58
                        'cdn_url' => null,
59
                    ],
60
                ],
61
            ],
62
        ],
63
    ];
64
65
    /**
66
     * Required configurations (must exist in the config file).
67
     *
68
     * @var array
69
     */
70
    protected $rules = ['version', 'endpoint', 'region', 'key', 'secret', 'buckets', 'url'];
71
72
    /**
73
     * this array holds the parsed configuration to be used across the class.
74
     *
75
     * @var Array
76
     */
77
    protected $supplier;
78
79
    /**
80
     * @var Instance of Aws\S3\S3Client
81
     */
82
    protected $s3_client;
83
84
    /**
85
     * @var Instance of Guzzle\Batch\BatchBuilder
86
     */
87
    protected $batch;
88
89
    /**
90
     * @var \Rehmatworks\laravelcdn\Contracts\CdnHelperInterface
91
     */
92
    protected $cdn_helper;
93
94
    /**
95
     * @var \Rehmatworks\laravelcdn\Validators\Contracts\ConfigurationsInterface
96
     */
97
    protected $configurations;
98
99
    /**
100
     * @var \Rehmatworks\laravelcdn\Validators\Contracts\ProviderValidatorInterface
101
     */
102
    protected $provider_validator;
103
104
    /**
105
     * @param \Symfony\Component\Console\Output\ConsoleOutput $console
106
     * @param \Rehmatworks\laravelcdn\Validators\Contracts\ProviderValidatorInterface $provider_validator
107
     * @param \Rehmatworks\laravelcdn\Contracts\CdnHelperInterface                    $cdn_helper
108
     */
109
    public function __construct(
110
        ConsoleOutput $console,
111
        ProviderValidatorInterface $provider_validator,
112
        CdnHelperInterface $cdn_helper
113
    ) {
114
        $this->console = $console;
0 ignored issues
show
Documentation Bug introduced by
It seems like $console of type object<Symfony\Component...e\Output\ConsoleOutput> is incompatible with the declared type object<Rehmatworks\laravelcdn\Providers\Instance> of property $console.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
115
        $this->provider_validator = $provider_validator;
116
        $this->cdn_helper = $cdn_helper;
117
    }
118
119
    /**
120
     * Read the configuration and prepare an array with the relevant configurations
121
     * for the (AWS S3) provider. and return itself.
122
     *
123
     * @param $configurations
124
     *
125
     * @return $this
126
     */
127
    public function init($configurations)
128
    {
129
        // merge the received config array with the default configurations array to
130
        // fill missed keys with null or default values.
131
        $this->default = array_replace_recursive($this->default, $configurations);
132
133
        $supplier = [
134
            'provider_url' => $this->default['url'],
135
            'threshold' => $this->default['threshold'],
136
            'version' => $this->default['providers']['aws']['s3']['version'],
137
            'region' => $this->default['providers']['aws']['s3']['region'],
138
            'endpoint' => $this->default['providers']['aws']['s3']['endpoint'],
139
            'buckets' => $this->default['providers']['aws']['s3']['buckets'],
140
            'acl' => $this->default['providers']['aws']['s3']['acl'],
141
            'cloudfront' => $this->default['providers']['aws']['s3']['cloudfront']['use'],
142
            'cloudfront_url' => $this->default['providers']['aws']['s3']['cloudfront']['cdn_url'],
143
            'http' => $this->default['providers']['aws']['s3']['http'],
144
            'upload_folder' => $this->default['providers']['aws']['s3']['upload_folder']
145
        ];
146
147
        // check if any required configuration is missed
148
        $this->provider_validator->validate($supplier, $this->rules);
149
150
        $this->supplier = $supplier;
151
152
        return $this;
153
    }
154
155
    /**
156
     * Upload assets.
157
     *
158
     * @param $assets
159
     *
160
     * @return bool
161
     */
162
    public function upload($assets)
163
    {
164
        // connect before uploading
165
        $connected = $this->connect();
166
167
        if (!$connected) {
168
            return false;
169
        }
170
171
        // user terminal message
172
        $this->console->writeln('<fg=yellow>Comparing local files and bucket...</fg=yellow>');
173
174
        $assets = $this->getFilesAlreadyOnBucket($assets);
175
176
        // upload each asset file to the CDN
177
        if (count($assets) > 0) {
178
            $this->console->writeln('<fg=yellow>Upload in progress......</fg=yellow>');
179
            foreach ($assets as $file) {
180
                try {
181
                    $this->console->writeln('<fg=cyan>'.'Uploading file path: '.$file->getRealpath().'</fg=cyan>');
182
                    $command = $this->s3_client->getCommand('putObject', [
183
184
                        // the bucket name
185
                        'Bucket' => $this->getBucket(),
186
                        // the path of the file on the server (CDN)
187
                        'Key' => $this->supplier['upload_folder'] . str_replace('\\', '/', $file->getPathName()),
188
                        // the path of the path locally
189
                        'Body' => fopen($file->getRealPath(), 'r'),
190
                        // the permission of the file
191
192
                        'ACL' => $this->acl,
193
                        'CacheControl' => $this->default['providers']['aws']['s3']['cache-control'],
194
                        'Metadata' => $this->default['providers']['aws']['s3']['metadata'],
195
                        'Expires' => $this->default['providers']['aws']['s3']['expires'],
196
                    ]);
197
//                var_dump(get_class($command));exit();
0 ignored issues
show
Unused Code Comprehensibility introduced by
77% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
198
199
200
                    $this->s3_client->execute($command);
201
                } catch (S3Exception $e) {
202
                    $this->console->writeln('<fg=red>'.$e->getMessage().'</fg=red>');
203
204
                    return false;
205
                }
206
            }
207
208
            // user terminal message
209
            $this->console->writeln('<fg=green>Upload completed successfully.</fg=green>');
210
        } else {
211
            // user terminal message
212
            $this->console->writeln('<fg=yellow>No new files to upload.</fg=yellow>');
213
        }
214
215
        return true;
216
    }
217
218
    /**
219
     * Create an S3 client instance
220
     * (Note: it will read the credentials form the .env file).
221
     *
222
     * @return bool
223
     */
224
    public function connect()
225
    {
226
        try {
227
            // Instantiate an S3 client
228
            $this->setS3Client(new S3Client([
229
                        'version' => $this->supplier['version'],
230
                        'region' => $this->supplier['region'],
231
                        'endpoint' => $this->supplier['endpoint'],
232
                        'http' => $this->supplier['http']
233
                    ]
234
                )
235
            );
236
        } catch (\Exception $e) {
237
            return false;
238
        }
239
240
        return true;
241
    }
242
243
    /**
244
     * @param $s3_client
245
     */
246
    public function setS3Client($s3_client)
247
    {
248
        $this->s3_client = $s3_client;
249
    }
250
251
    /**
252
     * @param $assets
253
     * @return mixed
254
     */
255
    private function getFilesAlreadyOnBucket($assets)
256
    {
257
        $filesOnAWS = new Collection([]);
258
259
        $files = $this->s3_client->listObjects([
260
            'Bucket' => $this->getBucket(),
261
        ]);
262
263
        if (!$files['Contents']) {
264
            //no files on bucket. lets upload everything found.
265
            return $assets;
266
        }
267
268
        foreach ($files['Contents'] as $file) {
269
            $a = [
270
                'Key' => $file['Key'],
271
                "LastModified" => $file['LastModified']->getTimestamp(),
272
                'Size' => $file['Size']
273
            ];
274
            $filesOnAWS->put($file['Key'], $a);
275
        }
276
277
        $assets->transform(function ($item, $key) use (&$filesOnAWS) {
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
278
            $fileOnAWS = $filesOnAWS->get(str_replace('\\', '/', $item->getPathName()));
279
280
            //select to upload files that are different in size AND last modified time.
281
            if (!($item->getMTime() === $fileOnAWS['LastModified']) && !($item->getSize() === $fileOnAWS['Size'])) {
282
                return $item;
283
            }
284
        });
285
286
        $assets = $assets->reject(function ($item) {
287
            return $item === null;
288
        });
289
290
        return $assets;
291
    }
292
293
    /**
294
     * @return array
295
     */
296
    public function getBucket()
297
    {
298
        // this step is very important, "always assign returned array from
299
        // magical function to a local variable if you need to modify it's
300
        // state or apply any php function on it." because the returned is
301
        // a copy of the original variable. this prevent this error:
302
        // Indirect modification of overloaded property
303
        // Vinelab\Cdn\Providers\AwsS3Provider::$buckets has no effect
304
        $bucket = $this->buckets;
305
306
        return rtrim(key($bucket), '/');
307
    }
308
309
    /**
310
     * Empty bucket.
311
     *
312
     * @return bool
313
     */
314
    public function emptyBucket()
315
    {
316
317
        // connect before uploading
318
        $connected = $this->connect();
319
320
        if (!$connected) {
321
            return false;
322
        }
323
324
        // user terminal message
325
        $this->console->writeln('<fg=yellow>Emptying in progress...</fg=yellow>');
326
327
        try {
328
329
            // Get the contents of the bucket for information purposes
330
            $contents = $this->s3_client->listObjects([
331
                'Bucket' => $this->getBucket(),
332
                'Key' => '',
333
            ]);
334
335
            // Check if the bucket is already empty
336
            if (!$contents['Contents']) {
337
                $this->console->writeln('<fg=green>The bucket '.$this->getBucket().' is already empty.</fg=green>');
338
339
                return true;
340
            }
341
342
            // Empty out the bucket
343
            $empty = BatchDelete::fromListObjects($this->s3_client, [
344
                'Bucket' => $this->getBucket(),
345
                'Prefix' => null,
346
            ]);
347
348
            $empty->delete();
349
        } catch (S3Exception $e) {
350
            $this->console->writeln('<fg=red>'.$e->getMessage().'</fg=red>');
351
352
            return false;
353
        }
354
355
        $this->console->writeln('<fg=green>The bucket '.$this->getBucket().' is now empty.</fg=green>');
356
357
        return true;
358
    }
359
360
    /**
361
     * This function will be called from the CdnFacade class when
362
     * someone use this {{ Cdn::asset('') }} facade helper.
363
     *
364
     * @param $path
365
     *
366
     * @return string
367
     */
368
    public function urlGenerator($path)
369
    {
370
        if ($this->getCloudFront() === true) {
371
            $url = $this->cdn_helper->parseUrl($this->getCloudFrontUrl());
372
373
            return $url['scheme'] . '://' . $url['host'] . '/' . $path;
374
        }
375
376
        $url = $this->cdn_helper->parseUrl($this->getUrl());
377
378
        $bucket = $this->getBucket();
379
        $bucket = (!empty($bucket)) ? $bucket.'.' : '';
380
381
        return $url['scheme'] . '://' . $bucket . $url['host'] . '/' . $path;
382
    }
383
384
    /**
385
     * @return string
386
     */
387
    public function getCloudFront()
388
    {
389
        if (!is_bool($cloudfront = $this->cloudfront)) {
390
            return false;
391
        }
392
393
        return $cloudfront;
394
    }
395
396
    /**
397
     * @return string
398
     */
399
    public function getCloudFrontUrl()
400
    {
401
        return rtrim($this->cloudfront_url, '/').'/';
402
    }
403
404
    /**
405
     * @return string
406
     */
407
    public function getUrl()
408
    {
409
        return rtrim($this->provider_url, '/') . '/';
410
    }
411
412
    /**
413
     * @param $attr
414
     *
415
     * @return Mix | null
416
     */
417
    public function __get($attr)
418
    {
419
        return isset($this->supplier[$attr]) ? $this->supplier[$attr] : null;
420
    }
421
}
422