Completed
Push — master ( 12bc5d...f69768 )
by Oscar
58:41
created

ImageTransformer   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 222
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 9

Importance

Changes 15
Bugs 4 Features 3
Metric Value
wmc 29
c 15
b 4
f 3
lcom 3
cbo 9
dl 0
loc 222
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A cache() 0 6 1
A clientHints() 0 6 1
C __invoke() 0 53 12
A transform() 0 19 2
B parsePath() 0 22 6
A getClientHints() 0 14 4
A getCacheKey() 0 11 2
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
    /**
21
     * @var array|false Enable client hints
22
     */
23
    private $clientHints = false;
24
25
    /**
26
     * @var array Available sizes
27
     */
28
    private $sizes = [];
29
30
    /**
31
     * Define the available sizes, for example:
32
     * [
33
     *    'small'  => 'resizeCrop,50,50',
34
     *    'medium' => 'resize,500',
35
     *    'big'    => 'resize,1000',
36
     * ].
37
     * 
38
     * @param array $sizes
39
     */
40
    public function __construct(array $sizes)
41
    {
42
        $this->sizes = $sizes;
43
    }
44
45
    /**
46
     * To save the transformed images in the cache.
47
     * 
48
     * @param CacheItemPoolInterface $cache
49
     * 
50
     * @return self
51
     */
52
    public function cache(CacheItemPoolInterface $cache)
53
    {
54
        $this->cache = $cache;
0 ignored issues
show
Documentation Bug introduced by
It seems like $cache of type object<Psr\Cache\CacheItemPoolInterface> is incompatible with the declared type object<Psr7Middlewares\U...CacheItemPoolInterface> of property $cache.

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...
55
56
        return $this;
57
    }
58
59
    /**
60
     * Enable the client hints.
61
     * 
62
     * @param array $clientHints
63
     * 
64
     * @return self
65
     */
66
    public function clientHints($clientHints = ['Dpr', 'Viewport-Width', 'Width'])
67
    {
68
        $this->clientHints = $clientHints;
69
70
        return $this;
71
    }
72
73
    /**
74
     * Execute the middleware.
75
     *
76
     * @param ServerRequestInterface $request
77
     * @param ResponseInterface      $response
78
     * @param callable               $next
79
     *
80
     * @return ResponseInterface
81
     */
82
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
83
    {
84
        if (!Middleware::hasAttribute($request, FormatNegotiator::KEY)) {
85
            throw new RuntimeException('ResponsiveImage middleware needs FormatNegotiator executed before');
86
        }
87
88
        switch (FormatNegotiator::getFormat($request)) {
89
            case 'html':
90
                $response = $next($request, $response);
91
92
                if (!empty($this->clientHints)) {
93
                    return $response->withHeader('Accept-CH', implode(',', $this->clientHints));
94
                }
95
96
                return $response;
97
98
            case 'jpg':
99
            case 'jpeg':
100
            case 'gif':
101
            case 'png':
102
                $key = $this->getCacheKey($request);
103
104
                //Get from the cache
105
                if ($cached = $this->getFromCache($key, $response)) {
106
                    return $cached;
107
                }
108
109
                $uri = $request->getUri();
110
                $info = $this->parsePath($uri->getPath());
111
112
                if (!$info) {
113
                    break;
114
                }
115
116
                //Removes the transform from the path
117
                list($path, $transform) = $info;
118
                $request = $request->withUri($uri->withPath($path));
119
120
                $response = $next($request, $response);
121
122
                //Transform
123
                if ($response->getStatusCode() === 200 && $response->getBody()->getSize()) {
124
                    $response = $this->transform($request, $response, $transform);
125
126
                    //Save in the cache
127
                    $this->saveIntoCache($key, $response);
128
                }
129
130
                return $response;
131
        }
132
133
        return $next($request, $response);
134
    }
135
136
    /**
137
     * Transform the image.
138
     * 
139
     * @param ServerRequestInterface $request
140
     * @param ResponseInterface      $response
141
     * @param string                 $transform
142
     * 
143
     * @return ResponseInterface
144
     */
145
    private function transform(ServerRequestInterface $request, ResponseInterface $response, $transform)
146
    {
147
        $image = Image::fromString((string) $response->getBody());
148
        $hints = $this->getClientHints($request);
149
150
        if ($hints) {
151
            $image->setClientHints($hints);
152
            $response = $response->withHeader('Vary', implode(', ', $hints));
153
        }
154
155
        $image->transform($transform);
156
157
        $body = Middleware::createStream();
158
        $body->write($image->getString());
159
160
        return $response
161
            ->withBody($body)
162
            ->withHeader('Content-Type', $image->getMimeType());
163
    }
164
165
    /**
166
     * Parses the path and return the file and transform values.
167
     * For example, the path "/images/small.avatar.jpg" returns:
168
     * ["/images/avatar.jpg", "resizeCrop,50,50"].
169
     * 
170
     * @param string $path
171
     * 
172
     * @return false|array [file, transform]
173
     */
174
    private function parsePath($path)
175
    {
176
        $info = pathinfo($path);
177
        $file = $info['basename'];
178
        $path = $info['dirname'];
179
180
        foreach ($this->sizes as $pattern => $transform) {
181
            if (strpos($pattern, '/') === false) {
182
                $patternFile = $pattern;
183
                $patternPath = '';
184
            } else {
185
                $patternFile = pathinfo($pattern, PATHINFO_BASENAME);
186
                $patternPath = pathinfo($pattern, PATHINFO_BASENAME);
187
            }
188
189
            if (substr($file, 0, strlen($patternFile)) === $patternFile && ($patternPath === '' || substr($path, -strlen($patternPath)) === $patternPath)) {
190
                return [Utils\Helpers::joinPath($path, substr($file, strlen($patternFile))), $transform];
191
            }
192
        }
193
194
        return false;
195
    }
196
197
    /**
198
     * Returns the client hints sent.
199
     * 
200
     * @param ServerRequestInterface $request
201
     * 
202
     * @return array|null
203
     */
204
    private function getClientHints(ServerRequestInterface $request)
205
    {
206
        if (!empty($this->clientHints)) {
207
            $hints = [];
208
209
            foreach ($this->clientHints as $name) {
210
                if ($request->hasHeader($name)) {
211
                    $hints[$name] = $request->getHeaderLine($name);
212
                }
213
            }
214
215
            return $hints;
216
        }
217
    }
218
219
    /**
220
     * Generates the key used to save the image in cache.
221
     * 
222
     * @param ServerRequestInterface $request
223
     * 
224
     * @return string
225
     */
226
    private function getCacheKey(ServerRequestInterface $request)
227
    {
228
        $id = base64_encode((string) $request->getUri());
229
        $hints = $this->getClientHints($request);
230
231
        if ($hints) {
232
            $id .= '.'.base64_encode(json_encode($hints));
233
        }
234
235
        return $id;
236
    }
237
}
238