Passed
Push — master ( bca956...5c308e )
by Brent
06:40 queued 04:15
created

ResponsiveFactory::createScaledImages()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 29
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 29
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 19
nc 8
nop 2
1
<?php
2
3
namespace Brendt\Image;
4
5
use Amp\Parallel\Forking\Fork;
6
use AsyncInterop\Promise;
7
use Brendt\Image\Config\DefaultConfigurator;
8
use Brendt\Image\Config\ResponsiveFactoryConfigurator;
9
use Brendt\Image\Exception\FileNotFoundException;
10
use Brendt\Image\Scaler\Scaler;
11
use ImageOptimizer\Optimizer;
12
use ImageOptimizer\OptimizerFactory;
13
use Intervention\Image\Image;
14
use Intervention\Image\ImageManager;
15
use Symfony\Component\Filesystem\Filesystem;
16
use Symfony\Component\Finder\Finder;
17
use Symfony\Component\Finder\SplFileInfo;
18
19
class ResponsiveFactory
20
{
21
22
    /**
23
     * The image driver to use.
24
     * Available drivers: 'gd' and 'imagick'.
25
     *
26
     * @var string
27
     */
28
    protected $driver;
29
30
    /**
31
     * The source path to load images from.
32
     *
33
     * @var string
34
     */
35
    protected $sourcePath;
36
37
    /**
38
     * The public path to save rendered images.
39
     *
40
     * @var string
41
     */
42
    protected $publicPath;
43
44
    /**
45
     * Enabled cache will stop generated images from being overwritten.
46
     *
47
     * @var bool
48
     */
49
    private $enableCache;
50
51
    /**
52
     * Enable optimizers will run several image optimizers on the saved files.
53
     *
54
     * @var bool
55
     */
56
    private $optimize;
57
58
    /**
59
     * @var bool
60
     */
61
    private $async;
62
63
    /**
64
     * The Intervention image engine.
65
     *
66
     * @var ImageManager
67
     */
68
    protected $engine;
69
70
    /**
71
     * @var Filesystem
72
     */
73
    protected $fs;
74
75
    /**
76
     * @var Scaler
77
     */
78
    protected $scaler;
79
80
    /**
81
     * @var Optimizer
82
     */
83
    protected $optimizer;
84
85
    /**
86
     * @var Promise[]
87
     */
88
    protected $promises;
89
90
    /**
91
     * ResponsiveFactory constructor.
92
     *
93
     * @param ResponsiveFactoryConfigurator $configurator
94
     */
95
    public function __construct(ResponsiveFactoryConfigurator $configurator = null) {
96
        $configurator = $configurator ?? new DefaultConfigurator();
97
        $configurator->configure($this);
98
99
        $this->sourcePath = rtrim($this->sourcePath, '/');
100
        $this->publicPath = rtrim($this->publicPath, '/');
101
102
        $this->engine = new ImageManager([
103
            'driver' => $this->driver,
104
        ]);
105
        $this->optimizer = (new OptimizerFactory())->get();
106
        $this->fs = new Filesystem();
107
108
        if (!$this->fs->exists($this->publicPath)) {
109
            $this->fs->mkdir($this->publicPath);
110
        }
111
    }
112
113
    /**
114
     * @param string $src
115
     *
116
     * @return ResponsiveImage
117
     * @throws FileNotFoundException
118
     */
119
    public function create($src) {
120
        $responsiveImage = new ResponsiveImage($src);
121
        $src = $responsiveImage->src();
122
        $sourceImage = $this->getImageFile($this->sourcePath, $src);
123
124
        if (!$sourceImage) {
125
            throw new FileNotFoundException("{$this->sourcePath}{$src}");
126
        }
127
128
        $extension = $sourceImage->getExtension();
129
        $fileName = str_replace(".{$extension}", '', $sourceImage->getFilename());
130
        $publicImagePath = "{$this->publicPath}/{$src}";
131
132
        $urlParts = explode('/', $src);
133
        array_pop($urlParts);
134
        $urlPath = implode('/', $urlParts);
135
136
        $responsiveImage->setExtension($extension);
137
        $responsiveImage->setFileName($fileName);
138
        $responsiveImage->setUrlPath($urlPath);
139
140
        if ($this->enableCache && $this->fs->exists($publicImagePath)) {
141
            /** @var SplFileInfo[] $cachedFiles */
142
            $cachedFiles = Finder::create()->files()->in("{$this->publicPath}/{$sourceImage->getRelativePath()}")->name("{$fileName}-*.{$extension}");
143
144
            foreach ($cachedFiles as $cachedFile) {
145
                $cachedFilename = $cachedFile->getFilename();
146
                $size = (int) str_replace(".{$extension}", '', str_replace("{$fileName}-", '', $cachedFilename));
147
148
                $responsiveImage->addSource("{$urlPath}/{$cachedFilename}", $size);
149
            }
150
151
            return $responsiveImage;
152
        }
153
154
        if (!$this->enableCache || !$this->fs->exists($publicImagePath)) {
155
            $this->fs->dumpFile($publicImagePath, $sourceImage->getContents());
156
        }
157
158
        $imageObject = $this->engine->make($sourceImage->getPathname());
159
160
        // TODO: This piece of code should be added as a size and not as a "default".
161
        // It's because the WidthScaler skips the default size.
162
        $width = $imageObject->getWidth();
163
        $responsiveImage->addSource($src, $width);
164
        $imageObject->destroy();
165
166
        $this->createScaledImages($sourceImage, $responsiveImage);
167
168
        return $responsiveImage;
169
    }
170
171
    /**
172
     * Create scaled image files and add them as sources to a Responsive Image, based on an array of file sizes:
173
     * [
174
     *      width => height,
175
     *      ...
176
     * ]
177
     *
178
     * @param SplFileInfo     $sourceImage
179
     * @param ResponsiveImage $responsiveImage
180
     *
181
     * @return ResponsiveImage
182
     */
183
    public function createScaledImages(SplFileInfo $sourceImage, ResponsiveImage $responsiveImage) : ResponsiveImage {
184
        $async = $this->async && Fork::supported();
185
        $imageObject = $this->engine->make($sourceImage->getPathname());
186
        $urlPath = $responsiveImage->getUrlPath();
187
        $sizes = $this->scaler->scale($sourceImage, $imageObject);
188
189
        foreach ($sizes as $width => $height) {
190
            $scaledFileSrc = trim("{$urlPath}/{$imageObject->filename}-{$width}.{$imageObject->extension}", '/');
191
            $responsiveImage->addSource($scaledFileSrc, $width);
192
        }
193
194
        if ($async) {
195
            $factory = $this;
196
197
            $fork = Fork::spawn(function () use ($factory, $sourceImage, $responsiveImage) {
198
                $factory->scaleProcess($sourceImage, $responsiveImage);
199
            });
200
201
            $responsiveImage->setPromise($fork->join());
202
        } else {
203
            $this->scaleProcess($sourceImage, $responsiveImage);
204
            $deferred = new \Amp\Deferred();
205
            $deferred->resolve();
206
207
            $responsiveImage->setPromise($deferred->promise());
0 ignored issues
show
Documentation introduced by
$deferred->promise() is of type object<Amp\Deferred>, but the function expects a object<AsyncInterop\Promise>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
208
        }
209
210
        return $responsiveImage;
211
    }
212
213
    /**
214
     * @param SplFileInfo     $sourceImage
215
     * @param ResponsiveImage $responsiveImage
216
     */
217
    public function scaleProcess(SplFileInfo $sourceImage, ResponsiveImage $responsiveImage) {
218
        $urlPath = $responsiveImage->getUrlPath();
219
        $imageObject = $this->engine->make($sourceImage->getPathname());
220
        $sizes = $this->scaler->scale($sourceImage, $imageObject);
221
222
        foreach ($sizes as $width => $height) {
223
            $scaledFileSrc = trim("{$urlPath}/{$imageObject->filename}-{$width}.{$imageObject->extension}", '/');
224
            $scaledFilePath = "{$this->getPublicPath()}/{$scaledFileSrc}";
225
226
            $scaledImage = $imageObject->resize((int) $width, (int) $height)->encode($imageObject->extension);
227
228
            if (!$this->enableCache || !$this->fs->exists($scaledFilePath)) {
229
                $this->fs->dumpFile($scaledFilePath, $scaledImage);
230
            }
231
        }
232
233
        $imageObject->destroy();
234
235
        if ($this->optimize) {
236
            $this->optimizeResponsiveImage($responsiveImage);
237
        }
238
    }
239
240
    /**
241
     * Optimize all sources of a Responsive Image
242
     *
243
     * @param ResponsiveImage $responsiveImage
244
     *
245
     * @return ResponsiveImage
246
     */
247
    public function optimizeResponsiveImage(ResponsiveImage $responsiveImage) : ResponsiveImage {
248
        foreach ($responsiveImage->getSrcset() as $imageFile) {
249
            $this->optimizer->optimize("{$this->publicPath}/{$imageFile}");
250
        }
251
252
        return $responsiveImage;
253
    }
254
255
    /**
256
     * @param string $directory
257
     * @param string $path
258
     *
259
     * @return SplFileInfo
260
     */
261
    private function getImageFile(string $directory, string $path) : SplFileInfo {
262
        $iterator = Finder::create()->files()->in($directory)->path(ltrim($path, '/'))->getIterator();
263
        $iterator->rewind();
264
265
        return $iterator->current();
266
    }
267
268
    /**
269
     * @param string $driver
270
     *
271
     * @return ResponsiveFactory
272
     */
273
    public function setDriver($driver) : ResponsiveFactory {
274
        $this->driver = $driver;
275
276
        return $this;
277
    }
278
279
    /**
280
     * @param string $publicPath
281
     *
282
     * @return ResponsiveFactory
283
     */
284
    public function setPublicPath($publicPath) : ResponsiveFactory {
285
        $this->publicPath = $publicPath;
286
287
        return $this;
288
    }
289
290
    /**
291
     * @param boolean $enableCache
292
     *
293
     * @return ResponsiveFactory
294
     */
295
    public function setEnableCache($enableCache) : ResponsiveFactory {
296
        $this->enableCache = $enableCache;
297
298
        return $this;
299
    }
300
301
    /**
302
     * @param string $sourcePath
303
     *
304
     * @return ResponsiveFactory
305
     */
306
    public function setSourcePath($sourcePath) : ResponsiveFactory {
307
        $this->sourcePath = $sourcePath;
308
309
        return $this;
310
    }
311
312
    /**
313
     * @param Scaler $scaler
314
     *
315
     * @return ResponsiveFactory
316
     */
317
    public function setScaler($scaler) : ResponsiveFactory {
318
        $this->scaler = $scaler;
319
320
        return $this;
321
    }
322
323
    /**
324
     * @param bool $optimize
325
     *
326
     * @return ResponsiveFactory
327
     */
328
    public function setOptimize(bool $optimize) : ResponsiveFactory {
329
        $this->optimize = $optimize;
330
331
        return $this;
332
    }
333
334
    /**
335
     * @param bool $async
336
     *
337
     * @return ResponsiveFactory
338
     */
339
    public function setAsync(bool $async) : ResponsiveFactory {
340
        $this->async = $async;
341
342
        return $this;
343
    }
344
345
    /**
346
     * @return string
347
     */
348
    public function getPublicPath() : string {
349
        return $this->publicPath;
350
    }
351
352
}
353