Completed
Pull Request — master (#1)
by
unknown
09:06
created

S3EndpointMiddleware   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 212
Duplicated Lines 15.09 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
dl 32
loc 212
rs 9.1199
c 0
b 0
f 0
wmc 41
lcom 1
cbo 4

13 Methods

Rating   Name   Duplication   Size   Complexity  
A wrap() 0 6 1
A __construct() 0 14 4
B __invoke() 0 31 7
A isRequestHostStyleCompatible() 0 10 3
C endpointPatternDecider() 0 29 12
A canAccelerate() 0 5 2
A getBucketStyleHost() 0 9 2
A applyHostStyleEndpoint() 17 17 1
A applyDualStackEndpoint() 0 16 4
A getDualStackHost() 0 4 1
A applyAccelerateEndpoint() 15 15 1
A getAccelerateHost() 0 4 1
A getBucketlessPath() 0 5 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like S3EndpointMiddleware often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use S3EndpointMiddleware, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace Aws\S3;
3
4
use Aws\CommandInterface;
5
use Aws\S3\Exception\S3Exception;
6
use Psr\Http\Message\RequestInterface;
7
8
/**
9
 * Used to update the URL used for S3 requests to support:
10
 * S3 Accelerate, S3 DualStack or Both. It will build to
11
 * host style paths unless specified, including for S3
12
 * DualStack.
13
 *
14
 * IMPORTANT: this middleware must be added after the "build" step.
15
 *
16
 * @internal
17
 */
18
class S3EndpointMiddleware
19
{
20
    private static $exclusions = [
21
        'CreateBucket' => true,
22
        'DeleteBucket' => true,
23
        'ListBuckets' => true,
24
    ];
25
26
    const NO_PATTERN = 0;
27
    const DUALSTACK = 1;
28
    const ACCELERATE = 2;
29
    const ACCELERATE_DUALSTACK = 3;
30
    const PATH_STYLE = 4;
31
    const HOST_STYLE = 5;
32
33
    /** @var bool */
34
    private $accelerateByDefault;
35
    /** @var bool */
36
    private $dualStackByDefault;
37
    /** @var bool */
38
    private $pathStyleByDefault;
39
    /** @var string */
40
    private $region;
41
    /** @var callable */
42
    private $nextHandler;
43
44
    /**
45
     * Create a middleware wrapper function
46
     *
47
     * @param string $region
48
     * @param array  $options
49
     *
50
     * @return callable
51
     */
52
    public static function wrap($region, array $options)
53
    {
54
        return function (callable $handler) use ($region, $options) {
55
            return new self($handler, $region, $options);
56
        };
57
    }
58
59
    public function __construct(
60
        callable $nextHandler,
61
        $region,
62
        array $options
63
    ) {
64
        $this->pathStyleByDefault = isset($options['path_style'])
65
            ? (bool) $options['path_style'] : false;
66
        $this->dualStackByDefault = isset($options['dual_stack'])
67
            ? (bool) $options['dual_stack'] : false;
68
        $this->accelerateByDefault = isset($options['accelerate'])
69
            ? (bool) $options['accelerate'] : false;
70
        $this->region = (string) $region;
71
        $this->nextHandler = $nextHandler;
72
    }
73
74
    public function __invoke(CommandInterface $command, RequestInterface $request)
75
    {
76
        switch ($this->endpointPatternDecider($command, $request)) {
77
            case self::HOST_STYLE:
78
                $request = $this->applyHostStyleEndpoint($command, $request);
79
                break;
80
            case self::NO_PATTERN:
81
            case self::PATH_STYLE:
82
                break;
83
            case self::DUALSTACK:
84
                $request = $this->applyDualStackEndpoint($command, $request);
85
                break;
86
            case self::ACCELERATE:
87
                $request = $this->applyAccelerateEndpoint(
88
                    $command,
89
                    $request,
90
                    's3-accelerate'
91
                );
92
                break;
93
            case self::ACCELERATE_DUALSTACK:
94
                $request = $this->applyAccelerateEndpoint(
95
                    $command,
96
                    $request,
97
                    's3-accelerate.dualstack'
98
                );
99
                break;
100
        }
101
102
        $nextHandler = $this->nextHandler;
103
        return $nextHandler($command, $request);
104
    }
105
106
    private static function isRequestHostStyleCompatible(
107
        CommandInterface $command,
108
        RequestInterface $request
109
    ) {
110
        return S3Client::isBucketDnsCompatible($command['Bucket'])
111
            && (
112
                $request->getUri()->getScheme() === 'http'
113
                || strpos($command['Bucket'], '.') === false
114
            );
115
    }
116
117
    private function endpointPatternDecider(
118
        CommandInterface $command,
119
        RequestInterface $request
120
    ) {
121
        $accelerate = isset($command['@use_accelerate_endpoint'])
122
            ? $command['@use_accelerate_endpoint'] : $this->accelerateByDefault;
123
        $dualStack = isset($command['@use_dual_stack_endpoint'])
124
            ? $command['@use_dual_stack_endpoint'] : $this->dualStackByDefault;
125
        $pathStyle = isset($command['@use_path_style_endpoint'])
126
            ? $command['@use_path_style_endpoint'] : $this->pathStyleByDefault;
127
128
        if ($accelerate && $dualStack) {
129
            // When try to enable both for operations excluded from s3-accelerate,
130
            // only dualstack endpoints will be enabled.
131
            return $this->canAccelerate($command)
132
                ? self::ACCELERATE_DUALSTACK
133
                : self::DUALSTACK;
134
        } elseif ($accelerate && $this->canAccelerate($command)) {
135
            return self::ACCELERATE;
136
        } elseif ($dualStack) {
137
            return self::DUALSTACK;
138
        } elseif (!$pathStyle
139
            && self::isRequestHostStyleCompatible($command, $request)
140
        ) {
141
            return self::HOST_STYLE;
142
        } else {
143
            return self::PATH_STYLE;
144
        }
145
    }
146
147
    private function canAccelerate(CommandInterface $command)
148
    {
149
        return empty(self::$exclusions[$command->getName()])
150
            && S3Client::isBucketDnsCompatible($command['Bucket']);
151
    }
152
153
    private function getBucketStyleHost(CommandInterface $command, $host)
154
    {
155
        // For operations on the base host (e.g. ListBuckets)
156
        if (!isset($command['Bucket'])) {
157
            return $host;
158
        }
159
160
        return "{$command['Bucket']}.{$host}";
161
    }
162
163 View Code Duplication
    private function applyHostStyleEndpoint(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
164
        CommandInterface $command,
165
        RequestInterface $request
166
    ) {
167
        $uri = $request->getUri();
168
        $request = $request->withUri(
169
            $uri->withHost($this->getBucketStyleHost(
170
                    $command,
171
                    $uri->getHost()
172
                ))
173
                ->withPath($this->getBucketlessPath(
174
                    $uri->getPath(),
175
                    $command
176
                ))
177
        );
178
        return $request;
179
    }
180
181
    private function applyDualStackEndpoint(
182
        CommandInterface $command,
183
        RequestInterface $request
184
    ) {
185
        $request = $request->withUri(
186
            $request->getUri()
187
                ->withHost($this->getDualStackHost())
188
        );
189
        if (empty($command['@use_path_style_endpoint'])
190
            && !$this->pathStyleByDefault
191
            && self::isRequestHostStyleCompatible($command, $request)
192
        ) {
193
            $request = $this->applyHostStyleEndpoint($command, $request);
194
        }
195
        return $request;
196
    }
197
198
    private function getDualStackHost()
199
    {
200
        return "s3.dualstack.{$this->region}.amazonaws.com";
201
    }
202
203 View Code Duplication
    private function applyAccelerateEndpoint(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
204
        CommandInterface $command,
205
        RequestInterface $request,
206
        $pattern
207
    ) {
208
        $request = $request->withUri(
209
            $request->getUri()
210
                ->withHost($this->getAccelerateHost($command, $pattern))
211
                ->withPath($this->getBucketlessPath(
212
                    $request->getUri()->getPath(),
213
                    $command
214
                ))
215
        );
216
        return $request;
217
    }
218
219
    private function getAccelerateHost(CommandInterface $command, $pattern)
220
    {
221
        return "{$command['Bucket']}.{$pattern}.amazonaws.com";
222
    }
223
224
    private function getBucketlessPath($path, CommandInterface $command)
225
    {
226
        $pattern = '/^\\/' . preg_quote($command['Bucket'], '/') . '/';
227
        return preg_replace($pattern, '', $path) ?: '/';
228
    }
229
}
230