Test Failed
Branch develop (25b795)
by Florian
07:45
created

ImageProcessor::process()   B

Complexity

Conditions 9
Paths 12

Size

Total Lines 61
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 9.2631

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 9
eloc 30
c 2
b 0
f 0
nc 12
nop 1
dl 0
loc 61
ccs 23
cts 27
cp 0.8519
crap 9.2631
rs 8.0555

How to fix   Long Method   

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
/**
4
 * Copyright (c) Florian Krämer (https://florian-kraemer.net)
5
 * Licensed under The MIT License
6
 * For full copyright and license information, please see the LICENSE.txt
7
 * Redistributions of files must retain the above copyright notice.
8
 *
9
 * @copyright Copyright (c) Florian Krämer (https://florian-kraemer.net)
10
 * @author    Florian Krämer
11
 * @link      https://github.com/Phauthentic
12
 * @license   https://opensource.org/licenses/MIT MIT License
13
 */
14
15
declare(strict_types=1);
16
17
namespace Phauthentic\Infrastructure\Storage\Processor\Image;
18
19
use GuzzleHttp\Psr7\StreamWrapper;
20
use http\Exception\InvalidArgumentException;
21
use Intervention\Image\Image;
22
use Intervention\Image\ImageManager;
23
use League\Flysystem\Config;
24
use Phauthentic\Infrastructure\Storage\FileInterface;
25
use Phauthentic\Infrastructure\Storage\Processor\Image\Exception\TempFileCreationFailedException;
26
use Phauthentic\Infrastructure\Storage\PathBuilder\PathBuilderInterface;
27
use Phauthentic\Infrastructure\Storage\FileStorageInterface;
28
use Phauthentic\Infrastructure\Storage\Processor\ProcessorInterface;
29
use Phauthentic\Infrastructure\Storage\UrlBuilder\UrlBuilderInterface;
30
use Phauthentic\Infrastructure\Storage\Utility\TemporaryFile;
31
32
use function Phauthentic\Infrastructure\Storage\fopen;
33
34
/**
35
 * Image Operator
36
 */
37
class ImageProcessor implements ProcessorInterface
38
{
39
    use OptimizerTrait;
40
41
    /**
42
     * @var array
43
     */
44
    protected array $mimeTypes = [
45
        'image/gif',
46
        'image/jpg',
47
        'image/jpeg',
48
        'image/png'
49
    ];
50
51
    /**
52
     * @var array
53
     */
54
    protected array $processOnlyTheseVariants = [];
55
56
    /**
57
     * @var \Phauthentic\Infrastructure\Storage\FileStorageInterface
58
     */
59
    protected FileStorageInterface $storageHandler;
60
61
    /**
62
     * @var \Phauthentic\Infrastructure\Storage\PathBuilder\PathBuilderInterface
63
     */
64
    protected PathBuilderInterface $pathBuilder;
65
66
    /**
67
     * @var \Phauthentic\Infrastructure\Storage\UrlBuilder\UrlBuilderInterface
68
     */
69
    protected UrlBuilderInterface $urlBuilder;
70
71
    /**
72
     * @var \Intervention\Image\ImageManager
73
     */
74
    protected ImageManager $imageManager;
75
76
    /**
77
     * @var \Intervention\Image\Image
78
     */
79 1
    protected Image $image;
80
81
    /**
82
     * Quality setting for writing images
83
     *
84 1
     * @var int
85 1
     */
86 1
    protected int $quality = 90;
87 1
88
    /**
89
     * @param \Phauthentic\Infrastructure\Storage\FileStorageInterface $storageHandler File Storage Handler
90
     * @param \Phauthentic\Infrastructure\Storage\PathBuilder\PathBuilderInterface $pathBuilder Path Builder
91
     * @param \Intervention\Image\ImageManager $imageManager Image Manager
92
     */
93
    public function __construct(
94
        FileStorageInterface $storageHandler,
95
        PathBuilderInterface $pathBuilder,
96
        ImageManager $imageManager,
97
        ?UrlBuilderInterface $urlBuilder = null
98
    ) {
99
        $this->storageHandler = $storageHandler;
100
        $this->pathBuilder = $pathBuilder;
101
        $this->imageManager = $imageManager;
102
        $this->urlBuilder = $urlBuilder;
103
    }
104 1
105
    /**
106 1
     * @param array $mimeTypes Mime Type List
107 1
     * @return $this
108
     */
109
    protected function setMimeTypes(array $mimeTypes): self
110
    {
111
        $this->mimeTypes = $mimeTypes;
112
113
        return $this;
114
    }
115
116
    /**
117
     * @param int $quality Quality
118
     * @return $this
119
     */
120
    public function setQuality(int $quality): self
121
    {
122
        if ($quality > 100 || $quality <= 0) {
123
            throw new InvalidArgumentException(sprintf(
124
                'Quality has to be a positive integer between 1 and 100. %s was provided',
125
                (string)$quality
126
            ));
127
        }
128
129
        $this->quality = $quality;
130
131
        return $this;
132
    }
133
134
    /**
135
     * @param \Phauthentic\Infrastructure\Storage\FileInterface $file File
136
     * @return bool
137
     */
138
    protected function isApplicable(FileInterface $file): bool
139
    {
140 1
        return $file->hasVariants()
141
            && in_array($file->mimeType(), $this->mimeTypes, true);
142 1
    }
143 1
144
    /**
145 1
     * @param array $variants Variants by name
146
     * @return $this
147
     */
148
    public function processOnlyTheseVariants(array $variants): self
149 1
    {
150
        $this->processOnlyTheseVariants = $variants;
151 1
152 1
        return $this;
153
    }
154
155 1
    /**
156
     * @return $this
157 1
     */
158
    public function processAll(): self
159
    {
160
        $this->processOnlyTheseVariants = [];
161
162
        return $this;
163
    }
164
165 1
    /**
166
     * Read the data from the files resource if (still) present,
167
     * if not fetch it from the storage backend and write the data
168
     * to the stream of the temp file
169 1
     *
170
     * @param \Phauthentic\Infrastructure\Storage\FileInterface $file File
171
     * @param resource $tempFileStream Temp File Stream Resource
172 1
     * @return int|bool False on error
173 1
     */
174
    protected function copyOriginalFileData(FileInterface $file, $tempFileStream)
175
    {
176
        $stream = $file->resource();
177
        $storage = $this->storageHandler->getStorage($file->storage());
178
179
        if ($stream === null) {
180
            $stream = $storage->readStream($file->path());
181 1
            $stream = $stream['stream'];
182
        } else {
183 1
            rewind($stream);
184
        }
185
        $result = stream_copy_to_stream(
186
            $stream,
187 1
            $tempFileStream
188
        );
189
        fclose($tempFileStream);
190 1
191 1
        return $result;
192
    }
193
194
    /**
195
     * @param string $variant Variant name
196 1
     * @param array $variantData Variant data
197
     * @return bool
198
     */
199 1
    protected function shouldProcessVariant(string $variant, array $variantData): bool
200
    {
201
        return !(
202
            // Empty operations
203
            empty($variantData['operations'])
204 1
            || (
205 1
                // Check if the operation should be processed
206
                !empty($this->processOnlyTheseVariants)
207
                && !in_array($variant, $this->processOnlyTheseVariants, true)
208
            )
209 1
        );
210 1
    }
211
212
    /**
213 1
     * @inheritDoc
214 1
     */
215
    public function process(FileInterface $file): FileInterface
216
    {
217 1
        if (!$this->isApplicable($file)) {
218
            return $file;
219 1
        }
220 1
221
        $storage = $this->storageHandler->getStorage($file->storage());
222
223
        // Create a local tmp file on the processing system / machine
224
        $tempFile = TemporaryFile::create();
225
        $tempFileStream = fopen($tempFile, 'wb+');
226
227
        // Read the data from the files resource if (still) present,
228
        // if not fetch it from the storage backend and write the data
229 1
        // to the stream of the temp file
230 1
        $result = $this->copyOriginalFileData($file, $tempFileStream);
231
232
        // Stop if the temp file could not be generated
233 1
        if ($result === false) {
234
            throw TempFileCreationFailedException::withFilename($tempFile);
235 1
        }
236
237
        // Iterate over the variants described as an array
238
        foreach ($file->variants() as $variant => $data) {
239
            if (!$this->shouldProcessVariant($variant, $data)) {
240
                continue;
241
            }
242
243 1
            $this->image = $this->imageManager->make($tempFile);
244
            $operations = new Operations($this->image);
245 1
246
            // Apply the operations
247
            foreach ($data['operations'] as $operation => $arguments) {
248
                $operations->{$operation}($arguments);
249 1
            }
250 1
251
            $path = $this->pathBuilder->pathForVariant($file, $variant);
252
253 1
            if (isset($data['optimize']) && $data['optimize'] === true) {
254
                $this->optimizeAndStore($file, $path);
255 1
            } else {
256
                $storage->writeStream(
257 1
                    $path,
258
                    StreamWrapper::getResource($this->image->stream($file->extension(), $this->quality)),
259
                    new Config()
260 1
                );
261 1
            }
262
263 1
            $data['path'] = $path;
264
            $file = $file->withVariant($variant, $data);
265
266
            if ($this->urlBuilder !== null) {
267 1
                $data['url'] = $this->urlBuilder->urlForVariant($file, $variant);
268 1
            }
269 1
270
            $file = $file->withVariant($variant, $data);
271
        }
272
273 1
        unlink($tempFile);
274 1
275 1
        return $file;
276
    }
277 1
278
    /**
279
     * @param \Phauthentic\Infrastructure\Storage\FileInterface $file File
280
     * @param string $path Path
281
     * @return void
282
     */
283
    protected function optimizeAndStore(FileInterface $file, string $path): void
284
    {
285
        $storage = $this->storageHandler->getStorage($file->storage());
286
287
        // We need more tmp files because the optimizer likes to write
288
        // and read the files from disk, not from a stream. :(
289
        $optimizerTempFile = TemporaryFile::create();
290
        $optimizerOutput = TemporaryFile::create();
291
292
        // Save the image to the tmp file
293
        $this->image->save($optimizerTempFile, 90, $file->extension());
294
        // Optimize it and write it to another file
295
        $this->optimizer()->optimize($optimizerTempFile, $optimizerOutput);
296
        // Open a new stream for the storage system
297
        $optimizerOutputHandler = fopen($optimizerOutput, 'rb+');
298
299
        // And store it...
300
        $storage->writeStream(
301
            $path,
302
            $optimizerOutputHandler,
303
            new Config()
304
        );
305
306
        // Cleanup
307
        fclose($optimizerOutputHandler);
308
        unlink($optimizerTempFile);
309
        unlink($optimizerOutput);
310
311
        // Cleanup
312
        unset(
313
            $optimizerOutputHandler,
314
            $optimizerTempFile,
315
            $optimizerOutput
316
        );
317
    }
318
}
319