Image   A
last analyzed

Complexity

Total Complexity 41

Size/Duplication

Total Lines 390
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 139
dl 0
loc 390
rs 9.1199
c 3
b 0
f 0
wmc 41

21 Methods

Rating   Name   Duplication   Size   Complexity  
A setOriginalWidth() 0 4 1
A setCropPositionX() 0 4 1
A getPath() 0 3 1
A getTargetPath() 0 3 1
A getOriginalHeight() 0 3 1
A getCropPositionX() 0 3 1
A getCropPositionY() 0 3 1
A setTargetHeight() 0 4 1
A setTargetPath() 0 4 1
A setTargetWidth() 0 4 1
A setSplFileInfo() 0 5 1
A getTargetWidth() 0 3 1
A setPath() 0 5 1
A getOriginalWidth() 0 3 1
A getTargetHeight() 0 3 1
A setOriginalHeight() 0 4 1
A getSplFileInfo() 0 3 1
A setCropPositionY() 0 4 1
B __construct() 0 31 8
A generateFileName() 0 18 1
C calculateCropPositions() 0 68 14

How to fix   Complexity   

Complex Class

Complex classes like Image 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.

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 Image, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Rvdlee\ZfImageResizer\Model;
4
5
use Rvdlee\ZfImageResizer\Exception\InvalidArgumentException;
6
use SplFileInfo;
7
8
class Image
9
{
10
    const ONLY_RESIZE_MODUS     = 'resize';
11
    const CROP_AND_RESIZE_MODUS = 'crop-and-resize';
12
    const ONLY_CROP_MODUS       = 'crop';
13
    const MODUS                 = [
14
        self::ONLY_RESIZE_MODUS,
15
        self::CROP_AND_RESIZE_MODUS,
16
        self::ONLY_CROP_MODUS,
17
    ];
18
19
    const MANUAL_CROP       = 'manual';
20
    const UPPER_LEFT_CROP   = 'upper-left';
21
    const UPPER_MIDDLE_CROP = 'upper-middle';
22
    const UPPER_RIGHT_CROP  = 'upper-right';
23
    const MIDDLE_LEFT_CROP  = 'middle-left';
24
    const CENTERED_CROP     = 'centered';
25
    const MIDDLE_RIGHT_CROP = 'middle-right';
26
    const BOTTOM_LEFT       = 'bottom-left';
27
    const BOTTOM_MIDDLE     = 'bottom-middle';
28
    const BOTTOM_RIGHT      = 'bottom-right';
29
    const CROP_MODUS        = [
30
        self::MANUAL_CROP,
31
        self::UPPER_LEFT_CROP,
32
        self::UPPER_MIDDLE_CROP,
33
        self::UPPER_RIGHT_CROP,
34
        self::MIDDLE_LEFT_CROP,
35
        self::CENTERED_CROP,
36
        self::MIDDLE_RIGHT_CROP,
37
        self::BOTTOM_LEFT,
38
        self::BOTTOM_MIDDLE,
39
        self::BOTTOM_RIGHT,
40
    ];
41
42
    /**
43
     * @var string
44
     */
45
    protected $path;
46
47
    /**
48
     * @var string
49
     */
50
    protected $targetPath;
51
52
    /**
53
     * @var SplFileInfo
54
     */
55
    protected $splFileInfo;
56
57
    /**
58
     * @var int
59
     */
60
    protected $originalWidth;
61
62
    /**
63
     * @var int
64
     */
65
    protected $originalHeight;
66
67
    /**
68
     * @var int
69
     */
70
    protected $targetWidth = 0;
71
72
    /**
73
     * @var int
74
     */
75
    protected $targetHeight = 0;
76
77
    /**
78
     * @var int
79
     */
80
    protected $cropPositionX = 0;
81
82
    /**
83
     * @var int
84
     */
85
    protected $cropPositionY = 0;
86
87
    /**
88
     * Image constructor.
89
     *
90
     * @param string $path
91
     * @param int    $targetWidth
92
     * @param int    $targetHeight
93
     * @param string $cropMode
94
     * @param int    $x
95
     * @param int    $y
96
     * @throws InvalidArgumentException
97
     */
98
    public function __construct(string $path, int $targetWidth, int $targetHeight, string $cropMode = self::CENTERED_CROP, int $x = 0, int $y = 0)
99
    {
100
        // Check if given path exists
101
        if ( ! file_exists($path)) {
102
            throw new InvalidArgumentException(sprintf('The provided file(%s) does not exist.', $path));
103
        }
104
105
        // Get the original image dimensions and calculate ratio
106
        list($width, $height) = getimagesize($path);
107
        $ratio = ($width < $height ? $width : $height) / ($width > $height ? $width : $height);
108
109
        // If either of these is not set, scale to ratio
110
        if ($targetWidth === 0 || $targetHeight === 0) {
111
            if ($targetWidth === 0) {
112
                $targetWidth = $width * $ratio;
113
            }
114
115
            if ($targetHeight === 0) {
116
                $targetHeight = $height * $ratio;
117
            }
118
        }
119
120
        // Prep this model
121
        $this->setPath($path)
122
             ->setSplFileInfo(new SplFileInfo($path))
123
             ->setOriginalWidth($width)
124
             ->setOriginalHeight($height)
125
             ->setTargetWidth($targetWidth)
126
             ->setTargetHeight($targetHeight)
127
             ->calculateCropPositions($cropMode, $x, $y)
128
             ->setTargetPath($this->generateFileName());
129
    }
130
131
    /**
132
     * Generate a new unique name based on all the image parameters
133
     *
134
     * @return mixed
135
     */
136
    public function generateFileName()
137
    {
138
        // Extract the basename out of the path
139
        $filename = basename($this->getPath());
140
        // Get the filepath
141
        $explodedFilepath = explode(DIRECTORY_SEPARATOR, $this->getPath());
142
        // Reconstruct the base file path
143
        $baseFilepath = implode(DIRECTORY_SEPARATOR, array_slice($explodedFilepath, 0, count($explodedFilepath)-1));
144
        // Explode the filename
145
        $explodedFilename = explode('.', $filename);
146
        // Generate new name
147
        $thumbname = sprintf(
148
            '%s.%s',
149
            bin2hex(random_bytes(64)),
150
            end($explodedFilename)
151
        );
152
153
        return $baseFilepath . DIRECTORY_SEPARATOR . $thumbname;
154
    }
155
156
157
    /**
158
     * @param string $cropMode
159
     * @param int    $cropPositionX
160
     * @param int    $cropPositionY
161
     * @return Image
162
     * @throws InvalidArgumentException
163
     */
164
    public function calculateCropPositions(
165
        string $cropMode = self::CENTERED_CROP,
166
        int $cropPositionX = 0,
167
        int $cropPositionY = 0
168
    ): Image {
169
        // Check if we've recieved a valid crop modus
170
        if ( ! in_array($cropMode, self::CROP_MODUS)) {
171
            throw new InvalidArgumentException(
172
                sprintf(
173
                    'Crop mode \'%s\' is not a valid mode. Choose one of the following %s',
174
                    $cropMode,
175
                    implode(self::CROP_MODUS)
176
                )
177
            );
178
        }
179
180
        // If we want to manually control the crop just set it and return this (fluent interface)
181
        if ($cropMode === self::MANUAL_CROP) {
182
            return $this->setCropPositionX($cropPositionX)->setCropPositionY($cropPositionY);
183
        }
184
185
        // These need to be set with actual values
186
        if ($this->getTargetWidth() === 0 || $this->getTargetHeight() === 0) {
187
            throw new InvalidArgumentException('TargetWidth and TargetHeight cannot be 0.');
188
        }
189
190
        // Do some automatic (lazy) crop calculations
191
        switch ($cropMode) {
192
            case self::UPPER_LEFT_CROP:
193
                $cropPositionX = 0;
194
                $cropPositionY = 0;
195
            break;
196
            case self::UPPER_MIDDLE_CROP:
197
                $cropPositionX = ($this->getOriginalWidth() / 2) - ($this->getTargetWidth() / 2);
198
                $cropPositionY = 0;
199
            break;
200
            case self::UPPER_RIGHT_CROP:
201
                $cropPositionX = $this->getOriginalWidth() - $this->getTargetWidth();
202
                $cropPositionY = 0;
203
            break;
204
            case self::MIDDLE_LEFT_CROP:
205
                $cropPositionX = 0;
206
                $cropPositionY = ($this->getOriginalHeight() / 2) - ($this->getTargetHeight() / 2);
207
            break;
208
            case self::CENTERED_CROP:
209
                $cropPositionX = ($this->getOriginalWidth() / 2) - ($this->getTargetWidth() / 2);
210
                $cropPositionY = ($this->getOriginalHeight() / 2) - ($this->getTargetHeight() / 2);
211
            break;
212
            case self::MIDDLE_RIGHT_CROP:
213
                $cropPositionX = $this->getOriginalWidth() - $this->getTargetWidth();
214
                $cropPositionY = ($this->getOriginalHeight() / 2) - ($this->getTargetHeight() / 2);
215
            break;
216
            case self::BOTTOM_LEFT:
217
                $cropPositionX = 0;
218
                $cropPositionY = $this->getOriginalHeight() - $this->getTargetHeight();
219
            break;
220
            case self::BOTTOM_MIDDLE:
221
                $cropPositionX = ($this->getOriginalWidth() / 2) - ($this->getTargetWidth() / 2);
222
                $cropPositionY = $this->getOriginalHeight() - $this->getTargetHeight();
223
            break;
224
            case self::BOTTOM_RIGHT:
225
                $cropPositionX = $this->getOriginalWidth() - $this->getTargetWidth();
226
                $cropPositionY = $this->getOriginalHeight() - $this->getTargetHeight();
227
            break;
228
        }
229
230
        // Set the calculated crop positions and return this (fluent interface)
231
        return $this->setCropPositionX($cropPositionX)->setCropPositionY($cropPositionY);
232
    }
233
234
    /**
235
     * @return string
236
     */
237
    public function getPath(): string
238
    {
239
        return $this->path;
240
    }
241
242
    /**
243
     * @param string $path
244
     *
245
     * @return Image
246
     */
247
    public function setPath(string $path): Image
248
    {
249
        $this->path = $path;
250
251
        return $this;
252
    }
253
254
    /**
255
     * @return string
256
     */
257
    public function getTargetPath(): string
258
    {
259
        return $this->targetPath;
260
    }
261
262
    /**
263
     * @param string $targetPath
264
     * @return Image
265
     */
266
    public function setTargetPath(string $targetPath): Image
267
    {
268
        $this->targetPath = $targetPath;
269
        return $this;
270
    }
271
272
    /**
273
     * @return SplFileInfo
274
     */
275
    public function getSplFileInfo(): SplFileInfo
276
    {
277
        return $this->splFileInfo;
278
    }
279
280
    /**
281
     * @param SplFileInfo $splFileInfo
282
     *
283
     * @return Image
284
     */
285
    public function setSplFileInfo(SplFileInfo $splFileInfo): Image
286
    {
287
        $this->splFileInfo = $splFileInfo;
288
289
        return $this;
290
    }
291
292
    /**
293
     * @return int
294
     */
295
    public function getTargetWidth(): int
296
    {
297
        return $this->targetWidth;
298
    }
299
300
    /**
301
     * @param int $targetWidth
302
     * @return Image
303
     */
304
    public function setTargetWidth(int $targetWidth): Image
305
    {
306
        $this->targetWidth = $targetWidth;
307
        return $this;
308
    }
309
310
    /**
311
     * @return int
312
     */
313
    public function getOriginalWidth(): int
314
    {
315
        return $this->originalWidth;
316
    }
317
318
    /**
319
     * @param int $originalWidth
320
     * @return Image
321
     */
322
    public function setOriginalWidth(int $originalWidth): Image
323
    {
324
        $this->originalWidth = $originalWidth;
325
        return $this;
326
    }
327
328
    /**
329
     * @return int
330
     */
331
    public function getOriginalHeight(): int
332
    {
333
        return $this->originalHeight;
334
    }
335
336
    /**
337
     * @param int $originalHeight
338
     * @return Image
339
     */
340
    public function setOriginalHeight(int $originalHeight): Image
341
    {
342
        $this->originalHeight = $originalHeight;
343
        return $this;
344
    }
345
346
    /**
347
     * @return int
348
     */
349
    public function getTargetHeight(): int
350
    {
351
        return $this->targetHeight;
352
    }
353
354
    /**
355
     * @param int $targetHeight
356
     * @return Image
357
     */
358
    public function setTargetHeight(int $targetHeight): Image
359
    {
360
        $this->targetHeight = $targetHeight;
361
        return $this;
362
    }
363
364
    /**
365
     * @return int
366
     */
367
    public function getCropPositionX(): int
368
    {
369
        return $this->cropPositionX;
370
    }
371
372
    /**
373
     * @param int $cropPositionX
374
     * @return Image
375
     */
376
    public function setCropPositionX(int $cropPositionX): Image
377
    {
378
        $this->cropPositionX = $cropPositionX;
379
        return $this;
380
    }
381
382
    /**
383
     * @return int
384
     */
385
    public function getCropPositionY(): int
386
    {
387
        return $this->cropPositionY;
388
    }
389
390
    /**
391
     * @param int $cropPositionY
392
     * @return Image
393
     */
394
    public function setCropPositionY(int $cropPositionY): Image
395
    {
396
        $this->cropPositionY = $cropPositionY;
397
        return $this;
398
    }
399
}