Completed
Push — master ( 9d09a9...8a52a0 )
by Linus
05:24
created

S3BucketStreamZip   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 163
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 22
c 0
b 0
f 0
lcom 1
cbo 5
dl 0
loc 163
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 22 3
A bucket() 0 6 1
A prefix() 0 6 1
A addParams() 0 8 2
B send() 0 32 4
A parts() 0 23 3
A validateAuth() 0 12 3
B doesDirectoryExist() 0 17 5
1
<?php
2
3
namespace limenet\S3BucketStreamZip;
4
5
use Aws\S3\Exception\S3Exception;
6
use Aws\S3\S3Client;
7
use limenet\S3BucketStreamZip\Exception\InvalidParameterException;
8
use ZipStream\ZipStream;
9
10
class S3BucketStreamZip
11
{
12
    public const MAX_ARCHIVE_SIZE = 2147483648; // 2^30 * 2 = 2 GB
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% 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...
13
14
    protected $auth = [];
15
16
    protected $s3Client;
17
18
    protected $params;
19
20
    protected $part;
21
22
    /**
23
     * Create a new ZipStream object.
24
     *
25
     * @param array $auth - AWS key and secret
26
     * @throws InvalidParameterException
27
     */
28
    public function __construct($auth, $part = 0)
29
    {
30
        $this->validateAuth($auth);
31
32
        $this->auth = $auth;
33
        $this->part = $part;
34
35
        // S3 User in $this->auth should have permission to execute ListBucket on any buckets
36
        // AND GetObject on any object with which you need to interact.
37
        $this->s3Client = new S3Client([
38
            'version'     => (isset($this->auth['version'])) ? $this->auth['version'] : 'latest',
39
            'region'      => (isset($this->auth['region'])) ? $this->auth['region'] : 'us-east-1',
40
            'credentials' => [
41
                'key'    => $this->auth['key'],
42
                'secret' => $this->auth['secret'],
43
            ],
44
        ]);
45
46
        // Register the stream wrapper from an S3Client object
47
        // This allows you to access buckets and objects stored in Amazon S3 using the s3:// protocol
48
        $this->s3Client->registerStreamWrapper();
49
    }
50
51
    public function bucket($bucket)
52
    {
53
        $this->params = new S3Params($bucket);
54
55
        return $this;
56
    }
57
58
    public function prefix($prefix)
59
    {
60
        $this->params->setParam('Prefix', rtrim($prefix, '/') . '/');
61
62
        return $this;
63
    }
64
65
    public function addParams(array $params)
66
    {
67
        foreach($params as $key => $value) {
68
            $this->params->setParam($key, $value);
69
        }
70
71
        return $this;
72
    }
73
74
    /**
75
     * Stream a zip file to the client
76
     *
77
     * @param string $filename - Name for the file to be sent to the client
78
     * $filename will be what is sent in the content-disposition header
79
     * @throws InvalidParameterException
80
     * @internal param array - See the documentation for the List Objects API for valid parameters.
81
     * Only `Bucket` is required.
82
     *
83
     * http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGET.html
84
     */
85
    public function send($filename)
86
    {
87
        $params = $this->params->getParams();
88
89
        $this->doesDirectoryExist($params);
90
91
        $zip = new ZipStream($filename);
92
        // The iterator fetches ALL of the objects without having to manually loop over responses.
93
        $files = $this->s3Client->getIterator('ListObjects', $params);
0 ignored issues
show
Unused Code introduced by
$files is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
94
95
        $parts = $this->parts();
96
97
        // Add each object from the ListObjects call to the new zip file.
98
        foreach ($parts[$this->part] as $file) {
99
            var_dump($file['Size']);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($file['Size']); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
100
            // Get the file name on S3 so we can save it to the zip file using the same name.
101
            $fileName = basename($file['Key']);
102
103
            if (is_file("s3://{$params['Bucket']}/{$file['Key']}")) {
104
                $context = stream_context_create([
105
                    's3' => ['seekable' => true]
106
                ]);
107
                // open seekable(!) stream
108
                if ($stream = fopen("s3://{$params['Bucket']}/{$file['Key']}", 'r', false, $context)) {
109
                    $zip->addFileFromStream($fileName, $stream);
110
                }
111
            }
112
        }
113
114
        // Finalize the zip file.
115
        $zip->finish();
116
    }
117
118
    public function parts()
119
    {
120
        $params = $this->params->getParams();
121
122
        $this->doesDirectoryExist($params);
123
124
        $files = $this->s3Client->getIterator('ListObjects', $params);
125
126
        $parts = [0 => []];
127
        $partSizes = [0 => 0];
128
        $currentPart = 0;
129
        foreach ($files as $file) {
130
            if ($partSizes[$currentPart] + $file['Size'] > self::MAX_ARCHIVE_SIZE) {
131
                $currentPart++;
132
                $parts[$currentPart] = [];
133
                $partSizes[$currentPart] = 0;
134
            }
135
            $parts[$currentPart][] = $file;
136
            $partSizes[$currentPart] += $file['Size'];
137
        };
138
139
        return $parts;
140
    }
141
142
    protected function validateAuth($auth)
143
    {
144
        // We require the AWS key to be passed in $auth.
145
        if (!isset($auth['key'])) {
146
            throw new InvalidParameterException('$auth parameter to constructor requires a `key` attribute');
147
        }
148
149
        // We require the AWS secret to be passed in $auth.
150
        if (!isset($auth['secret'])) {
151
            throw new InvalidParameterException('$auth parameter to constructor requires a `secret` attribute');
152
        }
153
    }
154
155
    protected function doesDirectoryExist($params)
156
    {
157
        $command = $this->s3Client->getCommand('listObjects', $params);
158
159
        try {
160
            $result = $this->s3Client->execute($command);
161
162
            if (empty($result['Contents']) && empty($result['CommonPrefixes'])) {
163
                throw new InvalidParameterException('Bucket or Prefix does not exist');
164
            }
165
        } catch (S3Exception $e) {
166
            if ($e->getStatusCode() === 403) {
167
                return false;
168
            }
169
            throw $e;
170
        }
171
    }
172
}
173