Passed
Push — master ( a02eb8...bca956 )
by Brent
04:37 queued 02:11
created

ResponsiveFactory::getPublicPath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
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
165
        $sizes = $this->scaler->scale($sourceImage, $imageObject);
166
        $this->createScaledImages($imageObject, $responsiveImage, $sizes);
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 array           $sizes
179
     * @param Image           $imageObject
180
     * @param ResponsiveImage $responsiveImage
181
     *
182
     * @return ResponsiveImage
183
     *
184
     * @TODO: refactor code duplication
185
     */
186
    public function createScaledImages(Image $imageObject, ResponsiveImage $responsiveImage, array $sizes) : ResponsiveImage {
187
        $urlPath = $responsiveImage->getUrlPath();
188
        $async = $this->async && Fork::supported();
189
190
        if ($async) {
191
            $factory = $this;
192
            $optimize = $this->optimize;
193
194
            $fork = Fork::spawn(function () use ($factory, $imageObject, $responsiveImage, $sizes, $urlPath, $optimize) {
195
                foreach ($sizes as $width => $height) {
196
                    $scaledFileSrc = trim("{$urlPath}/{$imageObject->filename}-{$width}.{$imageObject->extension}", '/');
197
                    $scaledFilePath = "{$factory->getPublicPath()}/{$scaledFileSrc}";
198
199
                    $scaledImage = $imageObject->resize((int) $width, (int) $height)->encode($imageObject->extension);
200
                    $factory->saveImageFile($scaledFilePath, $scaledImage);
201
                }
202
203
                if ($optimize) {
204
                    $factory->optimizeResponsiveImage($responsiveImage);
205
                }
206
            });
207
208
            $responsiveImage->setPromise($fork->join());
209
        }
210
211
        foreach ($sizes as $width => $height) {
212
            $scaledFileSrc = trim("{$urlPath}/{$imageObject->filename}-{$width}.{$imageObject->extension}", '/');
213
            $scaledFilePath = "{$this->publicPath}/{$scaledFileSrc}";
214
215
            $responsiveImage->addSource($scaledFileSrc, $width);
216
217
            if (!$async) {
218
                $deferred = new \Amp\Deferred();
219
                $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...
220
                $deferred->resolve();
221
222
                $this->scaleImage($scaledFilePath, $imageObject, $width, $height);
223
224
                if ($this->optimize) {
225
                    $this->optimizeResponsiveImage($responsiveImage);
226
                }
227
            }
228
        }
229
230
        return $responsiveImage;
231
    }
232
233
    /**
234
     * Scale an image and save it.
235
     *
236
     * @param string $path
237
     * @param Image  $imageObject
238
     * @param        $width
239
     * @param        $height
240
     *
241
     * @return Image
242
     */
243
    public function scaleImage(string $path, Image $imageObject, $width, $height) : Image {
244
        $scaledImage = $imageObject->resize((int) $width, (int) $height)->encode($imageObject->extension);
245
246
        $this->saveImageFile($path, $scaledImage);
247
248
        return $scaledImage;
249
    }
250
251
    /**
252
     * Save the image file contents to a path
253
     *
254
     * @param string $path
255
     * @param string $image
256
     */
257
    public function saveImageFile(string $path, string $image) {
258
        if (!$this->enableCache || !$this->fs->exists($path)) {
259
            $this->fs->dumpFile($path, $image);
260
        }
261
    }
262
263
    /**
264
     * Optimize all sources of a Responsive Image
265
     *
266
     * @param ResponsiveImage $responsiveImage
267
     *
268
     * @return ResponsiveImage
269
     */
270
    public function optimizeResponsiveImage(ResponsiveImage $responsiveImage) : ResponsiveImage {
271
        foreach ($responsiveImage->getSrcset() as $imageFile) {
272
            $this->optimizer->optimize("{$this->publicPath}/{$imageFile}");
273
        }
274
275
        return $responsiveImage;
276
    }
277
278
    /**
279
     * @param string $directory
280
     * @param string $path
281
     *
282
     * @return SplFileInfo
283
     */
284
    private function getImageFile(string $directory, string $path) : SplFileInfo {
285
        $iterator = Finder::create()->files()->in($directory)->path(ltrim($path, '/'))->getIterator();
286
        $iterator->rewind();
287
288
        return $iterator->current();
289
    }
290
291
    /**
292
     * @param string $driver
293
     *
294
     * @return ResponsiveFactory
295
     */
296
    public function setDriver($driver) : ResponsiveFactory {
297
        $this->driver = $driver;
298
299
        return $this;
300
    }
301
302
    /**
303
     * @param string $publicPath
304
     *
305
     * @return ResponsiveFactory
306
     */
307
    public function setPublicPath($publicPath) : ResponsiveFactory {
308
        $this->publicPath = $publicPath;
309
310
        return $this;
311
    }
312
313
    /**
314
     * @param boolean $enableCache
315
     *
316
     * @return ResponsiveFactory
317
     */
318
    public function setEnableCache($enableCache) : ResponsiveFactory {
319
        $this->enableCache = $enableCache;
320
321
        return $this;
322
    }
323
324
    /**
325
     * @param string $sourcePath
326
     *
327
     * @return ResponsiveFactory
328
     */
329
    public function setSourcePath($sourcePath) : ResponsiveFactory {
330
        $this->sourcePath = $sourcePath;
331
332
        return $this;
333
    }
334
335
    /**
336
     * @param Scaler $scaler
337
     *
338
     * @return ResponsiveFactory
339
     */
340
    public function setScaler($scaler) : ResponsiveFactory {
341
        $this->scaler = $scaler;
342
343
        return $this;
344
    }
345
346
    /**
347
     * @param bool $optimize
348
     *
349
     * @return ResponsiveFactory
350
     */
351
    public function setOptimize(bool $optimize) : ResponsiveFactory {
352
        $this->optimize = $optimize;
353
354
        return $this;
355
    }
356
357
    /**
358
     * @param bool $async
359
     *
360
     * @return ResponsiveFactory
361
     */
362
    public function setAsync(bool $async) : ResponsiveFactory {
363
        $this->async = $async;
364
365
        return $this;
366
    }
367
368
    /**
369
     * @return string
370
     */
371
    public function getPublicPath() : string {
372
        return $this->publicPath;
373
    }
374
375
}
376