Completed
Pull Request — master (#300)
by
unknown
08:09 queued 04:33
created

Server   F

Complexity

Total Complexity 62

Size/Duplication

Total Lines 567
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 62
lcom 1
cbo 5
dl 0
loc 567
ccs 0
cts 240
cp 0
rs 3.44
c 0
b 0
f 0

33 Methods

Rating   Name   Duplication   Size   Complexity  
A setSourcePathPrefix() 0 4 1
A getSourcePathPrefix() 0 4 1
A getSourcePath() 0 20 4
A setGroupCacheInFolders() 0 4 1
A getGroupCacheInFolders() 0 4 1
A __construct() 0 6 1
A setCache() 0 4 1
A getCache() 0 4 1
A setSource() 0 4 1
A getSource() 0 4 1
A sourceFileExists() 0 8 2
A setBaseUrl() 0 4 1
A getBaseUrl() 0 4 1
A setCachePathPrefix() 0 4 1
A getCachePathPrefix() 0 4 1
A setCacheWithFileExtensions() 0 4 1
A getCacheWithFileExtensions() 0 4 1
B getCachePath() 0 28 7
A cacheFileExists() 0 12 2
A deleteCache() 0 18 3
A setApi() 0 4 1
A getApi() 0 4 1
A setDefaults() 0 4 1
A getDefaults() 0 4 1
A setPresets() 0 4 1
A getPresets() 0 4 1
A getAllParams() 0 14 4
A setResponseFactory() 0 4 1
A getResponseFactory() 0 4 1
A getImageResponse() 0 12 2
A getImageAsBase64() 0 22 4
A outputImage() 0 22 3
B makeImage() 0 61 8

How to fix   Complexity   

Complex Class

Complex classes like Server often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Server, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace League\Glide;
4
5
use InvalidArgumentException;
6
use League\Flysystem\FileExistsException;
7
use League\Flysystem\FilesystemInterface;
8
use League\Flysystem\FilesystemOperator;
9
use League\Flysystem\UnableToReadFile;
10
use League\Glide\Api\ApiInterface;
11
use League\Glide\Filesystem\FileNotFoundException;
12
use League\Glide\Filesystem\FilesystemException;
13
use League\Glide\Responses\ResponseFactoryInterface;
14
15
class Server
16
{
17
    /**
18
     * Source file system.
19
     * @var FilesystemInterface|FilesystemOperator
20
     */
21
    protected $source;
22
23
    /**
24
     * Source path prefix.
25
     * @var string
26
     */
27
    protected $sourcePathPrefix;
28
29
    /**
30
     * Cache file system.
31
     * @var FilesystemInterface|FilesystemOperator
32
     */
33
    protected $cache;
34
35
    /**
36
     * Cache path prefix.
37
     * @var string
38
     */
39
    protected $cachePathPrefix;
40
41
    /**
42
     * Whether to group cache in folders.
43
     * @var bool
44
     */
45
    protected $groupCacheInFolders = true;
46
47
    /**
48
     * Whether to cache with file extensions.
49
     * @var bool
50
     */
51
    protected $cacheWithFileExtensions = false;
52
53
    /**
54
     * Image manipulation API.
55
     * @var ApiInterface
56
     */
57
    protected $api;
58
59
    /**
60
     * Response factory.
61
     * @var ResponseFactoryInterface|null
62
     */
63
    protected $responseFactory;
64
65
    /**
66
     * Base URL.
67
     * @var string
68
     */
69
    protected $baseUrl;
70
71
    /**
72
     * Default image manipulations.
73
     * @var array
74
     */
75
    protected $defaults = [];
76
77
    /**
78
     * Preset image manipulations.
79
     * @var array
80
     */
81
    protected $presets = [];
82
83
    /**
84
     * Create Server instance.
85
     * @param FilesystemInterface|FilesystemOperator $source Source file system.
86
     * @param FilesystemInterface|FilesystemOperator $cache  Cache file system.
87
     * @param ApiInterface        $api    Image manipulation API.
88
     */
89
    public function __construct($source, $cache, ApiInterface $api)
90
    {
91
        $this->setSource($source);
92
        $this->setCache($cache);
93
        $this->setApi($api);
94
    }
95
96
    /**
97
     * Set source file system.
98
     * @param FilesystemInterface|FilesystemOperator $source Source file system.
99
     */
100
    public function setSource($source)
101
    {
102
        $this->source = $source;
103
    }
104
105
    /**
106
     * Get source file system.
107
     * @return FilesystemInterface|FilesystemOperator Source file system.
108
     */
109
    public function getSource()
110
    {
111
        return $this->source;
112
    }
113
114
    /**
115
     * Set source path prefix.
116
     * @param string $sourcePathPrefix Source path prefix.
117
     */
118
    public function setSourcePathPrefix($sourcePathPrefix)
119
    {
120
        $this->sourcePathPrefix = trim($sourcePathPrefix, '/');
121
    }
122
123
    /**
124
     * Get source path prefix.
125
     * @return string Source path prefix.
126
     */
127
    public function getSourcePathPrefix()
128
    {
129
        return $this->sourcePathPrefix;
130
    }
131
132
    /**
133
     * Get source path.
134
     * @param  string                $path Image path.
135
     * @return string                The source path.
136
     * @throws FileNotFoundException
137
     */
138
    public function getSourcePath($path)
139
    {
140
        $path = trim($path, '/');
141
        
142
        $baseUrl = $this->baseUrl.'/';
143
144
        if (substr($path, 0, strlen($baseUrl)) === $baseUrl) {
145
            $path = trim(substr($path, strlen($baseUrl)), '/');
146
        }
147
148
        if ($path === '') {
149
            throw new FileNotFoundException('Image path missing.');
150
        }
151
152
        if ($this->sourcePathPrefix) {
153
            $path = $this->sourcePathPrefix.'/'.$path;
154
        }
155
156
        return rawurldecode($path);
157
    }
158
159
    /**
160
     * Check if a source file exists.
161
     * @param  string $path Image path.
162
     * @return bool   Whether the source file exists.
163
     */
164
    public function sourceFileExists($path)
165
    {
166
        if ($this->source instanceof FilesystemInterface) {
0 ignored issues
show
Bug introduced by
The class League\Flysystem\FilesystemInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
167
            return $this->source->has($this->getSourcePath($path));
168
        }
169
170
        return $this->source->fileExists($this->getSourcePath($path));
171
    }
172
173
    /**
174
     * Set base URL.
175
     * @param string $baseUrl Base URL.
176
     */
177
    public function setBaseUrl($baseUrl)
178
    {
179
        $this->baseUrl = trim($baseUrl, '/');
180
    }
181
182
    /**
183
     * Get base URL.
184
     * @return string Base URL.
185
     */
186
    public function getBaseUrl()
187
    {
188
        return $this->baseUrl;
189
    }
190
191
    /**
192
     * Set cache file system.
193
     * @param FilesystemInterface|FilesystemOperator $cache Cache file system.
194
     */
195
    public function setCache($cache)
196
    {
197
        $this->cache = $cache;
198
    }
199
200
    /**
201
     * Get cache file system.
202
     * @return FilesystemInterface|FilesystemOperator Cache file system.
203
     */
204
    public function getCache()
205
    {
206
        return $this->cache;
207
    }
208
209
    /**
210
     * Set cache path prefix.
211
     * @param string $cachePathPrefix Cache path prefix.
212
     */
213
    public function setCachePathPrefix($cachePathPrefix)
214
    {
215
        $this->cachePathPrefix = trim($cachePathPrefix, '/');
216
    }
217
218
    /**
219
     * Get cache path prefix.
220
     * @return string Cache path prefix.
221
     */
222
    public function getCachePathPrefix()
223
    {
224
        return $this->cachePathPrefix;
225
    }
226
227
    /**
228
     * Set the group cache in folders setting.
229
     * @param bool $groupCacheInFolders Whether to group cache in folders.
230
     */
231
    public function setGroupCacheInFolders($groupCacheInFolders)
232
    {
233
        $this->groupCacheInFolders = $groupCacheInFolders;
234
    }
235
236
    /**
237
     * Get the group cache in folders setting.
238
     * @return bool Whether to group cache in folders.
239
     */
240
    public function getGroupCacheInFolders()
241
    {
242
        return $this->groupCacheInFolders;
243
    }
244
245
    /**
246
     * Set the cache with file extensions setting.
247
     * @param bool $cacheWithFileExtensions Whether to cache with file extensions.
248
     */
249
    public function setCacheWithFileExtensions($cacheWithFileExtensions)
250
    {
251
        $this->cacheWithFileExtensions = $cacheWithFileExtensions;
252
    }
253
254
    /**
255
     * Get the cache with file extensions setting.
256
     * @return bool Whether to cache with file extensions.
257
     */
258
    public function getCacheWithFileExtensions()
259
    {
260
        return $this->cacheWithFileExtensions;
261
    }
262
263
    /**
264
     * Get cache path.
265
     * @param  string $path   Image path.
266
     * @param  array  $params Image manipulation params.
267
     * @return string Cache path.
268
     */
269
    public function getCachePath($path, array $params = [])
270
    {
271
        $sourcePath = $this->getSourcePath($path);
272
273
        if ($this->sourcePathPrefix) {
274
            $sourcePath = substr($sourcePath, strlen($this->sourcePathPrefix) + 1);
275
        }
276
277
        $params = $this->getAllParams($params);
278
        unset($params['s'], $params['p']);
279
        ksort($params);
280
281
        $md5 = md5($sourcePath.'?'.http_build_query($params));
282
283
        $cachedPath = $this->groupCacheInFolders ? $sourcePath.'/'.$md5 : $md5;
284
285
        if ($this->cachePathPrefix) {
286
            $cachedPath = $this->cachePathPrefix.'/'.$cachedPath;
287
        }
288
        
289
        if ($this->cacheWithFileExtensions) {
290
            $ext = (isset($params['fm']) ? $params['fm'] : pathinfo($path)['extension']);
291
            $ext = ($ext === 'pjpg') ? 'jpg' : $ext;
292
            $cachedPath .= '.'.$ext;
293
        }
294
295
        return $cachedPath;
296
    }
297
298
    /**
299
     * Check if a cache file exists.
300
     * @param  string $path   Image path.
301
     * @param  array  $params Image manipulation params.
302
     * @return bool   Whether the cache file exists.
303
     */
304
    public function cacheFileExists($path, array $params)
305
    {
306
        if ($this->cache instanceof FilesystemInterface) {
0 ignored issues
show
Bug introduced by
The class League\Flysystem\FilesystemInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
307
            return $this->cache->has(
308
                $this->getCachePath($path, $params)
309
            );
310
        }
311
312
        return $this->cache->fileExists(
313
            $this->getCachePath($path, $params)
314
        );
315
    }
316
317
    /**
318
     * Delete cached manipulations for an image.
319
     * @param  string $path Image path.
320
     * @return bool   Whether the delete succeeded.
321
     */
322
    public function deleteCache($path)
323
    {
324
        if (!$this->groupCacheInFolders) {
325
            throw new InvalidArgumentException(
326
                'Deleting cached image manipulations is not possible when grouping cache into folders is disabled.'
327
            );
328
        }
329
330
        if ($this->cache instanceof FilesystemInterface) {
0 ignored issues
show
Bug introduced by
The class League\Flysystem\FilesystemInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
331
            return $this->cache->deleteDir(
332
                dirname($this->getCachePath($path))
333
            );
334
        }
335
336
        return $this->cache->deleteDirectory(
337
            dirname($this->getCachePath($path))
338
        );
339
    }
340
341
    /**
342
     * Set image manipulation API.
343
     * @param ApiInterface $api Image manipulation API.
344
     */
345
    public function setApi(ApiInterface $api)
346
    {
347
        $this->api = $api;
348
    }
349
350
    /**
351
     * Get image manipulation API.
352
     * @return ApiInterface Image manipulation API.
353
     */
354
    public function getApi()
355
    {
356
        return $this->api;
357
    }
358
359
    /**
360
     * Set default image manipulations.
361
     * @param array $defaults Default image manipulations.
362
     */
363
    public function setDefaults(array $defaults)
364
    {
365
        $this->defaults = $defaults;
366
    }
367
368
    /**
369
     * Get default image manipulations.
370
     * @return array Default image manipulations.
371
     */
372
    public function getDefaults()
373
    {
374
        return $this->defaults;
375
    }
376
377
    /**
378
     * Set preset image manipulations.
379
     * @param array $presets Preset image manipulations.
380
     */
381
    public function setPresets(array $presets)
382
    {
383
        $this->presets = $presets;
384
    }
385
386
    /**
387
     * Get preset image manipulations.
388
     * @return array Preset image manipulations.
389
     */
390
    public function getPresets()
391
    {
392
        return $this->presets;
393
    }
394
395
    /**
396
     * Get all image manipulations params, including defaults and presets.
397
     * @param  array $params Image manipulation params.
398
     * @return array All image manipulation params.
399
     */
400
    public function getAllParams(array $params)
401
    {
402
        $all = $this->defaults;
403
404
        if (isset($params['p'])) {
405
            foreach (explode(',', $params['p']) as $preset) {
406
                if (isset($this->presets[$preset])) {
407
                    $all = array_merge($all, $this->presets[$preset]);
408
                }
409
            }
410
        }
411
412
        return array_merge($all, $params);
413
    }
414
415
    /**
416
     * Set response factory.
417
     * @param ResponseFactoryInterface|null $responseFactory Response factory.
418
     */
419
    public function setResponseFactory(ResponseFactoryInterface $responseFactory = null)
420
    {
421
        $this->responseFactory = $responseFactory;
422
    }
423
424
    /**
425
     * Get response factory.
426
     * @return ResponseFactoryInterface Response factory.
427
     */
428
    public function getResponseFactory()
429
    {
430
        return $this->responseFactory;
431
    }
432
433
    /**
434
     * Generate and return image response.
435
     * @param  string                   $path   Image path.
436
     * @param  array                    $params Image manipulation params.
437
     * @return mixed                    Image response.
438
     * @throws InvalidArgumentException
439
     */
440
    public function getImageResponse($path, array $params)
441
    {
442
        if (is_null($this->responseFactory)) {
443
            throw new InvalidArgumentException(
444
                'Unable to get image response, no response factory defined.'
445
            );
446
        }
447
448
        $path = $this->makeImage($path, $params);
449
450
        return $this->responseFactory->create($this->cache, $path);
0 ignored issues
show
Bug introduced by
It seems like $this->cache can also be of type object<League\Flysystem\FilesystemOperator>; however, League\Glide\Responses\R...toryInterface::create() does only seem to accept object<League\Flysystem\FilesystemInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
451
    }
452
453
    /**
454
     * Generate and return Base64 encoded image.
455
     * @param  string              $path   Image path.
456
     * @param  array               $params Image manipulation params.
457
     * @return string              Base64 encoded image.
458
     * @throws FilesystemException
459
     */
460
    public function getImageAsBase64($path, array $params)
461
    {
462
        $path = $this->makeImage($path, $params);
463
464
        try {
465
            $source = $this->cache->read($path);
466
        } catch (UnableToReadFile $exception) {
467
            $source = false;
468
        }
469
470
        if ($source === false) {
471
            throw new FilesystemException(
472
                'Could not read the image `'.$path.'`.'
473
            );
474
        }
475
476
        if ($this->cache instanceof FilesystemInterface) {
0 ignored issues
show
Bug introduced by
The class League\Flysystem\FilesystemInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
477
            return 'data:'.$this->cache->getMimetype($path).';base64,'.base64_encode($source);
478
        }
479
480
        return 'data:'.$this->cache->mimeType($path).';base64,'.base64_encode($source);
481
    }
482
483
    /**
484
     * Generate and output image.
485
     * @param  string                   $path   Image path.
486
     * @param  array                    $params Image manipulation params.
487
     * @throws InvalidArgumentException
488
     */
489
    public function outputImage($path, array $params)
490
    {
491
        $path = $this->makeImage($path, $params);
492
493
        if ($this->cache instanceof FilesystemInterface) {
0 ignored issues
show
Bug introduced by
The class League\Flysystem\FilesystemInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
494
            header('Content-Type:'.$this->cache->getMimetype($path));
495
            header('Content-Length:'.$this->cache->getSize($path));
496
        } else {
497
            header('Content-Type:'.$this->cache->mimeType($path));
498
            header('Content-Length:'.$this->cache->fileSize($path));
499
        }
500
        header('Cache-Control:'.'max-age=31536000, public');
501
        header('Expires:'.date_create('+1 years')->format('D, d M Y H:i:s').' GMT');
502
503
        $stream = $this->cache->readStream($path);
504
505
        if (ftell($stream) !== 0) {
506
            rewind($stream);
507
        }
508
        fpassthru($stream);
509
        fclose($stream);
510
    }
511
512
    /**
513
     * Generate manipulated image.
514
     * @param  string                $path   Image path.
515
     * @param  array                 $params Image manipulation params.
516
     * @return string                Cache path.
517
     * @throws FileNotFoundException
518
     * @throws FilesystemException
519
     */
520
    public function makeImage($path, array $params)
521
    {
522
        $sourcePath = $this->getSourcePath($path);
523
        $cachedPath = $this->getCachePath($path, $params);
524
525
        if ($this->cacheFileExists($path, $params) === true) {
526
            return $cachedPath;
527
        }
528
529
        if ($this->sourceFileExists($path) === false) {
530
            throw new FileNotFoundException(
531
                'Could not find the image `'.$sourcePath.'`.'
532
            );
533
        }
534
535
        try {
536
            $source = $this->source->read(
537
                $sourcePath
538
            );
539
        } catch (UnableToReadFile $exception) {
540
            $source = false;
541
        }
542
543
        if ($source === false) {
544
            throw new FilesystemException(
545
                'Could not read the image `'.$sourcePath.'`.'
546
            );
547
        }
548
549
        // We need to write the image to the local disk before
550
        // doing any manipulations. This is because EXIF data
551
        // can only be read from an actual file.
552
        $tmp = tempnam(sys_get_temp_dir(), 'Glide');
553
554
        if (file_put_contents($tmp, $source) === false) {
555
            throw new FilesystemException(
556
                'Unable to write temp file for `'.$sourcePath.'`.'
557
            );
558
        }
559
560
        try {
561
            $write = $this->cache->write(
562
                $cachedPath,
563
                $this->api->run($tmp, $this->getAllParams($params))
564
            );
565
566
            if ($write === false) {
567
                throw new FilesystemException(
568
                    'Could not write the image `'.$cachedPath.'`.'
569
                );
570
            }
571
        } catch (FileExistsException $exception) {
0 ignored issues
show
Bug introduced by
The class League\Flysystem\FileExistsException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
572
            // This edge case occurs when the target already exists
573
            // because it's currently be written to disk in another
574
            // request. It's best to just fail silently.
575
        } finally {
576
            unlink($tmp);
577
        }
578
579
        return $cachedPath;
580
    }
581
}
582