Completed
Push — master ( 1b2d0a...89ea1a )
by Oscar
03:00
created

ImageTransformer::__invoke()   C

Complexity

Conditions 13
Paths 20

Size

Total Lines 64
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 7
Bugs 2 Features 4
Metric Value
c 7
b 2
f 4
dl 0
loc 64
rs 6.0391
cc 13
eloc 34
nc 20
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Psr7Middlewares\Middleware;
4
5
use Psr7Middlewares\Utils;
6
use Psr7Middlewares\Middleware;
7
use Psr\Http\Message\ServerRequestInterface;
8
use Psr\Http\Message\ResponseInterface;
9
use Psr\Cache\CacheItemPoolInterface;
10
use Imagecow\Image;
11
use RuntimeException;
12
13
/**
14
 * Middleware to manipulate images on demand.
15
 */
16
class ImageTransformer
17
{
18
    use Utils\CacheMessageTrait;
19
20
    const KEY_GENERATOR = 'IMAGE_TRANSFORMER';
21
22
    /**
23
     * @var array|false Enable client hints
24
     */
25
    private $clientHints = false;
26
27
    /**
28
     * @var array Available sizes
29
     */
30
    private $sizes = [];
31
32
    /**
33
     * Returns a callable to generate urls.
34
     *
35
     * @param ServerRequestInterface $request
36
     *
37
     * @return callable|null
38
     */
39
    public static function getGenerator(ServerRequestInterface $request)
40
    {
41
        return Middleware::getAttribute($request, self::KEY_GENERATOR);
42
    }
43
44
    /**
45
     * Define the available sizes, for example:
46
     * [
47
     *    'small'  => 'resizeCrop,50,50',
48
     *    'medium' => 'resize,500',
49
     *    'big'    => 'resize,1000',
50
     * ].
51
     * 
52
     * @param array $sizes
53
     */
54
    public function __construct(array $sizes)
55
    {
56
        foreach ($sizes as $prefix => $transform) {
57
            if (strpos($prefix, '/') === false) {
58
                $path = '';
59
            } else {
60
                $path = pathinfo($prefix, PATHINFO_DIRNAME);
61
                $prefix = pathinfo($prefix, PATHINFO_BASENAME);
62
            }
63
64
            if (!isset($this->sizes[$prefix])) {
65
                $this->sizes[$prefix] = [$path => $transform];
66
            } else {
67
                $this->sizes[$prefix][$path] = $transform;
68
            }
69
        }
70
    }
71
72
    /**
73
     * To save the transformed images in the cache.
74
     * 
75
     * @param CacheItemPoolInterface $cache
76
     * 
77
     * @return self
78
     */
79
    public function cache(CacheItemPoolInterface $cache)
80
    {
81
        $this->cache = $cache;
82
83
        return $this;
84
    }
85
86
    /**
87
     * Enable the client hints.
88
     * 
89
     * @param array $clientHints
90
     * 
91
     * @return self
92
     */
93
    public function clientHints($clientHints = ['Dpr', 'Viewport-Width', 'Width'])
94
    {
95
        $this->clientHints = $clientHints;
96
97
        return $this;
98
    }
99
100
    /**
101
     * Execute the middleware.
102
     *
103
     * @param ServerRequestInterface $request
104
     * @param ResponseInterface      $response
105
     * @param callable               $next
106
     *
107
     * @return ResponseInterface
108
     */
109
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
110
    {
111
        if (!Middleware::hasAttribute($request, FormatNegotiator::KEY)) {
112
            throw new RuntimeException('ResponsiveImage middleware needs FormatNegotiator executed before');
113
        }
114
115
        $format = FormatNegotiator::getFormat($request);
116
117
        switch ($format) {
118
            case 'jpg':
119
            case 'jpeg':
120
            case 'gif':
121
            case 'png':
122
                $key = $this->getCacheKey($request);
123
124
                //Get from the cache
125
                if ($cached = $this->getFromCache($key, $response)) {
126
                    return $cached;
127
                }
128
129
                $info = $this->parsePath($request->getUri()->getPath());
130
131
                if (!$info) {
132
                    break;
133
                }
134
135
                //Removes the transform info in the path
136
                list($path, $transform) = $info;
137
                $request = $request->withUri($request->getUri()->withPath($path));
138
                $response = $next($request, $response);
139
140
                //Transform
141
                if ($response->getStatusCode() === 200 && $response->getBody()->getSize()) {
142
                    $response = $this->transform($request, $response, $transform);
143
144
                    //Save in the cache
145
                    $this->saveIntoCache($key, $response);
146
                }
147
148
                return $response;
149
150
            case 'html':
151
                $generator = function ($path, $transform) use ($request) {
152
                    $info = pathinfo($path);
153
154
                    if (!isset($this->sizes[$transform])) {
155
                        throw new \InvalidArgumentException(sprintf('The image size "%s" is not valid', $transform));
156
                    }
157
158
                    return Utils\Helpers::joinPath($info['dirname'], $transform.$info['basename']);
159
                };
160
161
                $request = Middleware::setAttribute($request, self::KEY_GENERATOR, $generator);
162
                $response = $next($request, $response);
163
164
                if (!empty($this->clientHints)) {
165
                    return $response->withHeader('Accept-CH', implode(',', $this->clientHints));
166
                }
167
168
                return $response;
169
        }
170
171
        return $next($request, $response);
172
    }
173
174
    /**
175
     * Transform the image.
176
     * 
177
     * @param ServerRequestInterface $request
178
     * @param ResponseInterface      $response
179
     * @param string                 $transform
180
     * 
181
     * @return ResponseInterface
182
     */
183
    private function transform(ServerRequestInterface $request, ResponseInterface $response, $transform)
184
    {
185
        $image = Image::fromString((string) $response->getBody());
186
        $hints = $this->getClientHints($request);
187
188
        if ($hints) {
189
            $image->setClientHints($hints);
190
            $response = $response->withHeader('Vary', implode(', ', $hints));
191
        }
192
193
        $image->transform($transform);
194
195
        $body = Middleware::createStream();
196
        $body->write($image->getString());
197
198
        return $response
199
            ->withBody($body)
200
            ->withHeader('Content-Type', $image->getMimeType());
201
    }
202
203
    /**
204
     * Parses the path and return the file and transform values.
205
     * For example, the path "/images/small.avatar.jpg" returns:
206
     * ["/images/avatar.jpg", "resizeCrop,50,50"].
207
     * 
208
     * @param string $path
209
     * 
210
     * @return false|array [file, transform]
211
     */
212
    private function parsePath($path)
213
    {
214
        $info = pathinfo($path);
215
        $basename = $info['basename'];
216
        $dirname = $info['dirname'];
217
218
        foreach ($this->sizes as $prefix => $paths) {
219
            if (strpos($basename, $prefix) !== 0) {
220
                continue;
221
            }
222
223
            foreach ($paths as $path => $transform) {
224
                $needle = $path === '' ? '' : substr($dirname, -strlen($path));
225
226
                if ($path === $needle) {
227
                    return [Utils\Helpers::joinPath($dirname, substr($basename, strlen($prefix))), $transform];
228
                }
229
            }
230
        }
231
232
        return false;
233
    }
234
235
    /**
236
     * Returns the client hints sent.
237
     * 
238
     * @param ServerRequestInterface $request
239
     * 
240
     * @return array|null
241
     */
242
    private function getClientHints(ServerRequestInterface $request)
243
    {
244
        if (!empty($this->clientHints)) {
245
            $hints = [];
246
247
            foreach ($this->clientHints as $name) {
248
                if ($request->hasHeader($name)) {
249
                    $hints[$name] = $request->getHeaderLine($name);
250
                }
251
            }
252
253
            return $hints;
254
        }
255
    }
256
257
    /**
258
     * Generates the key used to save the image in cache.
259
     * 
260
     * @param ServerRequestInterface $request
261
     * 
262
     * @return string
263
     */
264
    private function getCacheKey(ServerRequestInterface $request)
265
    {
266
        $id = base64_encode((string) $request->getUri());
267
        $hints = $this->getClientHints($request);
268
269
        if ($hints) {
270
            $id .= '.'.base64_encode(json_encode($hints));
271
        }
272
273
        return $id;
274
    }
275
}
276