Passed
Push — master ( e21e50...142758 )
by Brent
04:59 queued 02:31
created

ResponsiveFactory::create()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 9.2
c 0
b 0
f 0
cc 3
eloc 15
nc 4
nop 1
1
<?php
2
3
namespace Brendt\Image;
4
5
use Brendt\Image\Config\DefaultConfigurator;
6
use Brendt\Image\Config\ResponsiveFactoryConfigurator;
7
use Brendt\Image\Exception\FileNotFoundException;
8
use Brendt\Image\Scaler\Scaler;
9
use ImageOptimizer\Optimizer;
10
use ImageOptimizer\OptimizerFactory;
11
use Intervention\Image\ImageManager;
12
use Symfony\Component\Filesystem\Filesystem;
13
use Symfony\Component\Finder\Finder;
14
use Symfony\Component\Finder\SplFileInfo;
15
16
class ResponsiveFactory
17
{
18
19
    /**
20
     * The image driver to use.
21
     * Available drivers: 'gd' and 'imagick'.
22
     *
23
     * @var string
24
     */
25
    protected $driver;
26
27
    /**
28
     * The source path to load images from.
29
     *
30
     * @var string
31
     */
32
    protected $sourcePath;
33
34
    /**
35
     * The public path to save rendered images.
36
     *
37
     * @var string
38
     */
39
    protected $publicPath;
40
41
    /**
42
     * Enabled cache will stop generated images from being overwritten.
43
     *
44
     * @var bool
45
     */
46
    private $enableCache;
47
48
    /**
49
     * Enable optimizers will run several image optimizers on the saved files.
50
     *
51
     * @var bool
52
     */
53
    private $optimize;
54
55
    /**
56
     * @var bool
57
     */
58
    private $rebase;
59
60
    /**
61
     * @var array
62
     */
63
    private $optimizerOptions = [];
64
65
    /**
66
     * The Intervention image engine.
67
     *
68
     * @var ImageManager
69
     */
70
    protected $engine;
71
72
    /**
73
     * @var Filesystem
74
     */
75
    protected $fs;
76
77
    /**
78
     * @var Scaler
79
     */
80
    protected $scaler;
81
82
    /**
83
     * @var Optimizer
84
     */
85
    protected $optimizer;
86
87
    /**
88
     * ResponsiveFactory constructor.
89
     *
90
     * @param ResponsiveFactoryConfigurator $configurator
91
     */
92
    public function __construct(ResponsiveFactoryConfigurator $configurator = null) {
93
        $configurator = $configurator ?? new DefaultConfigurator();
94
        $configurator->configure($this);
95
96
        $this->sourcePath = rtrim($this->sourcePath, '/');
97
        $this->publicPath = rtrim($this->publicPath, '/');
98
99
        $this->engine = new ImageManager([
100
            'driver' => $this->driver,
101
        ]);
102
103
        $this->optimizer = (new OptimizerFactory($this->optimizerOptions))->get();
104
        $this->fs = new Filesystem();
105
106
        if (!$this->fs->exists($this->publicPath)) {
107
            $this->fs->mkdir($this->publicPath);
108
        }
109
    }
110
111
    /**
112
     * @param string $src
113
     *
114
     * @return ResponsiveImage
115
     * @throws FileNotFoundException
116
     */
117
    public function create($src) {
118
        $responsiveImage = new ResponsiveImage($src);
119
        $src = $responsiveImage->src();
120
        $sourceFilename = $this->rebase ? pathinfo($src, PATHINFO_BASENAME) : $src;
121
        $sourceFile = $this->getImageFile($this->sourcePath, $sourceFilename);
122
123
        $filename = pathinfo($sourceFile->getFilename(), PATHINFO_FILENAME);
124
        $urlPath = '/' . trim(pathinfo($src, PATHINFO_DIRNAME), '/');
125
126
        $responsiveImage->setExtension($sourceFile->getExtension());
127
        $responsiveImage->setFileName($filename);
128
        $responsiveImage->setUrlPath($urlPath);
129
130
        if ($cachedResponsiveImage = $this->getCachedResponsiveImage($responsiveImage, $sourceFile)) {
0 ignored issues
show
Bug introduced by
It seems like $sourceFile defined by $this->getImageFile($thi...ePath, $sourceFilename) on line 121 can be null; however, Brendt\Image\ResponsiveF...CachedResponsiveImage() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
131
            return $cachedResponsiveImage;
132
        }
133
134
        $this->fs->dumpFile("{$this->publicPath}{$src}", $sourceFile->getContents());
135
        $this->createScaledImages($sourceFile, $responsiveImage);
0 ignored issues
show
Bug introduced by
It seems like $sourceFile defined by $this->getImageFile($thi...ePath, $sourceFilename) on line 121 can be null; however, Brendt\Image\ResponsiveF...y::createScaledImages() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
136
        
137
        return $responsiveImage;
138
    }
139
140
    /**
141
     * @param ResponsiveImage $responsiveImage
142
     * @param SplFileInfo     $imageFile
143
     *
144
     * @return ResponsiveImage|null
145
     */
146
    public function getCachedResponsiveImage(ResponsiveImage $responsiveImage, SplFileInfo $imageFile) : ?ResponsiveImage {
147
        $src = $responsiveImage->src();
148
        $publicImagePath = "{$this->publicPath}{$src}";
149
150
        if ($this->enableCache && $this->fs->exists($publicImagePath)) {
151
            $extension = $imageFile->getExtension();
152
            $publicDirectory = $this->rebase ? trim(pathinfo($src, PATHINFO_DIRNAME), '/') : $imageFile->getRelativePath();
153
            $imageFilename = pathinfo($imageFile->getFilename(), PATHINFO_FILENAME);
154
            
155
            /** @var SplFileInfo[] $cachedFiles */
156
            $cachedFiles = Finder::create()->files()->in("{$this->publicPath}/{$publicDirectory}")->name("{$imageFilename}-*.{$extension}");
157
158
            foreach ($cachedFiles as $cachedFile) {
159
                $cachedFilename = $cachedFile->getFilename();
160
                $size = (int) str_replace(".{$extension}", '', str_replace("{$imageFilename}-", '', $cachedFilename));
161
162
                $responsiveImage->addSource("{$responsiveImage->getUrlPath()}/{$cachedFilename}", $size);
163
            }
164
165
            return $responsiveImage;
166
        }
167
168
        return null;
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
        $imageObject = $this->engine->make($sourceImage->getPathname());
185
        $urlPath = $responsiveImage->getUrlPath();
186
        $sizes = $this->scaler->scale($sourceImage, $imageObject);
187
188
        foreach ($sizes as $width => $height) {
189
            $scaledFileSrc = trim("{$urlPath}/{$imageObject->filename}-{$width}.{$imageObject->extension}", '/');
190
            $scaledFilePath = "{$this->getPublicPath()}/{$scaledFileSrc}";
191
            $responsiveImage->addSource($scaledFileSrc, $width);
192
193
            $scaledImage = $imageObject->resize((int) $width, (int) $height)->encode($imageObject->extension);
194
195
            if (!$this->enableCache || !$this->fs->exists($scaledFilePath)) {
196
                $this->fs->dumpFile($scaledFilePath, $scaledImage);
197
            }
198
        }
199
200
        $imageObject->destroy();
201
202
        if ($this->optimize) {
203
            $this->optimizeResponsiveImage($responsiveImage);
204
        }
205
206
        return $responsiveImage;
207
    }
208
209
    /**
210
     * Optimize all sources of a Responsive Image
211
     *
212
     * @param ResponsiveImage $responsiveImage
213
     *
214
     * @return ResponsiveImage
215
     */
216
    private function optimizeResponsiveImage(ResponsiveImage $responsiveImage) : ResponsiveImage {
217
        foreach ($responsiveImage->getSrcset() as $imageFile) {
218
            $this->optimizer->optimize("{$this->publicPath}/{$imageFile}");
219
        }
220
221
        return $responsiveImage;
222
    }
223
224
    /**
225
     * @param string $directory
226
     * @param string $path
227
     *
228
     * @return null|SplFileInfo
229
     * @throws FileNotFoundException
230
     */
231
    private function getImageFile(string $directory, string $path) : ?SplFileInfo {
232
        $path = ltrim($path, '/');
233
        $iterator = Finder::create()->files()->in($directory)->path($path)->getIterator();
234
        $iterator->rewind();
235
236
        $sourceImage = $iterator->current();
237
238
        if (!$sourceImage) {
239
            throw new FileNotFoundException("{$this->sourcePath}/{$path}");
240
        }
241
242
        return $sourceImage;
243
    }
244
245
    /**
246
     * @param string $driver
247
     *
248
     * @return ResponsiveFactory
249
     */
250
    public function setDriver($driver) : ResponsiveFactory {
251
        $this->driver = $driver;
252
253
        return $this;
254
    }
255
256
    /**
257
     * @param string $publicPath
258
     *
259
     * @return ResponsiveFactory
260
     */
261
    public function setPublicPath($publicPath) : ResponsiveFactory {
262
        $this->publicPath = $publicPath;
263
264
        return $this;
265
    }
266
267
    /**
268
     * @param boolean $enableCache
269
     *
270
     * @return ResponsiveFactory
271
     */
272
    public function setEnableCache($enableCache) : ResponsiveFactory {
273
        $this->enableCache = $enableCache;
274
275
        return $this;
276
    }
277
278
    /**
279
     * @param string $sourcePath
280
     *
281
     * @return ResponsiveFactory
282
     */
283
    public function setSourcePath($sourcePath) : ResponsiveFactory {
284
        $this->sourcePath = $sourcePath;
285
286
        return $this;
287
    }
288
289
    /**
290
     * @param Scaler $scaler
291
     *
292
     * @return ResponsiveFactory
293
     */
294
    public function setScaler($scaler) : ResponsiveFactory {
295
        $this->scaler = $scaler;
296
297
        return $this;
298
    }
299
300
    /**
301
     * @param bool $optimize
302
     *
303
     * @return ResponsiveFactory
304
     */
305
    public function setOptimize(bool $optimize) : ResponsiveFactory {
306
        $this->optimize = $optimize;
307
308
        return $this;
309
    }
310
311
    /**
312
     * @return string
313
     */
314
    public function getPublicPath() : string {
315
        return $this->publicPath;
316
    }
317
318
    /**
319
     * @param mixed $optimizerOptions
320
     *
321
     * @return ResponsiveFactory
322
     */
323
    public function setOptimizerOptions($optimizerOptions) : ResponsiveFactory {
324
        $this->optimizerOptions = $optimizerOptions;
0 ignored issues
show
Documentation Bug introduced by
It seems like $optimizerOptions of type * is incompatible with the declared type array of property $optimizerOptions.

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...
325
326
        return $this;
327
    }
328
329
    /**
330
     * @param bool $rebase
331
     *
332
     * @return ResponsiveFactory
333
     */
334
    public function setRebase(bool $rebase) : ResponsiveFactory {
335
        $this->rebase = $rebase;
336
337
        return $this;
338
    }
339
340
}
341