Completed
Pull Request — master (#3)
by Linus
10:16
created

S3BucketStreamZip   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 166
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 31.43%

Importance

Changes 0
Metric Value
wmc 22
lcom 1
cbo 5
dl 0
loc 166
rs 10
c 0
b 0
f 0
ccs 11
cts 35
cp 0.3143

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
     *
27
     * @throws InvalidParameterException
28
     */
29
    public function __construct($auth, $part = 0)
30
    {
31
        $this->validateAuth($auth);
32
33
        $this->auth = $auth;
34
        $this->part = $part;
35
36
        // S3 User in $this->auth should have permission to execute ListBucket on any buckets
37
        // AND GetObject on any object with which you need to interact.
38
        $this->s3Client = new S3Client([
39
            'version'     => (isset($this->auth['version'])) ? $this->auth['version'] : 'latest',
40
            'region'      => (isset($this->auth['region'])) ? $this->auth['region'] : 'us-east-1',
41
            'credentials' => [
42
                'key'    => $this->auth['key'],
43
                'secret' => $this->auth['secret'],
44
            ],
45
        ]);
46
47
        // Register the stream wrapper from an S3Client object
48
        // This allows you to access buckets and objects stored in Amazon S3 using the s3:// protocol
49
        $this->s3Client->registerStreamWrapper();
50
    }
51
52
    public function bucket($bucket)
53
    {
54
        $this->params = new S3Params($bucket);
55
56
        return $this;
57
    }
58
59 4
    public function prefix($prefix)
60
    {
61
        $this->params->setParam('Prefix', rtrim($prefix, '/').'/');
62 4
63 4
        return $this;
64
    }
65
66 3
    public function addParams(array $params)
67 3
    {
68
        foreach ($params as $key => $value) {
69
            $this->params->setParam($key, $value);
70 2
        }
71 2
72
        return $this;
73 1
    }
74 1
75
    /**
76 1
     * Stream a zip file to the client.
77 1
     *
78
     * @param string $filename - Name for the file to be sent to the client
79
     *                         $filename will be what is sent in the content-disposition header
80
     *
81
     * @throws InvalidParameterException
82
     *
83
     * @internal param array - See the documentation for the List Objects API for valid parameters.
84
     * Only `Bucket` is required.
85
     *
86
     * http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGET.html
87
     */
88
    public function send($filename)
89
    {
90
        $params = $this->params->getParams();
91
92
        $this->doesDirectoryExist($params);
93
94
        $zip = new ZipStream($filename);
95
        // The iterator fetches ALL of the objects without having to manually loop over responses.
96
        $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...
97
98
        $parts = $this->parts();
99
100
        // Add each object from the ListObjects call to the new zip file.
101
        foreach ($parts[$this->part] as $file) {
102
            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...
103
            // Get the file name on S3 so we can save it to the zip file using the same name.
104
            $fileName = basename($file['Key']);
105
106
            if (is_file("s3://{$params['Bucket']}/{$file['Key']}")) {
107
                $context = stream_context_create([
108
                    's3' => ['seekable' => true],
109
                ]);
110
                // open seekable(!) stream
111
                if ($stream = fopen("s3://{$params['Bucket']}/{$file['Key']}", 'r', false, $context)) {
112
                    $zip->addFileFromStream($fileName, $stream);
113
                }
114
            }
115
        }
116
117
        // Finalize the zip file.
118
        $zip->finish();
119
    }
120
121
    public function parts()
122
    {
123
        $params = $this->params->getParams();
124
125
        $this->doesDirectoryExist($params);
126
127
        $files = $this->s3Client->getIterator('ListObjects', $params);
128
129
        $parts = [0 => []];
130
        $partSizes = [0 => 0];
131
        $currentPart = 0;
132
        foreach ($files as $file) {
133
            if ($partSizes[$currentPart] + $file['Size'] > self::MAX_ARCHIVE_SIZE) {
134
                $currentPart++;
135
                $parts[$currentPart] = [];
136
                $partSizes[$currentPart] = 0;
137
            }
138
            $parts[$currentPart][] = $file;
139
            $partSizes[$currentPart] += $file['Size'];
140
        }
141
142
        return $parts;
143
    }
144
145
    protected function validateAuth($auth)
146
    {
147
        // We require the AWS key to be passed in $auth.
148
        if (!isset($auth['key'])) {
149
            throw new InvalidParameterException('$auth parameter to constructor requires a `key` attribute');
150
        }
151
152
        // We require the AWS secret to be passed in $auth.
153
        if (!isset($auth['secret'])) {
154
            throw new InvalidParameterException('$auth parameter to constructor requires a `secret` attribute');
155
        }
156
    }
157
158
    protected function doesDirectoryExist($params)
159
    {
160
        $command = $this->s3Client->getCommand('listObjects', $params);
161
162
        try {
163
            $result = $this->s3Client->execute($command);
164
165
            if (empty($result['Contents']) && empty($result['CommonPrefixes'])) {
166
                throw new InvalidParameterException('Bucket or Prefix does not exist');
167
            }
168
        } catch (S3Exception $e) {
169
            if ($e->getStatusCode() === 403) {
170
                return false;
171
            }
172
            throw $e;
173
        }
174
    }
175
}
176