Test Failed
Branch develop (c705b4)
by Florian
03:13
created

ImageProcessor   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 241
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 83
c 3
b 0
f 0
dl 0
loc 241
rs 10
wmc 20

9 Methods

Rating   Name   Duplication   Size   Complexity  
B process() 0 55 8
A setMimeTypes() 0 5 1
A __construct() 0 8 1
A processOnlyTheseVariants() 0 5 1
A processAll() 0 5 1
A optimizeAndStore() 0 33 1
A copyOriginalFileData() 0 18 2
A isApplicable() 0 4 2
A shouldProcessVariant() 0 9 3
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 Intervention\Image\Image;
21
use Intervention\Image\ImageManager;
22
use League\Flysystem\Config;
23
use Phauthentic\Infrastructure\Storage\FileInterface;
24
use Phauthentic\Infrastructure\Storage\Processor\Image\Exception\TempFileCreationFailedException;
25
use Phauthentic\Infrastructure\Storage\PathBuilder\PathBuilderInterface;
26
use Phauthentic\Infrastructure\Storage\FileStorageInterface;
27
use Phauthentic\Infrastructure\Storage\Processor\ProcessorInterface;
28
use Phauthentic\Infrastructure\Storage\Utility\TemporaryFile;
29
30
use function Phauthentic\Infrastructure\Storage\fopen;
31
32
/**
33
 * Image Operator
34
 */
35
class ImageProcessor implements ProcessorInterface
36
{
37
    use OptimizerTrait;
38
39
    /**
40
     * @var array
41
     */
42
    protected array $mimeTypes = [
43
        'image/gif',
44
        'image/jpg',
45
        'image/jpeg',
46
        'image/png'
47
    ];
48
49
    /**
50
     * @var array
51
     */
52
    protected array $processOnlyTheseVariants = [];
53
54
    /**
55
     * @var \Phauthentic\Infrastructure\Storage\FileStorageInterface
56
     */
57
    protected FileStorageInterface $storageHandler;
58
59
    /**
60
     * @var \Phauthentic\Infrastructure\Storage\PathBuilder\PathBuilderInterface
61
     */
62
    protected PathBuilderInterface $pathBuilder;
63
64
    /**
65
     * @var \Intervention\Image\ImageManager
66
     */
67
    protected ImageManager $imageManager;
68
69
    /**
70
     * @var \Intervention\Image\Image
71
     */
72
    protected Image $image;
73
74
    /**
75
     * @param \Phauthentic\Infrastructure\Storage\FileStorageInterface $storageHandler File Storage Handler
76
     * @param \Phauthentic\Infrastructure\Storage\PathBuilder\PathBuilderInterface $pathBuilder Path Builder
77
     * @param \Intervention\Image\ImageManager $imageManager Image Manager
78
     */
79
    public function __construct(
80
        FileStorageInterface $storageHandler,
81
        PathBuilderInterface $pathBuilder,
82
        ImageManager $imageManager
83
    ) {
84
        $this->storageHandler = $storageHandler;
85
        $this->pathBuilder = $pathBuilder;
86
        $this->imageManager = $imageManager;
87
    }
88
89
    /**
90
     * @param array $mimeTypes Mime Type List
91
     * @return $this
92
     */
93
    protected function setMimeTypes(array $mimeTypes): self
94
    {
95
        $this->mimeTypes = $mimeTypes;
96
97
        return $this;
98
    }
99
100
    /**
101
     * @param \Phauthentic\Infrastructure\Storage\FileInterface $file File
102
     * @return bool
103
     */
104
    protected function isApplicable(FileInterface $file): bool
105
    {
106
        return $file->hasVariants()
107
            && in_array($file->mimeType(), $this->mimeTypes, true);
108
    }
109
110
    /**
111
     * @param array $variants Variants by name
112
     * @return $this
113
     */
114
    public function processOnlyTheseVariants(array $variants): self
115
    {
116
        $this->processOnlyTheseVariants = $variants;
117
118
        return $this;
119
    }
120
121
    /**
122
     * @return $this
123
     */
124
    public function processAll(): self
125
    {
126
        $this->processOnlyTheseVariants = [];
127
128
        return $this;
129
    }
130
131
    /**
132
     * Read the data from the files resource if (still) present,
133
     * if not fetch it from the storage backend and write the data
134
     * to the stream of the temp file
135
     *
136
     * @param \Phauthentic\Infrastructure\Storage\FileInterface $file File
137
     * @param resource $tempFileStream Temp File Stream Resource
138
     * @return int|bool False on error
139
     */
140
    protected function copyOriginalFileData(FileInterface $file, $tempFileStream)
141
    {
142
        $stream = $file->resource();
143
        $storage = $this->storageHandler->getStorage($file->storage());
144
145
        if ($stream === null) {
146
            $stream = $storage->readStream($file->path());
147
            $stream = $stream['stream'];
148
        } else {
149
            rewind($stream);
150
        }
151
        $result = stream_copy_to_stream(
152
            $stream,
153
            $tempFileStream
154
        );
155
        fclose($tempFileStream);
156
157
        return $result;
158
    }
159
160
    /**
161
     * @param string $variant Variant name
162
     * @param array $variantData Variant data
163
     * @return bool
164
     */
165
    protected function shouldProcessVariant(string $variant, array $variantData): bool
166
    {
167
        return !(
168
            // Empty operations
169
            empty($variantData['operations'])
170
            || (
171
                // Check if the operation should be processed
172
                !empty($this->processOnlyTheseVariants)
173
                && !in_array($variant, $this->processOnlyTheseVariants, true)
174
            )
175
        );
176
    }
177
178
    /**
179
     * @inheritDoc
180
     */
181
    public function process(FileInterface $file): FileInterface
182
    {
183
        if (!$this->isApplicable($file)) {
184
            return $file;
185
        }
186
187
        $storage = $this->storageHandler->getStorage($file->storage());
188
189
        // Create a local tmp file on the processing system / machine
190
        $tempFile = TemporaryFile::create();
191
        $tempFileStream = fopen($tempFile, 'wb+');
192
193
        // Read the data from the files resource if (still) present,
194
        // if not fetch it from the storage backend and write the data
195
        // to the stream of the temp file
196
        $result = $this->copyOriginalFileData($file, $tempFileStream);
197
198
        // Stop if the temp file could not be generated
199
        if ($result === false) {
200
            throw TempFileCreationFailedException::withFilename($tempFile);
201
        }
202
203
        // Iterate over the variants described as an array
204
        foreach ($file->variants() as $variant => $data) {
205
            if (!$this->shouldProcessVariant($variant, $data)) {
206
                continue;
207
            }
208
209
            $this->image = $this->imageManager->make($tempFile);
210
            $operations = new Operations($this->image);
211
212
            // Apply the operations
213
            foreach ($data['operations'] as $operation => $arguments) {
214
                $operations->{$operation}($arguments);
215
            }
216
217
            $path = $this->pathBuilder->pathForVariant($file, $variant);
218
219
            if (isset($data['optimize']) && $data['optimize'] === true) {
220
                $this->optimizeAndStore($file, $path);
221
            } else {
222
                $storage->writeStream(
223
                    $path,
224
                    StreamWrapper::getResource($this->image->stream($file->extension(), 90)),
225
                    new Config()
226
                );
227
            }
228
229
            $data['path'] = $path;
230
            $file = $file->withVariant($variant, $data);
231
        }
232
233
        unlink($tempFile);
234
235
        return $file;
236
    }
237
238
    /**
239
     * @param \Phauthentic\Infrastructure\Storage\FileInterface $file File
240
     * @param string $path Path
241
     * @return void
242
     */
243
    protected function optimizeAndStore(FileInterface $file, string $path): void
244
    {
245
        $storage = $this->storageHandler->getStorage($file->storage());
246
247
        // We need more tmp files because the optimizer likes to write
248
        // and read the files from disk, not from a stream. :(
249
        $optimizerTempFile = TemporaryFile::create();
250
        $optimizerOutput = TemporaryFile::create();
251
252
        // Save the image to the tmp file
253
        $this->image->save($optimizerTempFile, 90, $file->extension());
254
        // Optimize it and write it to another file
255
        $this->optimizer()->optimize($optimizerTempFile, $optimizerOutput);
256
        // Open a new stream for the storage system
257
        $optimizerOutputHandler = fopen($optimizerOutput, 'rb+');
258
259
        // And store it...
260
        $storage->writeStream(
261
            $path,
262
            $optimizerOutputHandler,
263
            new Config()
264
        );
265
266
        // Cleanup
267
        fclose($optimizerOutputHandler);
268
        unlink($optimizerTempFile);
269
        unlink($optimizerOutput);
270
271
        // Cleanup
272
        unset(
273
            $optimizerOutputHandler,
274
            $optimizerTempFile,
275
            $optimizerOutput
276
        );
277
    }
278
}
279